组件间通信

19 9月

大型APP都是多个团队一起维护的,所以需要组件化合作开发。这样也能减少编译,测试时间。组件化首先需要解决的就是组件间的通信问题。常见的组件间通信有三种方式:

  • Target-Action
  • URL Scheme
  • Protocol Class

Target-Action

Target-Action通信方式是抽离业务逻辑后,通过中间层使用runtime反射进行调研。例如从自己团队维护的列表页,跳转到其他团队维护的详情页:

// Mediator.h
+ (__kindof UIViewController *)detailViewControllerWithUrl:(NSString *)detailUrl;

// Mediator.m
+ (__kindof UIViewController *)detailViewControllerWithUrl:(NSString *)detailUrl {
    Class detailCls = NSClassFromString(@"DetailViewController");   // 通过字符串反射出 class
    UIViewController *controller = [[detailCls alloc] performSelector:NSSelectorFromString(@"initWithUrlString:") withObject:detailUrl];    // 执行 target 的 selector
    return controller;
}

可以通过中间层Mediator调用其他团队维护的组件了:

#import "Mediator.h"

UIViewController *vc = [Mediator detailViewControllerWithUrl:item.articleUrl];

Target-Action方式的缺点是:虽然通过了反射进行了最大可能的解耦,但反射还是需要业务组件名作为字符串,所以解耦的不彻底。而且如果Target过多,会导致Mediator代码体积极速膨胀,难以维护。

URL Scheme

URL Scheme的思路是借鉴了APP间的唤起跳转,将它应用到了APP内部的组件中。

将所有业务逻辑都封装在组件内,而不是像Target-Action那样封装在中间层里。在中间层Mediator里仅仅提供注册和调用的方法:

// Mediator.h
typedef void(^MediatorProcessBlock)(NSDictionary *params);
+ (void)registerScheme:(NSString *)scheme processBlock:(MediatorProcessBlock)processBlock;
+ (void)openUrl:(NSString *)url params:(NSDictionary *)params;

// Mediator.m
+ (NSMutableDictionary *)mediatorCache {     // 内部维护一个 scheme 的跳转表
    static NSMutableDictionary *cache;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        cache = @{}.mutableCopy;
    });
    return cache;
}

+ (void)registerScheme:(NSString *)scheme processBlock:(MediatorProcessBlock)processBlock {
    if (scheme && processBlock) {
        [[[self class] mediatorCache] setObject:processBlock forKey:scheme];
    }
}

+ (void)openUrl:(NSString *)url params:(NSDictionary *)params {
    MediatorProcessBlock block = [[[self class] mediatorCache] objectForKey:url];
    if (block) {
        block(params);
    }
}

可以对比Target-Action方式,在中间层Mediator里已经没有业务相关代码了,实现了和业务的彻底解耦,只提供了注册和调用两个方法。

被调用的组件往中间层里注册,调用方用中间层提供的调用方法进行调用。

被调用的组件往中间层Mediator里注册:

#import "Mediator.h"

+ (void)load {
    [Mediator registerScheme:@"detail://" processBlock:^(NSDictionary * _Nonnull params) {  // 自定义 scheme
        NSString *url = (NSString *)[params objectForKey:@"url"];
        UINavigationController *navigationController = (UINavigationController *)[params objectForKey:@"controller"];
        DetailViewController *controller = [[DetailViewController alloc] initWithUrlString:url];
        [navigationController pushViewController:controller animated:YES];
    }];    
}

在调用组件处,使用中间层Mediator的调用方法来调用组件:

#import "Mediator.h"

[Mediator openUrl:@"detail" params:@{@"url":item.articleUrl,@"controller":self.navigationController}];

URL Scheme方式的缺点是:参数通过NSDictionary传递,具体传什么参数调用方是不知道的,需要看被调用方的注册方法时需要哪些参数。

Protocol Class

引入Protocol包装层,包装层里返回Protocol对应的Class。

同样在在中间层Mediator里提供注册和调用的方法:

// Mediator.h
@protocol DetailViewControllerProtocol 
- (__kindof UIViewController *)detailViewControllerWithUrl:(NSString *)detailUrl;
@end

@interface Mediator : NSObject
+ (void)registerProtol:(Protocol *)proto class:(Class)cls;
+ (Class)classForProtocol:(Protocol *)proto;
@end

// Mediator.m
+ (NSMutableDictionary *)mediatorCache {     // 内部维护一个 scheme 的跳转表
    static NSMutableDictionary *cache;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        cache = @{}.mutableCopy;
    });
    return cache;
}

+ (void)registerProtol:(Protocol *)proto class:(Class)cls{
    if (proto && cls) {
        [[[self class] mediatorCache] setObject:cls forKey:NSStringFromProtocol(proto)];
    }
}
+ (Class)classForProtocol:(Protocol *)proto{
    return [[[self class] mediatorCache] objectForKey:NSStringFromProtocol(proto)];
}

被调用的组件往Protocol里注册:

#import "Mediator.h"

+ (void)load {
    [Mediator registerProtol:@protocol(DetailViewControllerProtocol) class:[self class]];
}

在调用组件处,使用中间层Mediator的Protocol的获取class的方法来调用组件:

#import "Mediator.h"

Class cls = [Mediator classForProtocol:@protocol(DetailViewControllerProtocol)];
[self.navigationController pushViewController:[[cls alloc] detailViewControllerWithUrl:item.articleUrl] animated:YES];

发表评论

电子邮件地址不会被公开。 必填项已用*标注