1.什么是组件?
A.组件很类似功能模块,但会更细致一点.
B.其实不需要纠结单个组件到底是不是以对象实体的形式存在于内存中
,因为不同的实现方式做法不一样.
C.组件化的精髓在于App工程内部拆分多个组件后,组件之间相互不引用对方的代码,组件调用组件由中间件
统一调度,组件内页面跳转+组件间页面跳转也由中间件
调度.
2.悲痛的教训
场景1:一个App项目中团队人员比较多,不同的人负责不同的模块开发,有的
人直接使用资源文件设计的,有的人用代码直接写的,有的人负责登录,有的
人负责订单,突然有一天搞订单的开发A找搞登录的开发B说要调一下登录,登
录成功以后你要再回调下我写的模块的方法告诉我成功登录,我要刷新一下订
单页面,B傻傻的就答应了,找B的人C、D、F....越来越多,B负责的代码越写
越多,同时A也不怎么开心,因为A发现调B写的登录要通过类实例化函数获取
模块,调C写的支付使用工厂方法,调D写的计算器组件又是另外一种写法,结
果A自己的代码也越来越丑。
场景1告诉我们:
没有中间件==>各自为战,相互伤害
有中间件==>各自为战,相安无事
深有体会
场景2:一个App里面有很多内嵌的H5页面,缠品A对猿B说,我们的活动页面
要调用一下我们的订单页面,用户如果下了一个订单成功以后H5要能够拿到反
馈有欢迎语,猿B和H5的开发猿C经过很久很久的讨论,确定了H5如果调用
App的订单页面,参数怎么传,订单提交以后怎么再调H5的接口,参数怎么定
义,各自把代码写到各自的项目里,没过多久缠品A说另外的H5要调用原生的
界面,怎么怎么个流程,推送点击要调用原生的某个页面,点完要反馈给后台
统计,兄弟App要跳转到我们的App某个页面跳转完成某个动作以后要再跳转
回去......猿B每每接到这样的需求就紧紧握住自己中箭的膝盖,收拾了一下写的
那么多代码,深藏功与名......🌚.
场景2告诉我们:
没有中间件==>业务代码混杂,代码复用率底下
有中间件==>业务分离,代码复用率高
体会不是那么深
3.如何拆分组件
3.1 基础功能组件
类似于性能统计、Networking、Patch、网络诊断等
按功能分库,不涉及产品业务需求,跟库Library类似
通过良好的接口拱上层业务组件调用;
不写入产品定制逻辑,通过扩展接口完成定制;
3.2 基础UI组件
产品内通用UI组件;(各个业务模块依赖使用,但需要保持好定制扩展的设计)
公共通用UI组件;(不涉及具体产品的视觉设计, 目前较少)
3.3 产品业务组件
例如:圈子、1元购、登录、客服MM等
4.各大门派的做法
4.1 蘑菇街的做法:
- 页面跳转:
[MGJRouter registerURLPattern:@"mgj://detail?id=:id" toHandler:^(NSDictionary *routerParameters) {
NSNumber *id = routerParameters[@"id"];
// create view controller with id
// push view controller
}];
[MGJRouter openURL:@"mgj://detail?id=10"]
- 组件间功能调用:
作者表述有两种方案:
<方案1>
//register
[MGJRouter registerURLPattern:@"mgj://cart/ordercount" toObjectHandler:^id(NSDictionary *routerParamters){
// do some calculation
return @42;
}];
//use
NSNumber *orderCount = [MGJRouter objectForURL:@"mgj://cart/ordercount"]
<方案2>
每个组件都是真实存在于内存中,所有组件归属于ModuleManager
.组件间靠已经约定好的协议
进行通信.(作者表述这种用的更广泛).
蘑菇街总结:(忽略
MGJRouter
实现组件间功能调用
)MGJRouter
只负责页面跳转.也就是说蘑菇街的方案中,Router(路由)只不过是组件化的一部分.
4.2 CTMediator的做法:
将ModuleA
组件的所有方法写在分类CTMediator+CTMediatorModuleAActions
中.
- 页面跳转:
UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail];
[self.navigationController pushViewController:viewController animated:YES];
- 组件间功能调用:
[[CTMediator sharedInstance] CTMediator_presentImage:[UIImage imageNamed:@"image"]];
CTMediator总结:简单粗暴.
4.3 网易的做法
网易的做法的在使用外观上看上去和MGJ是一样的,不过不同的是MGJ采取的前置注册的策略,而网易采取的是后置询问的策略.
MGJ:
App launch 完成,建立各个组件所有页面与url的映射关系,建立各个组件所有协议与服务类的映射关系.
真正调用当然就像字典取值一样简单明了.
网易:
App launch 完成,各个组件只在中间件内设置一个点连接点.
到真正调用的时候,中间件通过遍历这些连接点询问各组件谁能对这个调用做出反应.
5.关于路由(Router)多说两句
5.1 岛内(App内)
大前端对Router
的运用,在SPA上解决URL和UI的同步问题.
iOS端也有许多关于Router
的轮子(HHRouter
,JLRoutes
,MGJRouter
),功能大同小异,都是以URLPattern
做键,存入一个block或者是以个VC的类名.
存取方式
JLRoutes:对URLPattern挨个匹配,返回结果(查找较慢)
HHRouter,MGJRouter:对URLPattern做打断,分级存储(查找较快)
存入类型
HHRouter:支持VC+block
MGJRouter,JLRoutes:只支持block
如果组件化用到了路由,个人意见:
App组件化专注于两个问题:
1.各个页面之间的跳转问题.
2.各个组件之间相互调用.
App内路由专注于:
各个页面之间的跳转问题.
就像上面提到的MGJRouter
,采用Router
还是用于页面跳转,不怎么用与组件间的调用.用url
来指定跳转到那个页面无论是iOS,安卓,web是相通的.而且三端都走一套逻辑,在App上线突然遇到了紧急bug,就发布动态降级成H5或者一个本地的错误界面.
反观用Router
打开url
来调用方法外加获取返回值(如:NSNumber *orderCount = [MGJRouter objectForURL:@"mgj://cart/ordercount"]
),混杂于上面Router
协调页面跳转的逻辑中真的是有一种怪怪的感觉.
路由只不过是组件化的一部分.
5.2 岛外(App外)
Route should be:
Scheme + module + VC + param
上面的讨论全在App内,所以只有module + VC + param
,现在看看Scheme
.
将App内的逻辑公布于外(Share your App with the world),一个Scheme
就可以进入你的App.
一个app==>孤岛
一片app==>群岛
群岛内岛与岛用Scheme
跳转,具体到具体的互通业务则是岛内Route
与岛内Route
的对接.
看完组件是什么,如何拆分组件,各大门派的做法后,我在现有的大工程内效仿MGJ,工程内加入MGJRouter+加了一个协议映射服务类的中间件.全局页面跳转不再写
push
+present
,A模块
调用B模块
的方法也不再直接调用,组件化完成了!!!本宝宝还太年轻
文章参考:
Limboy>蘑菇街 App 的组件化之路
Casa>iOS应用架构谈 组件化方案
iOS组件化实践方案-LDBusMediator炼就
iOS 组件化 —— 路由设计思路分析
带你一步步构建iOS路由
移动端路由层设计