文章目录
  1. 1. 文档更新说明
  2. 2. 前言
  3. 3. 准备工作
  4. 4. 三种常用的组件化设计方案
    1. 4.1. Targer Acton
    2. 4.2. URL Scheme
    3. 4.3. Protocol Class
  5. 5. 总结

文档更新说明

  • 最后更新 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
// Mediator.h
@interface Mediator : NSObject
// 组件化之一: Target Action
// 这里主要注意的是, 每次有一个新功能, 就在像这样添加一个新方法~ 当然实际产品的组件化细粒度应该没有这么高, 不至于一个控制器就解偶一次.
// 另外也可以将组件的代码单独写到一个Mediator的分类里单独维护, 再把头文件 xxx+Mediator.h 引入当前文件即可
+ (__kindof UIViewController *)detailViewControllerWithTitle:(NSString *)title;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Mediator.m
#import "Mediator.h"
#import <objc/message.h>
@implementation Mediator
+ (__kindof UIViewController *)detailViewControllerWithTitle:(NSString *)title {
// 根据类名字符获取类对象, 申请内存空间, 再使用runtime机制发送消息初始化
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
// ModularizationTableViewListVC.m
// 列表控制器只需要引入中间件即可, 不需要关心具体要加载的是哪一个详情界面
#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
// Mediator.h
@interface Mediator : NSObject
// 组件化之二: URL Scheme
/**
传入组件的标识
@param scheme 标识
@param block 组件自身的初始代码
*/
+ (void)registerScheme:(NSString *)scheme processBlock:(MediatorProcessBlock)block;
/**
根据url打开对应的组件, 并根据传入的参数执行MediatorProcessBlock
@param url 组件的url, 可携带url参数
@param dic 传入MediatorProcessBlock的参数
*/
+ (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
// Mediator.h
typedef void(^MediatorProcessBlock)(NSDictionary *);
// Mediator.m
#import "Mediator.h"
#import <objc/message.h>
@implementation Mediator
/**
存放MediatorProcessBlock
@return cache单例
*/
+ (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);
// 从url中获取scheme, 找到对应的block, 传入参数执行; 至于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
//ModularizationDetailVC.h
#import "Mediator.h"
@implementation ModularizationDetailVC
// 每一个组件都需要引入中间件, 注册自己的使用逻辑
+ (void)load {
// 下面定义的block没有引用任何外部变量, 为NSGlobalBlock类型, 内部所有变量都为临时变量, 用完即毁无需担心~
[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
// ModularizationTableViewListVC.m
// 列表控制器只需要引入中间件即可, 不需要关心具体要加载的是哪一个详情界面
#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
// Mediator.h
// 开发ModularizationDetailVC组件的人, 需要额外定义暴露给调用方的协议, 同时实现协议内容.
// 实际开发可以直接把代码写进独立的头文件, 这样每个队员都自己维护自己的头文件, 头文件引入公共的Mediator.h即可
@protocol ModularizationDetailVCProtocol <NSObject>
+ (__kindof UIViewController *) detailVCWith:(NSString *)title;
// 还可以声明其他参数
// 比如回调的block, 这样组件功能完成以后, 退出前执行该block可以把某些数据带给调用方
//
@end
@interface Mediator : NSObject
// 组件化之三: Protocol-Class 模式
/**
注册protocol和class的关系, 这样用户就可以通过protocol获取真实的class, 进行相关操作
@param protol 组件的类实现了该协议
@param cls 组件的类, 提供组件的功能
*/
+ (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
// Mediator.m
#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
//ModularizationDetailVC.h
#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
// ModularizationTableViewListVC.m
// 列表控制器只需要引入中间件即可, 不需要关心具体要加载的是哪一个详情界面
#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, 打开指定界面. 而另外两种其实原理都是一样的, 就是风格不同.

文章目录
  1. 1. 文档更新说明
  2. 2. 前言
  3. 3. 准备工作
  4. 4. 三种常用的组件化设计方案
    1. 4.1. Targer Acton
    2. 4.2. URL Scheme
    3. 4.3. Protocol Class
  5. 5. 总结