# 为什么要组件化
1.实现之间解耦、减少项目的编译时间,提升业务开发效率
2.模块拆分成组件,方便对外付能
## 组件化方案
1.代码解耦
1)组件原则
组件通常分为两种类型的组件:基础组件,业务组件。
*业务组件依赖基础组件
*基础组件不可依赖业务组件。
*业务组件间不可相互依赖。
2)组件间的通信方式:
组件间通信方式,业内主要有两种实现方式:
1.协议式框架,比如蘑菇街的这种方案。蘑菇街 App 的组件化之路。不建议
2. 中间者架构,比如casatwy的方案
casatwy的方案-基于Category和Target-Action
主要是基于Mediator模式和Target-Action模式,中间采用了runtime来完成调用。通过mediator的Category来输出组件的对外调用方法。使用同一种方案,实现组件间调用。
优点:
1)不需要注册URL
2)使用同一种方案,基于Target-Action模式实现组件间的调用。
3)区分app内部调用和外部调用。
4)同一种方法可以实现简单参数和复杂参数的调用。
2.版本管理
版本管理使用 cocoapods,每个组件都拆成独立的pod库,并生成一个配置表,来进行组件间的依赖组件的版本管理。
1.通过 Cocoapods 搭建私有库,创建相应的模版。
2.不推荐 Cocoapods 编译二进制文件,自己写脚本编起来更灵活。
3.自定义 gem,完成 podspec 源码二进制切换。
4.二进制文件(组件编译为静态包)存储到内部云。
5.使用工具/脚本管理 podfile。
6.系统管理 pod 库。
工程结构
通过 Cocoapods 搭的自定义库,自定义模版。
每个同学拿到的组件都是一个相同结构的工程,所需要做的工作就是在相应的 Pods/Developemnt Pods/ 组件 /Classes 下编码,组件输出类通过模版创建,可在相应的类里使用 JDROUTER_EXTERN_METHOD 提供接口。
## 组件化通信
1、URL-Block的方式 蘑菇街为代表
这种方式的核心是预先将url对应的方法先注册给中间人,然后调用方调用的时候,中间人就会去哈希表或者数组里找到对应的方法进行调用。
总结:
1、被调用方需要提前register相应的block才能调用,而且被注册的block似乎并没有很好的释放时机?内存方面是否存在问题。
2、调用方和被调用方都需要依赖中间件。
3、单纯的URL拼接的方式并没有办法满足复杂的参数传递,所以才会衍生多种调用的方法。
2、Protocol-Class的方式
这种方案类似于第一种方案,它是保存和Protocol和Class的映射关系,每次调用方通过中间人拿到注册时候的Class,直接通过对象进行方法调用;
[Router registerClass:[SomeClass Class] forProtocol:aProtocol];
id someOne =[Router classForProtocol:aProtocol];
[someOne call];
总结:
1、类似于block的注册方式,也需要提前register对象。
2、调用双方都需要依赖中间管理类。
3、调用方式是直接调用,比较符合常用习惯。
3、Target-Selector的方式
这个方案是由CTMediator的作者提出的,他指出了一些URL-Block方式的缺点,然后提出了这种方案。
这个方案的核心就是我们平常使用的performSelector的方式。
// 远程App调用入口
-(id)performActionWithUrl:(NSURL *)url completion:(void(^)(NSDictionary *info))completion;
// 本地组件调用入口
-(id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget;
-(void)releaseCachedTargetWithTargetName:(NSString *)targetName;
中间人负责找到具体的target,然后实现方法转发。这个方式还强调了要区分本地调用和远程调用的方式,以防止黑客直接通过浏览器或OpenUrl的方式直接访问到APP内部模块。
核心实现主要是:
-(id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
"把对象名转换为相应的类"
// generate target
NSString *targetClassString = nil;
if (swiftModuleName.length > 0) {
targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
} else {
targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
}
NSObject *target = self.cachedTarget[targetClassString];
if (target == nil) {
Class targetClass = NSClassFromString(targetClassString);
target = [[targetClass alloc] init];
}
// generate action
NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
SEL action = NSSelectorFromString(actionString);
if (target == nil) {
// 这里是处理无响应请求的地方之一,这个demo做得比较简单,如果没有可以响应的target,就直接return了。实际开发过程中是可以事先给一个固定的target专门用于在这个时候顶上,然后处理这种请求的
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
return nil;
}
if (shouldCacheTarget) {
self.cachedTarget[targetClassString] = target;
}
if ([target respondsToSelector:action]) {
"消息转发,safePerformAction使用NSInvocation进行转发"
return [self safePerformAction:action target:target params:params];
} else {
// 这里是处理无响应请求的地方,如果无响应,则尝试调用对应target的notFound方法统一处理
SEL action = NSSelectorFromString(@"notFound:");
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target params:params];
} else {
// 这里也是处理无响应请求的地方,在notFound都没有的时候,这个demo是直接return了。实际开发过程中,可以用前面提到的固定的target顶上的。
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
[self.cachedTarget removeObjectForKey:targetClassString];
return nil;
}
}
}
总结:
1、需要额外维护中间类拓展,只有调用方需要引用中间类。