CTMediator

作者: forping | 来源:发表于2020-12-23 13:52 被阅读0次

    基本概念

    首先,对于 iOS 这种面向对象编程的开发模式来说,我们应该遵循以下五个原则,即 SOLID 原则

    另外,组件可以认为是可组装的、独立的业务单元,具有高内聚,低耦合的特性,是一种比较适中的粒度。就像用乐高拼房子一样,每个对象就是一块小积木。一个组件就是由一块一块的小积木组成的有单一功能的组合,比如门、柱子、烟囱。这让我想到了viper设计模式

    iOS 开发中的组件,不是 UI 的控件,也不是 ViewController 这种大 UI 和功能的集合。因为,UI 控件的粒度太小,而页面的粒度又太大。iOS 组件,应该是包含 UI 控件、相关多个小功能的合集,是一种粒度适中的模块。
    而对于组件间如何分层这个问题,层级最好不要超过三个,你可以这么设置:

    • 底层可以是与业务无关的基础组件,比如网络和存储等;
    • 中间层一般是通用的业务组件,比如账号、埋点、支付、购物车等;
    • 最上层是迭代业务组件,更新频率最高。

    在实践中,,组件化的设计方法一般分为了协议式中间者两种架构设计方案

    协议式架构设计主要采用的是协议式编程的思路:在编译层面使用协议定义规范,实现在不同地方,从而达到分布管理和维护组件的目的。这种方式也遵循了依赖反转原则,是一种很好的面向对象编程的实践。
    但是,这个方案的缺点也很明显,主要体现在以下两个方面:

    1. 由于协议式编程缺少统一调度层,导致难于集中管理,特别是项目规模变大、团队变多的情况下,架构管控就会显得越来越重要。
    2. 协议式编程接口定义模式过于规范,从而使得架构的灵活性不够高。当需要引入一个新的设计模式来开发时,我们就会发现很难融入到当前架构中,缺乏架构的统一性。

    中间者架构。它采用中间者统一管理的方式,来控制 App 的整个生命周期中组件间的调用关系。同时,iOS 对于组件接口的设计也需要保持一致性,方便中间者统一调用。

    image.png

    拆分的组件都会依赖于中间者,但是组间之间就不存在相互依赖的关系了。由于其他组件都会依赖于这个中间者,相互间的通信都会通过中间者统一调度,所以组件间的通信也就更容易管理了。在中间者上也能够轻松添加新的设计模式,从而使得架构更容易扩展。

    好的架构一定是健壮的、灵活的。中间者架构的易管控带来的架构更稳固,易扩展带来的灵活性, CTMediator 就是按照中间者架构思路设计的。

    CTMediator

    CTMediator 使用的是运行时解耦,解耦核心方法如下所示:

    - (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
    {
        NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
        
        // 适配swift
        NSString *targetClassString = nil;
        if (swiftModuleName.length > 0) {
            targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
        } else {
            targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
        }
        // 获取缓存的target
        NSObject *target = self.cachedTarget[targetClassString];
        if (target == nil) {
            // 没有缓存则创建
            Class targetClass = NSClassFromString(targetClassString);
            target = [[targetClass alloc] init];
        }
    
    
        // 创建消息
        NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
        SEL action = NSSelectorFromString(actionString);
        
        if (target == nil) {
            // 这里是处理无响应请求的地方之一
            [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
            return nil;
        }
        
        // 缓存target
        if (shouldCacheTarget) {
            self.cachedTarget[targetClassString] = target;
        }
    
    
        if ([target respondsToSelector:action]) {
          // 发送请求
            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都没有的时候,
                [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
                // 移除缓存
                [self.cachedTarget removeObjectForKey:targetClassString];
                return nil;
            }
        }
    

    performTarget:action:params:shouldCacheTarget:方法主要是对 targetName 和 actionName 进行容错处理,也就是对调用方法无响应的处理。
    这个方法封装了safePerformAction:target:params 方法,入参 targetName 就是调用接口的对象,actionName 是调用的方法名,params 是参数。

    并且代码中同时还能看出只有满足Target_ 前缀的类的对象Action_ 的方法才能被 CTMediator 使用。这时,我们可以看出中间者架构的优势,也就是利于统一管理,可以轻松管控制定的规则。

    处理无响应的情况
    #pragma mark - private methods
    - (void)NoTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams
    {
        // 给一个固定的target专门用于在这个时候顶上,处理这种请求的
        SEL action = NSSelectorFromString(@"Action_response:");
        NSObject *target = [[NSClassFromString(@"Target_NoTargetAction") alloc] init];
        
        NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
        params[@"originParams"] = originParams;
        params[@"targetString"] = targetString;
        params[@"selectorString"] = selectorString;
        
        [self safePerformAction:action target:target params:params];
    }
    
    处理有action的情况
    - (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
    {
        // 获取方法签名
        NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
        if(methodSig == nil) {
            return nil;
        }
        const char* retType = [methodSig methodReturnType];
        
        // 对非对象的返回类型做一次封装
        
        // 如果返回值类型是 void
        if (strcmp(retType, @encode(void)) == 0) {
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
            [invocation setArgument:&params atIndex:2];
            [invocation setSelector:action];
            [invocation setTarget:target];
            [invocation invoke];
            return nil;
        }
        
        // 如果是整型
        if (strcmp(retType, @encode(NSInteger)) == 0) {
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
            [invocation setArgument:&params atIndex:2];
            [invocation setSelector:action];
            [invocation setTarget:target];
            [invocation invoke];
            NSInteger result = 0;
            [invocation getReturnValue:&result];
            return @(result);
        }
    
        // 如果是bool类型
        if (strcmp(retType, @encode(BOOL)) == 0) {
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
            [invocation setArgument:&params atIndex:2];
            [invocation setSelector:action];
            [invocation setTarget:target];
            [invocation invoke];
            BOOL result = 0;
            [invocation getReturnValue:&result];
            return @(result);
        }
    
        // 如果是浮点型
        if (strcmp(retType, @encode(CGFloat)) == 0) {
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
            [invocation setArgument:&params atIndex:2];
            [invocation setSelector:action];
            [invocation setTarget:target];
            [invocation invoke];
            CGFloat result = 0;
            [invocation getReturnValue:&result];
            return @(result);
        }
    
        // 如果是无符号整型
        if (strcmp(retType, @encode(NSUInteger)) == 0) {
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
            [invocation setArgument:&params atIndex:2];
            [invocation setSelector:action];
            [invocation setTarget:target];
            [invocation invoke];
            NSUInteger result = 0;
            [invocation getReturnValue:&result];
            return @(result);
        }
    
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        return [target performSelector:action withObject:params];
    #pragma clang diagnostic pop
    }
    

    简单实例

    弹出一个Alert

    [[CTMediator sharedInstance] CTMediator_showAlertWithMessage:@"casa" cancelAction:nil confirmAction:^(NSDictionary *info) {
                // 做你想做的事
            }];
    

    内部实现

    NSString * const kCTMediatorTargetA = @"A";
    NSString * const kCTMediatorActionShowAlert = @"showAlert";
    
    - (void)CTMediator_showAlertWithMessage:(NSString *)message cancelAction:(void(^)(NSDictionary *info))cancelAction confirmAction:(void(^)(NSDictionary *info))confirmAction
    {
        NSMutableDictionary *paramsToSend = [[NSMutableDictionary alloc] init];
        if (message) {
            paramsToSend[@"message"] = message;
        }
        if (cancelAction) {
            paramsToSend[@"cancelAction"] = cancelAction;
        }
        if (confirmAction) {
            paramsToSend[@"confirmAction"] = confirmAction;
        }
        [self performTarget:kCTMediatorTargetA
                     action:kCTMediatorActionShowAlert
                     params:paramsToSend
          shouldCacheTarget:NO];
    }
    
    @interface Target_A : NSObject
    - (id)Action_showAlert:(NSDictionary *)params;
    @end
    @implementation Target_A
    - (id)Action_showAlert:(NSDictionary *)params
    {
        UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
            CTUrlRouterCallbackBlock callback = params[@"cancelAction"];
            if (callback) {
                callback(@{@"alertAction":action});
            }
        }];
        
        UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"confirm" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            CTUrlRouterCallbackBlock callback = params[@"confirmAction"];
            if (callback) {
                callback(@{@"alertAction":action});
            }
        }];
        
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"alert from Module A" message:params[@"message"] preferredStyle:UIAlertControllerStyleAlert];
        [alertController addAction:cancelAction];
        [alertController addAction:confirmAction];
        [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
        return nil;
    }
    @end
    

    可以看出,指定了类名和调用方法名,把参数封装成字典传进去就能够直接调用该方法了。
    但是,这种运行时直接硬编码的调用方式也有些缺点,主要表现在两个方面:

    1. 直接硬编码的调用方式,参数是以 string 的方法保存在内存里,虽然和将参数保存在 Text 字段里占用的内存差不多,同时还可以避免.h 文件的耦合,但是其对代码编写效率的降低也比较明显。
    2. 由于是在运行时才确定的调用方法,调用方式由 [obj method] 变成 [obj performSelector:@""]。这样的话,在调用时就缺少类型检查,是个很大的缺憾。因为,如果方法和参数比较多的时候,代码编写效率就会比较低。

    相关文章

      网友评论

        本文标题:CTMediator

        本文链接:https://www.haomeiwen.com/subject/edvsnktx.html