模块化的意义
当项目大到一定程度,开发人员也多,所有的代码都集中到一个仓库,提交修改都要等其他人提交完成不报错才能开始,非常麻烦。
再者就是代码之间耦合严重,到处引用,穿插错综复杂,往往改变一个变量,需要修改很多处代码,很容易出错。
对于这种情况,就要进行架构整治,模块化无疑是一个好的方案。
模块化的粒度
对于模块化,并不是一味的全部分离成模块就是最好的。模块之间或许会有必要的引用以及上下级依赖关系,没必要完全的独立。再者需要考虑到业务变化时,可能又要重新划分模块,工作量和成本又高了。
iOS 模块化的划分应该遵循 SOLID原则 ,如下几点:
(S)单一原则
:对象的功能要单一,不要是很多功能的集合体
例如CALayer
负责动画视图显示,UIView
则负责事件传递,事件响应
(O)开闭原则
:“开”,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;开闭原则中“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代码。(L)里氏替换原则
:子类对象是可以替换基类对象的。所有引用基类的地方必须能透明地使用其子类的对象。
例如KVO
的实现机制,利用isa-swizzling
父类指向子类。
(I)接口隔离原则
:接口的用途要单一,不要一个接口上根据入参不同实现不同的功能。
可以使用多个专门的协议,而不是使用一个庞大臃肿的协议。
(D)依赖反转原则
:方法应该依赖抽象,不要依赖实例。面向接口编程,不要面向实现编程。让调用接口感觉不到内部是如何操作。
还一个 (D)迪米特法则
:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。即高聚合,低耦合。
可以顺带记忆
组件
可组装,独立的业务单元,高内聚,低耦合的特性。
iOS开发的组件,不应是UI控件
,也不是UIViewController
这种大UI和功能的集合,应该是包含UI控件
,小功能集合的组件这样划分。
组件化方案
MGJRouter 的路由映射
https://github.com/meili/MGJRouter
蘑菇街的组件化方案 MGJRouter
[MGJRouter registerURLPattern:@"mgj://detail?id=:id" toHandler:^(NSDictionary *routerParameters) {
NSNumber *id = routerParameters[@"id"];
// create view controller with id
// push view controller
}];
是一个路由方案url-block
,通过注册url
和block
到一个单例中的字典routes
中。
当调用
[MGJRouter openURL:@"mgj://detail?id=404"]
就通过Url
去从字典routes
取其block
来执行。这样内存中维护了很多映射关系,Url
这样的使用方式,也比较繁琐。而且需要将Url
的时机必须要在调用之前,在+load
进行注册又拖长了App启动时间,在其他时机注册又需要统筹兼顾。
另外还有protocol-class
和url-controller
,这些方式都大同小异,需要维护一个映射表,是一个短板,个人更倾向于CTMediator
这种中间者架构
CTMediator 中间者
https://github.com/casatwy/CTMediator
其实就是使用runtime
通过[target performSelector:action withObject:params];
取到需要的数据,例如界面,视图。
如何使用这些界面视图就不关CTMediator
的事了,这些界面,视图就可以封装成单独的模块,互无关系。
Category的编写
CTMediator
的关键在于其分类的编写,每个模块功能的分类由其模块开发者编写。
- (UIViewController *)CTMediator_viewControllerForDetail
{
UIViewController *viewController = [self performTarget:kCTMediatorTargetA
action:kCTMediatorActionNativeFetchDetailViewController
params:@{@"key":@"value"}
shouldCacheTarget:NO
];
if ([viewController isKindOfClass:[UIViewController class]]) {
// view controller 交付出去之后,可以由外界选择是push还是present
return viewController;
} else {
// 这里处理异常场景,具体如何处理取决于产品
return [[UIViewController alloc] init];
}
}
这是demo
中分类的一个方法实现,kCTMediatorTargetA
隶属于模块A
kCTMediatorTargetA
中有一个
kCTMediatorActionNativeFetchDetailViewController方法可以获取一个
UIViewController`,
通过写CTMediator
对于模块A的分类,就可以获取模块A中相关的界面以及UI,例如CTMediator_viewControllerForDetail
。
这样的优势在于,没有了key-value
的映射关系,不需要维护url映射表
,更不需要存储block块,Category For A
由开发模块A
的人员来写,他很熟悉模块A
(对于他来说将模块A
中某个界面取出,并设置相关参数是很简单的事情),通过分类再转给不熟悉的人(无需关心模块A
的内容)使用,就很方便了。
对于远程url
调用,同样是通过将url
解析成对应的动作,例如取某个视图,推出某个界面。
组件化能让工程架构更加清晰,每个人负责一个模块,通过pod导入,互不影响。