文档更新说明
- 最后更新 2019年07月19日
- 首次更新 2019年07月19日
前言
今天想把业界流行的三种iOS组件化设计方案简述一番, 每一种方案都有其优缺点, 也不是完全绝对的设计方案, 需要根据自身项目实际情况灵活变通. 这三种方案其实网络上也有很多的文章介绍, 本文简述核心思路, 其他枝叶细节可以另行搜索学习.
组件化设计模式主要解决多人协作中出现的功能耦合问题, 简单说就是开发者A的代码和开发者B的代码互相引用, 测试的时候需要双方都准备好代码, 没法将功能作为一个独立的程序编译测试. 解决这一问题的思路就是功能模块化,解偶,使得开发者A和开发者B负责的模块之间互不依赖, 当然解偶的方案就是增加中间件(老套路).
组件化的优势来自中间件, 缺点也来自中间件, 多了一层代码, 维护工作也就多了一层, 所以团队人少的情况下就没必要做这件事了.
准备工作
想象一下我们有这样一个场景: 程序员A负责一个新闻APP的列表页, 由名为ModularizationTableViewListVC的控制器实现; 程序员B负责APP的详情页, 由ModularizationDetailVC控制器实现. 当用户点击新闻列表页的某一行时, 程序会根据所在行的url, 调用ModularizationDetailVC, 展示详情页.
为了让程序员A,B在开发的时候可以分别独立编译测试自己的代码, 我们可以增加一个中间件, 一个叫Mediator的类, 这样A和B就可以事先商量好详情页需要对外提供哪些接口, A如何从Mediator调起详情页等. 而商量出来的解决方案, 最流行的就是本文下面要讲述的三种.
三种常用的组件化设计方案
Targer Acton
Targer Acton, 顾名思义, 就是找到目标对象, 向它发送消息. 先看看代码.
1 2 3 4 5 6 7 8
|
@interface Mediator : NSObject
+ (__kindof UIViewController *)detailViewControllerWithTitle:(NSString *)title; @end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
#import "Mediator.h" #import <objc/message.h>
@implementation Mediator + (__kindof UIViewController *)detailViewControllerWithTitle:(NSString *)title { UIViewController *vc = [NSClassFromString(@"ModularizationDetailVC") alloc]; vc = ((id (*)(id, SEL, id))(void *)objc_msgSend)(vc, sel_registerName("initWithTitle:"), title);
return vc; }
|
开发者A调用开发者B的组件, 可以使用下面代码实现
1 2 3 4 5 6 7 8 9 10 11 12
|
#import "Mediator.h"
@implementation ModularizationTableViewListVC - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { UIViewController *vc = [Mediator detailViewControllerWithTitle:@"组件化设计之一"]; [self.navigationController pushViewController:vc animated:YES]; } @end
|
上面代码都比较简单, 就不多说了. 有一个问题就是Mediator这个类会随着组件越来越多, 里面的方法也越来越多, 可以通过分类解决, 负责开发组件的人, 对应的中间件代码写到Mediator的分类里, 再把头文件放入Mediator.h 即可.
URL Scheme
URL Scheme, 看起来和系统的URL Scheme有点像, 其实本质是一样的.把APP看成整个手机, APP的各个组件看成手机里头的各个APP, 不同的组件互相调用可以看成不同APP互相跳转.
言归正传, URL Scheme组件化设计, 简单说就是各个组件事先向Mediator注册自己的scheme和启动组件的block(MediatorProcessBlock), 这样开发者A就可以通过Mediator, 传入约定好的scheme名字和相关参数, 调用开发者B的组件了. 下面看看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
@interface Mediator : NSObject
+ (void)registerScheme:(NSString *)scheme processBlock:(MediatorProcessBlock)block;
+ (void)openURL:(NSString *)url params:(NSDictionary *)dic; @end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| typedef void(^MediatorProcessBlock)(NSDictionary *);
#import "Mediator.h" #import <objc/message.h>
@implementation Mediator
+ (NSMutableDictionary *)schemeCache { static NSMutableDictionary *schemeCache; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ schemeCache = @{}.mutableCopy; }); return schemeCache; }
+ (void)registerScheme:(NSString *)scheme processBlock:(MediatorProcessBlock)block { if (scheme && block) { [[Mediator schemeCache] setObject:block forKey:scheme]; } }
+ (void)openURL:(NSString *)url params:(NSDictionary *)dic { NSLog(@"reveive url=%@", url); NSString *scheme = [url componentsSeparatedByString:@"://"][0]; MediatorProcessBlock block = [[Mediator schemeCache] objectForKey:scheme]; if (block) { block(dic); } }
|
开发者B需要为自己的组件注册scheme, 代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #import "Mediator.h"
@implementation ModularizationDetailVC
+ (void)load { [Mediator registerScheme:@"detailvc" processBlock:^(NSDictionary *params) { UINavigationController *nav = params[@"nav"]; NSString *title = params[@"title"]; assert(title != nil && nav != nil); ModularizationDetailVC *detailVC = [[ModularizationDetailVC alloc] initWithTitle:title]; [nav pushViewController:detailVC animated:YES]; }]; } @end
|
开发者A调用开发者B的组件, 可以使用下面代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
#import "Mediator.h"
@implementation ModularizationTableViewListVC - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [Mediator openURL:@"detailvc://hello?a=1&b=2" params: @{ @"nav": self.navigationController, @"title": @"组件化设计之一", @"t": [[NSTestOBJ alloc] init] }]; } @end
|
Protocol Class
Protocol Class的方案, 倒是和第一种Targer Action有点像, 只不过具体功能的声明并不是写成Mediator类的方法, 而是写到一个协议里, 开发者A只需要从Mediator获取到实现了某个Protocol的类, 执行其中的方法即可. 具体看看下面代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
@protocol ModularizationDetailVCProtocol <NSObject>
+ (__kindof UIViewController *) detailVCWith:(NSString *)title;
@end
@interface Mediator : NSObject
+ (void)registerProtocol:(Protocol *)protol class:(Class)cls; + (Class)classForProtocol:(Protocol *)protol;
@end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #import "Mediator.h" #import <objc/message.h>
@implementation Mediator + (NSMutableDictionary *)protocolCache { static NSMutableDictionary *protocolCache; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ protocolCache = @{}.mutableCopy; }); return protocolCache; }
+ (void)registerProtocol:(Protocol *)protol class:(Class)cls { if (protol && cls) { [[Mediator protocolCache] setObject:cls forKey:NSStringFromProtocol(protol)]; } }
+ (Class)classForProtocol:(Protocol *)protol { return [[Mediator protocolCache] objectForKey:NSStringFromProtocol(protol)]; }
|
开发者B需要为自己的组件注册Protocol和Class的对应关系, 代码实现如下:
1 2 3 4 5 6 7 8 9 10 11
|
#import "Mediator.h"
@implementation ModularizationDetailVC
+ (void)load { [Mediator registerProtocol:@protocol(ModularizationDetailVCProtocol) class:self.class]; } @end
|
开发者A调用开发者B的组件, 可以使用下面代码实现
1 2 3 4 5 6 7 8 9 10 11 12
|
#import "Mediator.h"
@implementation ModularizationTableViewListVC - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { Class cls = [Mediator classForProtocol:@protocol(ModularizationDetailVCProtocol)]; [self.navigationController pushViewController: [cls detailVCWith:@"组件化设计之三"] animated:YES]; } @end
|
总结
三种组件化方案, 各有优缺点, 其中URL Scheme灵活性最大, 本身支持改造成根据其他APP跳入时传入的URL, 打开指定界面. 而另外两种其实原理都是一样的, 就是风格不同.