QHOServiceManager组件间通信方式

一种组件间通信方式

QHOServiceManager 是我们推出的业务间通信的解决方案,方便客户端不同的业务仓库之间进行交互。解决不同业务库代码需要互相调用,但是不能直接依赖代码的问题。

没有看懂?没关系,简单来说,当你想要用其他业务库的代码逻辑,却不想,或者,不能把其他业务 pod 添加到你的 podspec 时,你可能就是 QHOServiceManager 的潜在用户。

问题的起因和我们的解决思路:

一、问题与现状

现实中,如果有两个组件中的代码需要互相调用,或者复用代码,比如,我们希望在订单详情页收藏结束后,展示收藏清单。

然而订单详情页面和收藏清单页面分散在两个仓库中,此时,为了实现这个需求,我们有哪些选择呢?

1. 直接依赖

这是最简单直接的一种做法,但是有以下缺点:

1. 订单引入了清单的直接或间接依赖,比如清单使用了 Logan埋点 后,订单也不得不引入,并且在自己的工程中做初始化配置

2. 当清单需要使用订单的代码时,会因为循环依赖导致需要另寻他法

3. 造成应用内整体依赖层级过多,如果大家都这么做,那么可能会出现很多业务库之间相互依赖的情况,增加依赖复杂度。

2. 下沉公共代码

相对间接的一种做法,但是有以下缺点:

1. 需要建立一个新的第三方仓库,成本高

2. 新的第三方仓库可能会成为无人管理的状态

3. 复制代码

简单粗暴,但是:

1. 复制代码需要繁琐的改名,容易忘记改名

2. 造成代码成倍膨胀

4. 运行时依赖

也就是通过类似 NSClassFromString 等方式拿到代码逻辑,但是:

1. 代码存在很多硬编码

2. 当 Class 名称修改时,没有有效的机制去校验

总结上述4种方式:

方法 优点 缺点
直接依赖 不需要额外代码改动

调用方引入过多无用依赖

依赖层级加深

下沉公共代码 拆分干净

开发成本高

依赖层级加深,严重时影响可维护性

复制代码 开发成本低 代码量增加,影响包体积
运行依赖 开发成本低 隐形依赖,严重时影响可维护性

解决方案

思路与目标

这一类问题的常规解决方案是遵循依赖倒置的设计原则(DIP),通过面向接口编程的方式来解决。之前说到的四种解决方案,其实3和4都能看到一点面向接口编程的影子。只是他们对应的协议不是成本太高,就是不可靠

构建一个框架,方便业务方通过结构化,低成本的方式实现面向接口编程

模型设计

本质来说,我们的需求是实现两个同级组件之间优雅的互相调用,系统中可以抽象为如下三个角色:服务提供者,服务调用者和框架。按照目标所述,我们对于组件间调用,定义以下两条选型原则:

1. 遵循 DIP 原则,解耦依赖流和控制流

2. 遵照一般的设计原则,将编译时依赖决议推迟到运行时

根据以上设计原则,抽象模型设计如下:

技术选型

在 iOS 上实现面向接口编程的方案有几种思路,如下:

1. 基于协议, 通过运行时获取实例

2. 基于虚基类,通过 category 覆盖实现扩展

3. performSelector

最后我们选择基于协议的方案,这也是在其他语言框架中常见的 ServiceLoader 设计方案

架构设计

通过对模型的细化,框架设计如下:

组件分为服务的提供方和调用方,双方共同依赖公共协议来交互

ServiceRegistry 是一个公共协议池(层),沉淀了公共协议和元信息

ServiceManager 负责在运行时处理绑定协议的声明和实现

为了弥补 ServiceLoader 的缺点,加强开发体验,框架需要大量使用工具辅助生成代码。

工作流举例

服务提供(P)和使用(C)双方定义公共协议(或者单方面定义)

双方同时依赖公共协议

P 实现并注册自己对协议的实现

C 以协议为参数和 Manager 通信,获取服务实例

C 根据协议,调用服务实例

业务举例

在以上方案中,方案四的短板是我们可以想办法弥补的,所以我们可以建立一个协议仓库(METServiceRegistry)

订单和清单共同依赖这个仓库,因此都可以拿到这个协议

在清单仓库中,声明一个 Service 类实现该协议。

然后在订单仓库中,通过向 QHOServiceManager 传递该协议即可拿到 Service 类的示例,从而调用显示清单的代码。

整体组成

提供三个仓库实现方案功能:

METServiceRegistry: 整个 imeituan 使用的协议池,所有的协议都放到这里

QHOServiceManager: 在运行时,各个组件通过 QHOServiceManager 来通信

cocoapods-service-manager: 由于 METServicePool 是一个所有人都要编辑的仓库,通过这个插件来加快一些本来很麻烦的操作

更多业务场景举例

1. 从订单的收藏页面进入业务方的详情页,详情页取消收藏,通知订单页,订单页不需要重新刷新页面即可同步数据

2. 搜索需要在搜索结果中展示酒旅的内容,这部分 UI 以及数据获取在酒旅的仓库中进行开发,搜索通过 Service Manager 在运行时获取已经渲染好的 UI

3. 美食搜索需要和首页搜索共享搜索记录

4. 首页的导航栏的代码在 PFBEntrance 中,内容在 PFB 中,导航栏的颜色需要根据内容变色。

5. UGC 的业务方需要对 UGC 进行自定义配置

猜你喜欢

转载自blog.csdn.net/ivolcano/article/details/89790476