随着工龄的增长,越发感叹架构的重要性。当你负责的业务会有很多人使用到,或者说会有很多组内成员在你的代码里添加他们的业务逻辑时,这时考察的就不再是单一的能不能实现某个功能,而是你实现这个功能的方式优不优雅,能不能让大家快速接入。如果写出一个只能自己看得懂、别人不敢触碰的代码,就表明确实有很大的进步空间。
直观但难以维护的跳转方案
比如个人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剔除维护的工作
参考资料: