iOS 源码分析(一):CTMediator

本文的主要目的是分析CTMediator以及其使用

CTMediator简介

CTMediatorcasatwy开源的一个三方组件通讯框架,下图是通过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的runtimecategory特性动态获取模块
    • 通过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的解耦方式


通过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;
}

参考链接:

推荐阅读更多精彩内容