本文的主要目的是分析CTMediator以及其使用
CTMediator简介
CTMediator是casatwy
开源的一个三方组件通讯框架,下图是通过CTMediator通讯的整体架构图示
优点:
- 组件仅通过Action暴露可调用接口,模块与模块之间的接口被固化在了Target-Action这一层,避免了实施组件化的改造过程中,对Business的侵入,同时也提高了组件化接口的可维护性。
- 方便传递各种类型的参数。
- 本地组件通讯为远程组件通讯提供服务
- 利用 category 可以明确声明的接口,进行编译检查
- 实现方式属于轻量级
缺点:
- 需要给每一个模块创建对应的 target和 mediator 分类,模块化代码时较为繁琐
- 在 category 中仍然使用了字符串硬编码,内部使用字典传参
- 无法保证所调用的目标模块一定存在,运行时才能发现错误
CTMediator原理
解耦原理
- 通过
中介模式
,将CTMediator类作为各个模块的中心,各个模块以CTMediator分类的形式扩展功能,CTMediator分类中提供服务 分类中的函数具体去调用target-action
,target和action需要以字符串硬编码
的形式,如果模块比较多,提供服务比较多,那么字符串的硬编码需要时间维护。去model化思想
:如果A模块向B模块传递信息,推荐参数使用基本数据类型,而不是以model形式提供,否则A模块依赖同一个model,B模块也依赖同一个model,这样模块间还是存在相互依赖,没有达到真正完全耦合。
实现原理
- 基于OC的
runtime
、category
特性动态获取模块
- 通过
NSClassFromString
动态获取类,并创建实例 - 通过
NSSelectorFromString
动态获取SEL
- 通过
performSelector+NSInvocation
动态调用方法
- performSelector响应OC的动态性,将
方法的绑定延迟到运行时
,即在编译阶段不会检测方法的有效性(如果方法不存在也不会报错)。 - 如果方法名未知可能会引起内存泄漏相关问题,可以通过以下代码忽略此警告
//如果方法名称是动态不确定的,会有如下提示 ⚠️ PerformSelector may cause a leak because its selector is unknown #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [self performSelector:selector]; #pragma clang diagnostic pop
performSelector
提供动态执行方法的能力
NSInvocation
提供了消息调用的能力
方法调用
的本质是消息发送
,即底层调用的是objc_msgSend
,可以从objc源码得到验证
- (id)performSelector:(SEL)sel withObject:(id)obj { if (!sel) [self doesNotRecognizeSelector:sel]; return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj); }
源码分析
通过阅读CTMediator的源码可知,以下是源码的通讯过程图示
以上是组件化方案的一个简化版架构描述,主要是基于Mediator模式
和Target-Action模式
,中间采用了runtime
来完成调用。这套组件化方案将远程应用调用和本地应用调用做了拆分,而且是由本地应用调用为远程应用调用提供服务
CTMediator调用的方式有两种(伪代码实现)
- 本地组件化通讯:本地原生模块间的调用
- (id _Nullable )performTarget:action:params:shouldCacheTarget:{ //拼接类名字符串 NSString *targetClassString = "Target_" + targetNaem; //从缓存中获取类 NSObject *target = [self safeFetchCachedTarget:targetClassString] if (缓存中没有target){ Class targetClass = NSClassFromString(targetClassString); target = [[targetClass alloc] init]; } //拼接方法字符串 NSString *actionString = "Action_" + actionName; //生成SEL SEL action = NSSelectorFromString(actionString); if (target为空){ 处理target为空的情况 } //是否缓存target if (shouldCacheTarget) { //以key-value的形式缓存 (key:target, value:targetClassString) } //判断target是否响应action if ([target respondsToSelector:action]) { return [self safePerformAction:action target:target params:params]; }else{ 处理target未响应action的情况 } } - (id)safePerformAction:target:params:{ //根据action生成签名 NSMethodSignature* methodSig = [target methodSignatureForSelector:action]; //根据不同的返回类型,进行封装 //根据签名对象创建调用对象invocation NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig]; invocation设置target、action、params //消息调用 [invocation invoke]; //提供动态执行方法 return [target performSelector:action withObject:params]; }
- 远程组件化通讯:其他app调用,主要是通过openURL的方式,需要在AppDelegate的openUrl代理方法中进行处理
- (id _Nullable)performActionWithUrl:completion:{ 处理url参数 - 遍历并存放到字典中 处理targetName,目的是为了防止黑客通过远程方式调用本地模块 [self performTarget:action:params:shouldCacheTarget:]; 回调处理 }
CTMediator使用
传统的界面跳转方式如下,模块间高度耦合
以下是以Home、Detail模块为例,通过CTMediator的解耦方式
以下是Demo代码的整体结构
整体结构
具体的实现代码如下
- Home模块实现的核心代码如下
- (void)pushDetailAction:(UIButton *)sender{ UIViewController *vc = [MIM() MIMediator_pushDetail]; [self.navigationController pushViewController:vc animated:YES]; }
- Detail模块实现的核心代码如下
- (void)showAlert{ // [[MIMediator sharedInstance] MIMediator_showAlert]; [MIM() MIMediator_showAlert]; }
可以简化组件化单例调用方式
// 简化调用单例的函数 MIMediator* _Nonnull MIMD(void);
- 组件化通讯实现的代码如下
//MIMediator+Universal中的实现 - (UIViewController *)MIMediator_pushDetail{ UIViewController *vc = [self performTarget:kMIMediatorTargetDetail action:kMIMediatorActionPushDetail params:nil shouldCacheTarget:NO]; if ([vc isKindOfClass:[UIViewController class]]) { return vc; }else{ return [[UIViewController alloc] init];; } } - (void)MIMediator_showAlert{ [self performTarget:kMIMediatorTargetDetail action:kMIMediatorActionShowAlert params:nil shouldCacheTarget:NO]; } //Target_Detail中的实现 - (UIViewController *)Action_pushToDetail:(NSDictionary *)param{ DetailViewController *detailVC = [[DetailViewController alloc] init]; detailVC.title = @"详情页"; return detailVC; } - (id)Action_showAlert:(NSDictionary *)dic{ UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"alert title" message:@"alert message" preferredStyle:UIAlertControllerStyleAlert]; [alertVC addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil]]; [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertVC animated:YES completion:nil]; return nil; }
参考链接: