日常开发中,你可能会遇到这样的问题,各个模块相互引用,相互依赖,直观表现就是引入了很多头文件,很混乱,比如登录
、收藏
、消息
模块间的相互调用,这样的代码显然不能符合 低耦合、高内聚、职责单一逻辑清晰
的代码设计原则。
为此,我们可能需要使用一个 调度中心 去管理这些模块,有了这个调度中心,每个模块就不需要依赖其它模块,不用导入其它模块的头文件了,只需要调度中心关心每个模块的调度,其它模块只需要关心怎么调用和调用后反馈的结果,这个调度中心就是 路由(Router), 这种设计模式也被称为 中介者模式。
下面我将从以下几个方面来介绍一下我学习到的关于路由的知识:
- 路由是什么
- 路由和组件化的关系
- 业界常见的三种解决方案
- 总结
路由是什么
路由简单来说就是一个中转站,输入一条数据,然后按照内部的一定规则,处理这条数据,最后输出特定格式的新数据。
1.网络层
从网络的角度来说,路由就是指路由器从一个接口收到数据包,根据数据路由包的目的地址进行定向并转发到另一个接口的过程。
2.服务器端
从服务器角度来说,当接收到客户端发来的 HTTP
请求时,会根据请求的URL
,来找到相应的映射函数,然后执行该函数,并将函数的返回值发送给客户端。 在WEB
开发中,路由就是URL
到函数
的映射,在WEB
开发中,Route
是指根据URL
找到处理这个URL
的类
和函数
,Router
可以理解为一个容器,或者说是一种机制,它管理了一组Route
3.移动端
从移动端的角度来说,就是把URL
映射到相应的类
上,iOS中的路由不是仅仅做页面跳转的,只是用的最多的地方,主要是因为可以解耦页面跳转的逻辑,它可以中转的不仅仅是 Controller
还可以是其它对象,比如 UIImage
...
路由和组件化的关系
路由是一个很好的解耦的方案,它可以为各个组件之间调用提供遍历,没有路由,组件化也是可以用的。
业界常见的三种解决方案
1.Url-scheme注册(MGJRouter
)
iOS
系统中默认是支持 url scheme
方式的,例如可以在浏览器中输入: weixin://
就可以打开微信应用。自然在APP
内部也可以通过这种方法来实现组件之间的路由设计。
这种方式实现的原理是在APP
启动的时候,或者是向以下实例中的每个模块自己的load
方法里面注册自己的断链(url
),以及对外提供服务(Block
),通过url-scheme
标记好,然后维护在url-router
里面。 url-router
中保存了各个组件对应的url-scheme
,只要其它组件调用了 open url
的方法,url-router
就会去根据url
去查找对应的服务并执行,详见demo。
1.1 URL的命名规范
遵循网上通用的 URI
(web service
模式的资源通用表示方式) 的格式,例如 appscheme://path
:ctd://home/scan
1.2 常见案例
1.2.1 JLRoutes git 上 star 最多的一个开源库
本质可以理解为保存一个全局的map
,key
是url
,value
是对应存放的block数组
,url
和block
都会常驻在内存中,当打开一个url
时,GLRoutes
就可以遍历这个全局的map
,通过url
来执行对应的block
。
1.2.2 routable-ios
1.2.3 HHRouter
1.2.4 MGJRouter
蘑菇街
的技术团队开源的一个router
,特点是使用简单方便,JLRoutes
的问题主要在于查找url
的实现不够高效,通过遍历而不是匹配。还有就是功能偏多。HHRouter
的url
查找是基于匹配,所以会更高效,MGJRouter
也是采用的这种方法,但它和viewcontroller
绑定地过于紧密,一定程度上降低了灵活性。于是就有了 MGJrouter
, 从数据结构上看它和 HHRouter
是一样的。
蘑菇街方案不好的地方:
URL注册
对于实施组件化是完全没有必要的,拓展性和可维护性都降低;- 基于
Open-url
的方案的话,有一个致命缺陷:非常规对象无法参与本地组件间调度;但是可以通过传递parms
来解决,但是这个区分了远程调用和本地调用的接口;- 模块内部是否仍然需要使用
URL
去完成调度?是没有必要的,为啥要复杂化?- 当组件多起来的时候,需要提供一个关乎
URL
和服务的对应表,并且需要开发人员对这样一个表进行维护;- 这种方式需要在APP启动时,每个组件需要到路由管理中心注册自己的
URL
及服务,因此 内存中需要保存这样一份表,当组件多起来以后就会出现一些内存的问题- 混淆了本地调用和远程调用,它们的处理逻辑是不同的,正确的做法应该是把远程调用通过一个中间层转化成本地调用,如果把两者混为一谈,后期可能会出现无法区分业务的情况。比如对于组件无法响应的问题,远程调用可能直接显示一个
404页面
,但是本地调用可能需要做其它处理。如果不加以区分,那么就无法完成这种业务要求。 远程调用只能传递被序列化JSON
的数据,像UIImage
这样非常规的对象是不行的,所以如果组件接口要考虑远程调用,这里的参数与就不能是这类非常规对象
2.利用Runtime实现的target-action方式(CTMediator
)- 推荐
相较于url-scheme
的方式进行组件间的路由,runtime
的方式借助了OC运行时
的特征,实现了组件间服务的自动发现,无需注册即可实现组件间的调用,因此,不管从维护性、可读性、扩展性来说,都是一个比较完美的方案, 详见demo。
原理:
传统的
中介者模式
,这个中间件Mediator
会依赖其它组件,其它组件也会依赖mediator
, 但是能不能让mediator
不在依赖组件
,各个组件之间不再依赖,组件间调用只依赖中间者mediator
?
casa 大神是这样优化的:
利用target-action
的方式,创建一个taget
的类,里面定义了一些action
方法,这些方法的结果是返回一个controller
或其它object
,再给中间件CTMedator
添加一个分类方法,定义组件外部可调用的方法接口,内部实现方法perform:target:action
的方法,主要通过runtime
中的NSClassFromString
获取target
类和NSSelectorFromString
获取方法名,这样就可以执行先去创建的target
类中的方法得到返回值,再通过分类中的方法传值出去,完美解决~
3.protcol-class注册
通过协议
和类
绑定,核心思想和代理传值
是一样的,遵循协议,实现协议中的方法,详见demo。
主要思路
1、创建一个头文件 CommonProtocol.h
,里面存放各个模块提供的协议。在各个模块依赖这个头文件,实现协议的方法。
2、创建一个中间类 ProtocolMediator
, 提供模块的注册和获取模块的功能(其实就是将类和协议名进行绑定,放在一个字典里,key
是协议名字符串,value
是类)。
3、在各个模块中实现协议
,核心代码如下:
Class cls = [[ProtocolMediator sharedInstance] classForProtocol:@protocol(B_VC_Protocol)];
UIViewController<B_VC_Protocol> *B_VC = [[cls alloc] init];
[B_VC action_B:@"param1" para2:222 para3:333 para4:444];
[self presentViewController:B_VC animated:YES completion:nil];
参考文章
总结
通过这次对路由
方案的研究,认识到了自己对系统架构方面的认识还是太少,以前并没有认真考虑怎么去设计一个好代码,我们需要的事写一些优质代码,牢记 低耦合、高内聚、职责单一逻辑清晰
,不能甘心做码农,只知道堆代码!!!
本文Demo地址:点我 CXRouterDemo
网友评论