美文网首页
iOS 组件化通信

iOS 组件化通信

作者: yitez | 来源:发表于2019-11-10 19:25 被阅读0次

    最近新接触的项目开发方式使用的组件化开发的,以前从来没有接触过,也去网上找了些资料进行同步。
    组件化的每个模块都应该是要相互独立的,且能够独立运行,所以最多可以引入通用工具类,但不应该引入其他同级业务模块的文件。

    举个栗子:Home模块想要通过User模块中getUserInfo方法获取用户信息,普通的项目我们可以直接进行import "User.h",但是组件化的项目该怎么做才合适?

    目前看到网络上的方式主要有几种,三种方法的核心都是由中间人进行管理和消息传递:

    1、URL-Block的方式

    这种方式的核心是预先将url对应的方法先注册给中间人,然后调用方调用的时候,中间人就会去哈希表或者数组里找到对应的方法进行调用。
    以蘑菇街开发的MGJRouter为例,

      [MGJRouter registerURLPattern:@"mgj://category/travel" toHandler:^(NSDictionary *routerParameters) {
            [self appendLog:@"匹配到了 url,以下是相关信息"];
            [self appendLog:[NSString stringWithFormat:@"routerParameters:%@", routerParameters]];
        }];
        [MGJRouter openURL:@"mgj://category/travel" withUserInfo:@{@"user_id": @1900} completion:nil];
    

    整个哈希表的结构是类似树形的结构:

    (lldb) po self.routes
    {
        mgj =     {
            "_" = "<__NSMallocBlock__: 0x610000050680>";
            category =         {
                travel =             {
                    "_" = "<__NSMallocBlock__: 0x6180000554e0>";
                };
            };
            detail =         {
                "_" = "<__NSGlobalBlock__: 0x10f9bb448>";
            };
            search =         {
                ":keyword" =             {
                    "_" = "<__NSGlobalBlock__: 0x10f9bb4c8>";
                };
                ":query" =             {
                    "_" = "<__NSMallocBlock__: 0x610000051880>";
                };
            };
            "~" =         {
                "_" = "<__NSMallocBlock__: 0x61000004cfc0>";
            };
        };
    }
    

    在调用registerURLPattern方法的时候,会将URL整理,和Block一起保存进哈希表,关键字 “_”作为存放Block的key,通过key-value的方式找到对应的block进行调用。
    这种方式就是URL与Block之间的匹配,调用者和中间人连方法的持有者是谁都不需要知道。

    总结:
    1、被调用方需要提前register相应的block才能调用,而且被注册的block似乎并没有很好的释放时机?内存方面是否存在问题。
    2、调用方和被调用方都需要依赖中间件。
    3、单纯的URL拼接的方式并没有办法满足复杂的参数传递,所以才会衍生多种调用的方法。

    2、Protocol-Class的方式

    这种方案类似于第一种方案,它是保存和Protocol和Class的映射关系,每次调用方通过中间人拿到注册时候的Class,直接通过对象进行方法调用;

    “大概是这样?”
      [Router registerClass:[SomeClass Class] forProtocol:aProtocol];
      id<aProtocol> 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、需要额外维护中间类拓展,只有调用方需要引用中间类。
    
    

    相关文章

      网友评论

          本文标题:iOS 组件化通信

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