一. 总述
这个方案的最终目的很明确, 就是要抽象出一个中间层来对纷乱的引用关系进行统一的跳转. 模块只和中间层耦合, 模块间解耦; 中间层使用 runtime 的形式调用模块的业务组件, 不依赖具体的模块代码.
二. 业务场景
脱离业务需求的设计都是空中楼阁, 下面将结合具体的业务场景进行方案分析和设计.
下面是大多数 app 都会遇到的业务场景:
启动开屏广告是个运营位置, 需要任意跳转
轮播图是个运营位置, 需要任意跳转
webview 中通过 bridge, 也存在任意跳转原生的可能性
模块间各种 push
简单粗暴的方法, 就是 switch 判断类型, 引入各种头文件, 直接跳转. 随着业务膨胀, 以下弊端会越来越明显:
没有统一的跳转管理, 每块业务都维护了自身的一套逻辑, 使得跳转逻辑四处散落;
如果要新增一种跳转类型, 如 1 所述, 必须要找到四处的跳转逻辑, 把可能的地方都加一遍, 维护起来非常费劲;
模块间文件直接引用, 耦合严重. 如果修改删除了某个模块间的某个文件, 会直接影响到所有引用了他的文件; 理想期望是独立模块可以编译运行, 每个模块可随意插拔;
模块间的耦合导致模块无法复用; 理想期望是一个模块复制到新项目时, 可以直接编译运行;
三. 梳理分析
通过上述业务场景, 最直观的痛点的跳转管理问题, 但本质上都是耦合的问题, 是文件间的引用耦合. 要调用一个类的方法, import 相关的头文件, 再根据头文件暴露的 api 进行调用, 这再正常不过了. 当业务膨胀后, 模块内可以直接耦合引用, 但模块间就必须想办法解耦. 目前业内有两种主流方案:
以 JLRoutes 为代表的 URLRoute 方案: 以 URL 为 key, 以待执行的 block 为 value, 保存在一个全局 map 中, 在内存中常驻;
Mediator 中间人方案: 把所有的调用都集合在一起, 使用一个中间人管理. 所有调用方都通过中间人调取另外一个模块;
这两种方案单独来说都有各自的优劣势:
URLRoute 方案借鉴了 web 的思路, 非常契合远程调用这种场景. 但是在开发时本地原生调用时, 使用者会感到变扭. 我们更习惯调方法, 给方法赋值. 在使用 URLRoute 时, 我们还需要多一步转换逻辑, 将方法调用转换成 URL 形式. 这既不方便, 同样存在漏洞. 因为 URL 中只能携带常规数据, 无法把类似 UIImage 等格式的数据封装在 URL 中.
Mediator 方案相比起来对原生调用就非常友好, 但当遇见 app://user/:userName 这样的跳转需求时, 就显得不那么契合. 详情的分析可以参考 https://casatwy.com/iOS-Modulization.html.
四. 结合 URLRoute 和 Mediator 的跳转方案
分析到这里, 其实最后的方案已经呼之欲出了. 我们即想要 URLRoute 这种完美契合 schema 模型的远程调用体验, 也想要 Mediator 本地方法调用的体验.
方案如图所示, 具体 demo 在 https://github.com/xuzhenhao/ZHMediator. 下面将结合组件化的思路一起阐述整个方案如何落地.
模块化拆分.
首先要进行模块的划分, 只有模块间的调用才会考虑这种方式, 模块内的调用是允许相互间耦合的, 因此合理的模块划分就很重要.
暴露 Target 对象. Target 对象暴露整个模块对外提供的所有服务, 此外, 因为 Mediator 和 Target 是通过 Runtime 交互的, Target 暴露的方法中接收的参数是一个字典, 但在方法实现中负责将传过来的字典还原成各个参数, 并调用该模块具体的类和方法.
编写模块的 Mediator 分类. 如上所述, 受限于 runtime 只能以字典形式传一系列参数, Mediator 分类的职责就在于对外提供参数友好型的一系列方法, 但在方法实现中包装成字典形式. 这里涉及到 key 的定义必须和 Target 中还原时的 key 定义一致, 因此划分给相同的开发维护.
远程调用
配置 URL 解析规则. 类似 app://user/:userId/detail 这样的 URL, 需要通过解析规则, 解析出 TargetName,ActionName,Params, 交付给 Mediator 进行处理. 目前只是简单基于 JLRoutes 的二次封装, 当然这里可以根据需求, 后续也能方便替换. 这部分配置建议统一放在 AppDelegete+Routes 的分类中统一配置.
至此, 核心部分就介绍完毕了. 完整的 demo 可以在 https://github.com/xuzhenhao/ZHMediator 获得.
来源: https://juejin.im/post/5b2b0d98e51d4557aa5429e9