随着工龄的增长,越发感叹架构的重要性。当你负责的业务会有很多人使用到,或者说会有很多组内成员在你的代码里添加他们的业务逻辑时,这时考察的就不再是单一的能不能实现某个功能,而是你实现这个功能的方式优不优雅,能不能让大家快速接入。如果写出一个只能自己看得懂、别人不敢触碰的代码,就表明确实有很大的进步空间。

直观但难以维护的跳转方案

比如个人profile流 -> 评论 -> 个人profile页 -> 个人profile流 这种逻辑,会造成了互相依赖并且高度耦合,在最初第一版代码下如果改动到了一个ViewController的init初始化方法,则需要在代码里改动N处,给人一种牵一发而动全身的感觉,这样的代码耦合且高度冗余,且应用程序应由业务逻辑而不是视图驱动,急需优化。

1
2
3
4
5
6
7
8
9
10
- (void)gotoDetail {
DetailViewController *detailVC = [[DetailViewController alloc] initWithParam:self.param];
[self.navigationController pushViewController:detailVC animated:YES];
}

- (void)gotoProfile {
ProfileViewController *profileVC = [[ProfileViewController alloc] init];
[self.navigationController pushViewController: profileVC animated:YES];
}

使用Router进行解耦

鲁迅曾说过:”计算机科学中的每个问题都可以用一间接层解决”,项目开始通过router中间件的方案对架构进行精简。在 router 里面定义好每次跳转的方法,然后再需要用的界面调用 router 函数,传入对应的参数,跳转时采用push or present的操作则交给调用方进行选择。比如这样:

1
2
3
4
5
6
7
8
9
10
11
12

+ (UIViewController *)getDetailWithParam:(NSString *)param {
DetailViewController *detailVC = [[DetailViewController alloc] initWithParam:self.param];
return detailVC;
}

+ (UIVIewController *)getProfile {
ProfileViewController *profileVC = [[ProfileViewController alloc] init];
return profileVC;
}


真正的组件化

routerHelper这个类的出现,解决了针对类级别的去耦合,一个类不会在同一个地方多处创建,但尚未处理好解依赖的逻辑,routerHelper和其各个子VC还是互相依赖的关系。

但这样写还是有一个小问题,每个 vc 都会依赖 Router,而 Router 里面会依赖所有的 VC,代码去冗余的目标是实现了,但是耦合的问题还是存在(当然相比之前的方案耦合程度降低了很多)。那么如何打破这一层循环引用呢,可以使用黑魔法:Runtime

1
2
3
4
5
6
7
8
9
10
11

- (UIViewController *)getViewController:(NSString *)stringVCName {
Class class = NSClassFromString(stringVCName);
UIViewController *controller = [[class alloc]init];
if (controller == nil) {
NSLog("未找到此类:%@",stringVCName);
controller = [[RouterError sharedInstance] getErrorController];
}
return controller;
}

这样 Router 里面不需要 import 任何 VC 了,代码也就数十行而已,看起来非常的简便。而且做了异常处理,如果找不到此类,会返回预先设置的错误界面。

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

- (UIViewController *)getViewController:(NSString *)stringVCName {
Class class = NSClassFromString(stringVCName);
UIViewController *controller = [[class alloc]init];
return controller;
}

- (UIViewController *)getViewController:(NSString *)stringVCName witParam:(NSDictionary *)paramdic {
UIViewController *controller = [self getViewController:stringVCName];
if (controller != nil) {
controller = [self controller:controller withParam:paramdict andVCname:stringVCName];
} else {
NSLog(@"未找到此类:%@",stringVCName);
// EXCEPTION Push a Normal Error VC
controller = [[RouterError sharedInstance] getErrorController];
}
return controller;
}

/**
此方法用来初始化参数(控制器初始化方法默认为 initViewControllerParam。初始化方法可以自定义,前提是 VC 必须实现它。要想灵活一点,也可以添加一个参数 actionName,当做参数传入。不过这样你就需要修改此方法了)。
@param controller 获取到的实例 VC
@param paramdic 实例化参数
@param vcName 控制器名字
@return 初始化之后的VC
*/
- (UIViewController *)controller:(UIViewController *)controller withParam:(NSDictionary *)paradic andVCname:(NSString *)vcName {
SEL selector = NSSelectorFromString(@"initViewControllerParam:");
if(![controller respondsToSelector:selector]) { // 如果没定义初始化参数方法,直接返回,没必要在往下做设置参数的方法
NSLog(@"目标类:%@ 未定义:%@方法",controller,@"initViewControllerParam:");
return controller;
}
// 在初始化参数里面添加 key 信息,方便控制器中检验路由信息
if (paradic == nil) {
paramdic = [[NSMutableDictionary alloc] init];
[paradic setValue:vcName forKey:@"URLKEY"];
SuppressPerformSlelctorLeakWarning([controller performSelector:selector withObject:paramdic]);
} else {
[paramdic setValue:vcName forKey:@"URLKEY"];
}
SuppressPerformSelecorLeakWarning([controller performSelector:selector withObject:paramdic]);
return controller;
}

我们默认在业务控制器里面有个 initViewControllerParam 方法,然后再 router 里面可以用 respondsToSelector 手动触发这个方法,传入参数 paramdic。当然如果你想要更加灵活一点,那就将 initViewControllerParam 初始化方法当做一个 actionName 参数传到 router 里面。类似于这样:  

1
2
3
4
5
6

- (UIViewController *)controller:(UIViewController *)controller withParam:(NSDictionary *)paramdic andVCName:(NSString *)vcName actionName:(NSString *)actionName {
SEL selector = NSSelectorFromString(actionName);
... 后面就是一样的代码了
}

总结思考

iOS的动态化可以在运行时决定具体初始化哪个类,调用哪个方法。正是因为这种特性,可以处理业务膨胀场景下的VC解耦的问题(当然平时业务中若遇到机械性的工作,也可以考虑一下是否可以使用runtime进行流程优化)。

使用组件化可以实现什么样的好处呢?

  • 在VC跳转的数据上报可以收敛
  • ViewController的初始化接口可以统一
  • 不用每次新增一个类都要在router里写一份
  • 避免了ViewController和中间件的双向依赖,彻底实现模块化
  • 目前routerHelper有700行,在未来不断新增ViewController的基础上会越来越膨胀,且还需要做好旧ViewController剔除维护的工作

参考资料: