美文网首页iOS程序员
CTMediator 源码解析

CTMediator 源码解析

作者: iOS扫地僧 | 来源:发表于2019-03-20 17:30 被阅读0次

    获取CTMediator单例对象

    #pragma mark - public methods
    + (instancetype)sharedInstance
    {
        static CTMediator *mediator;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            mediator = [[CTMediator alloc] init];
        });
        return mediator;
    }
    

    远程App调用入口

    /*
     scheme://[target]/[action]?[params]
     
     url sample:
     aaa://targetA/actionB?id=1234&title=title
     
     [url query]:  id=1234&title=title
     [url path]:  /actionB
     [url host]:  targetA
     */
    
    - (id)performActionWithUrl:(NSURL *)url completion:(void (^)(NSDictionary *))completion
    {
        //url参数的处理
        NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
        //
        NSString *urlString = [url query];
        for (NSString *param in [urlString componentsSeparatedByString:@"&"]) {
            NSArray *elts = [param componentsSeparatedByString:@"="];
            if([elts count] < 2) continue;
            [params setObject:[elts lastObject] forKey:[elts firstObject]];
        }
        
        // 这里这么写主要是出于安全考虑,防止黑客通过远程方式调用本地模块。这里的做法足以应对绝大多数场景,如果要求更加严苛,也可以做更加复杂的安全逻辑。
        NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
        if ([actionName hasPrefix:@"native"]) {
            return @(NO);
        }
        
        // 这个demo针对URL的路由处理非常简单,就只是取对应的target名字和method名字,但这已经足以应对绝大部份需求。如果需要拓展,可以在这个方法调用之前加入完整的路由逻辑
        id result = [self performTarget:url.host action:actionName params:params shouldCacheTarget:NO];
        if (completion) {
            if (result) {
                completion(@{@"result":result});
            } else {
                completion(nil);
            }
        }
        return result;
    }
    

    本地组件调用入口

    /**
     *  本地组件调用入口
     *
     *  @param targetName 类对象   OC中类对象是要Target_为前缀的
     *  @param actionName 方法名称  最后实际调用的是以Action_为前缀的
     *  @param params     参数
     *  @param shouldCacheTarget 是否缓存拼接后的类对象
     *
     *  @return return value JSon格式的字符串
     */
    - (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
    {
        //供swift项目使用
        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];
        }
    
        // 拼装方法字符串
        NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
        // 生成SEL
        SEL action = NSSelectorFromString(actionString);
        //先从缓存取,取不到去创建,但是也有可能创建失败的情况(targetName值不正确)
        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]) {
            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;
            }
        }
    }
    

    注意:想要调用此方法,你的类必须是Target_为前缀的,而方法必须是Action_为前缀的,另外代码中对于处理无响应的情况分了两种情况:

    • target == nil 调用NoTargetActionResponseWithTargetString方法
    • action无法响应的时候,会先去响应notFound方法
      如果notFound方法无法响应,依然会去调用NoTargetActionResponseWithTargetString方法

    无响应事件处理

    上述代码中曾提到在实际开发过程中是可以事先给一个固定的target专门用于在无响应的时候顶上,然后处理这种请求。

    #pragma mark - private methods
    - (void)NoTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams
    {
        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];
    }
    

    注意:代码中Target_NoTargetAction就是用来统一处理无响应时给的固定的target, Action_response就是这个类用来调用的方法。

    @interface Target_NoTargetAction : NSObject
    
    - (void)Action_response:(NSDictionary *)params;
    
    @end
    

    调用代码解析

    - (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];
        NSLog(@"返回值的类型 %s",retType);
    
        if (strcmp(retType, @encode(void)) == 0) {
            // 通过NSMethodSignature对象创建NSInvocation对象,NSMethodSignature为方法签名类
            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);
        }
    
        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 ignored “xxx” 这样的语句来忽略掉相应的警告
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        return [target performSelector:action withObject:params];
    #pragma clang diagnostic pop
    }
    

    清除缓存

    - (void)releaseCachedTargetWithTargetName:(NSString *)targetName
    {
        NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
        [self.cachedTarget removeObjectForKey:targetClassString];
    }
    

    总结

    中间者架构采用中间者统一管理的方式,来控制app的整个生命周期中组件间的调用关系。拆分的组件都会依赖于中间者,但是组间之间就不存在相互依赖的关系了。由于其他组件都会依赖于这个中间者,相互间的通信都会通过中间者统一调度,所以组件间的通信也就更容易管理了。在中间者上也能够轻松添加新的设计模式。中间者架构的易管控带来的架构更稳固,易扩展带来的灵活性,从而使得架构更容易扩展。

    CTMediator源码这里

    相关文章

      网友评论

        本文标题:CTMediator 源码解析

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