大型APP都是多个团队一起维护的,所以需要组件化合作开发。这样也能减少编译,测试时间。组件化首先需要解决的就是组件间的通信问题。常见的组件间通信有三种方式:
- Target-Action
- URL Scheme
- Protocol Class
Target-Action
Target-Action通信方式是抽离业务逻辑后,通过中间层使用runtime反射进行调用。例如从自己团队维护的列表页,跳转到其他团队维护的详情页。先定义中间层Mediator:
// 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" __kindof 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];