背景:随着应用不停得进行版本迭代、功能模块的逐步增加,使得应用的代码体积越来越大,业务和功能间的依赖关系越来越复杂,耦合度越来越高,严重得降低了业务方的开发效率。在此背景下,组件化开发的思路得以进入众多app开发架构的设计方案之中。
简介:组件化开发的方案是将app分成了多个组件进行开发,各个组件之间可以互相通信,但是组件之间不能有直接的约束、不能直接引用。
实现方式:1 URL--Block方式;2 Target--Action方式。
1 URL-Block方式
蘑菇街App采用的组件化模式,每次启动时需要实例化各个模块的组件,并且通过URL注册响应者,每次注册时会将URL作为一个key值,与对应的Block保存在本地的注册表之中。组件管理器就是通过本地保存的这张注册表去知道有哪些模块哪些接口,接口的形式就是URL-Block。
[MGJRouter registerURLPattern:@"mgj://cart/ordercount" toObjectHandler:^id(NSDictionary *routerParamters){
// do some calculation
return @42;
}]
NSNumber *orderCount = [MGJRouter objectForURL:@"mgj://cart/ordercount”]
缺点:(1)采用key-block形式需要内存维护一张注册表,造成不必要的内存常驻。
(2)没有区分远程调用和本地调用。
2 Target-Action方式
每个组件都通过Action暴露可调用接口,组件通过自带得Target-Action来响应。
[[CTMediator sharedInstance] performTarget:targetName action:actionName params:@{…}]
根据targetName和actionName,可以通过Runtime机制转化生成Target的实例以及Action的选择子
NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
Class targetClass = NSClassFromString(targetClassString);
id target = [[targetClass alloc] init];
SEL action = NSSelectorFromString(actionString);
最后通过获得实例对象及选择子调用目标业务逻辑。
[target performSelector:action withObjects:params];
同时Target-Action的方式中区分了远程调用和本地调用,由本地调用服务为远程调用提供服务
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options
{
return [[[CTMediator sharedInstance] performActionWithUrl:url completion:nil] boolValue];
}
由于远程调用只能通过jsonString的形式来提供复杂参数,所以我们在远程调用时只能将非常规的参数转化成jsonString再通过urlEncode后,通过GET的形式来提供复杂参数。在openURL方法中接收到url后,要对url进行解析得到我们想要的参数才能调用本地调用的服务。结论:远程调用比本地调用多了一个解析URL的过程,因此远程调用和本地调用必须拆开,不能走同一个接口。
- (id)performActionWithUrl:(NSURL *)url completion:(void (^)(NSDictionary *))completion
{
/*
....(在此对url进行解析,并且得到targetName和actionName以及params)
*/
//然后再走本地调用的接口
id result = [self performTarget:targetName action:actionName params:params];
return result;
}
使用category定制不同组件的中间件
因为调用方不知道将要调起的组件需要传递哪些参数、以及哪些target可以调用,所以可以使用category的优势来针对每个组件写一个有针对性的中间件的类别。
//CTMediator+CTMediatorModuleAActions.m
- (void)CTMediator_presentImage:(UIImage *)image
{
if (image) {
[self performTarget:kCTMediatorTargetA
action:kCTMediatorActionNativePresentImage
params:@{@"image":image}];
} else {
// 这里处理image为nil的场景,如何处理取决于产品
[self performTarget:kCTMediatorTargetA
action:kCTMediatorActionNativeNoImage
params:@{@"image":[UIImage imageNamed:@"noImage"]}];
}
}
这样做得好处:
(1)在category的方法中可以做参数验证,例如上面的方法中,接收的参数为image对象,就可以校验调用方传的参数是否为image。是image的话在category方法中将其封装再走本地调用组件的方法。
(2)category统一了组件调用入口,因此无论在调试还是在源码阅读上都为开发者提供了极大的便利。
(3)由于category统一了组件调用入口,因此可以在category中使用宏或者调用声明。
(4)使用category可以解决组件间调用去Model化带来的参数繁杂的问题。
拆分组件
(1)基础功能组件
(2)基础UI组件
(3)产品业务组件
总结:组件化适用于业务稳定、逻辑复杂的app,能够解决项目模块间得耦合问题,有助于多人大团队的协同开发。方便组件的单独开发、单独测试。
参考文章:
http://www.th7.cn/Program/IOS/201603/776240.shtml
http://www.tuicool.com/articles/AB7jqeY
http://www.jianshu.com/p/afb9b52143d4