组件化之 Target-Action
iOS 中主要有三种组件化方式,URL Route、Target-Action、Protocol 匹配。这里主要讲第一种:Target-Action。
这类方式的大致思想是利用语言的特性 runtime,根据传入 target 和 action,动态的创建类去调用相应的方法。同时利用分类规范了传入的参数。
优缺点
优点
- 利用 Category 可以明确声明接口,进行编译检查
- 实现方式轻量,避免了注册操作
缺点
- 过度依赖 runtime,无法应用在纯 Swift 项目中
- 无法保证所使用的模块一定存在,target 模块在修改后,使用者只有在运行时才能发现错误
- 使用 runtime 相关的接口调用任意类的任意方法,需要注意别被苹果的审核误伤。参考:Are performSelector and respondsToSelector banned by App Store?
- 在 category 中仍然引入了字符串硬编码,内部使用字典传参,一定程度上也存在和 URL 路由相同的问题
代码示例
1 | // 模块管理者,提供了动态调用 target-action 的基本功能 |
1 | // 在 category 中定义新接口 |
1 | // 模块提供者提供 target-action 的调用方式 |
CTMediator 的实现
CTMediator
本质是利用Objective-C
的语言特性Runtime
,根据 URL 创建相应的类并调用方法,并利用分类对参数进行限制。
分类
1 | - (UIViewController *)CTMediator_viewControllerForDetail |
这里分类起到的作用主要是锦上添花。
- 规避了字符串的硬编码
- 明确了模块的接口
核心方法
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
分类内部主要调用的是performTarget
方法,下面是它的实现: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
40
41
42
43
44
45
46
47
48
49
50
51
52- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
if (targetName == nil || actionName == nil) {
return nil;
}
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 safeFetchCachedTarget: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 safeSetCachedTarget:target key:targetClassString];
}
if ([target respondsToSelector:action]) {
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];
@synchronized (self) {
[self.cachedTarget removeObjectForKey:targetClassString];
}
return nil;
}
}
}
- 首先在
cache
里寻找是否有存在的target
,如果没有则单独创建。 - 根据 target
和
action调用
- (id)safePerformAction:(SEL)action target:(NSObject )target params:(NSDictionary )params`方法
PS:其中这里有个两个机会可以处理无效的调用:1. 创建 Target 失败 2. 创建 Target 后调用 Action 失败
- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
1 | - (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params |