iOS 组件化通信

2019-11-10  本文已影响0人  yitez

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

举个栗子: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、需要额外维护中间类拓展,只有调用方需要引用中间类。

上一篇下一篇

猜你喜欢

热点阅读