组件化的一些思考
1. 组件化之前
1.1 组件化的目标
- 依赖解耦,模块化服务, 提升代码复用率
- 独立分库,二进制打包,提升编译速度
- 提升大团队、跨团队的开发效率
1.2 如何组件拆分
面临主要问题:
- 主业务库依赖复杂,不易拆分模块。
- 模块之间的依赖关系复杂·,很多模块存在不合理的横向依赖。
- 业务模块的边界是什么,粒度大小。
拆分模块的原则:
- 由下到上,单向依赖:
- 由粗到细:
- 由新到旧 :
- 模块间遵守接口隔离原则:
- 模块内遵守控制反转原则
参考模型:
App壳工程: 负责管理各个业务组件和打包APK,没有具体的业务功能。
业务组件层: 根据不同的业务构成独立的业务组件,其中每个业务组件包含一个Export Module和Implement Module。
功能组件/核心服务层: 对上层提供基础功能服务,如相机服务、IM、播放器、视频等。
组件基础设施: 包括Router提供页面路由服务、Kits、 SDK等。
2 组件管理
2.1. 组件工程标准化
创建统一的一套动态库/静态库工程模板,通过脚本新建静态库/动态库
- 完成统一基本的podfile配置
- 完成统一基本的podspec配置
- 统一xcodeproject的配置统一:Enable bitcode设置为NO,Mach-O type设置为Static Library等
- 目标framework target
运行 sh TBTemplate.sh TBNewFramework 你要生成工程的名称
以MyFramework为例
sh TBTemplate.sh TBNewFramework MyFramework
- 你可以把自己模块依赖的库写在podspec中,编译构建自动同步到在podfile里。
-
将你原来的源码文件拖入到目标framework target里,在这里就是MyFramework这个target,效果如下
image.png
2.2. 组件管理平台化
通过CI平台创建组件模块,完成depoy配置(支持打包源码和静态库), 编译构建、持续集成等。下图是MTL平台
参考下滴滴的组件管理工具:
2.3. 组件开发规范化
命名规范
- Module :包含界面且实现某部分具体业务功能的模块叫 Module。
- Service :只包含具体业务逻辑的模块叫 Service。
- SDK :不包含业务逻辑,提供基础服务的组件叫 SDK。
- Kit :包含多个类似基础服务的组件叫 Kit。
规范头文件的使用: 命名、设置可见性、头文件导入规范
规范podspec的管理 : 检查podspec中的dependency和podfile中引入的依赖是否一致。podfile有依赖变更,必须同步到podspec。
验收:确保framework工程本地编译通过,以及MTL平台打包成功
回归和Review:按照TestCase进行功能回归,打点回归,对模块变更进行review。
2.4. 组件的开发测试
使用脚本或工具将模块的源码映射成pod的开发库, 在主工程进行修改调试。
3 集成壳工程
- 壳工程提供基础的依赖和功能,提供独立的运行能力,
- 管理初始化、启动流程、主页面多tab的UI框架
4 组件通讯
iOS 组件化的三种方案
OC底层知识点之-组件化(下)组件化通信
4.1. URL路由
自己开发Router或开源MGJRouter等:使用url-block方案
约定URI规则:fnpt://module/vc/pushOrPresent/?key=value&key1=value
优点:
- 实现简单,易于理解和使用;
- 方便地统一管理多平台的路由规则
- 易于适配 URL Scheme
缺点:
- 传参方式有限,并且无法利用编译器进行参数类型检查,因此所有的参数都只能从字符串中转换而来
- 只适用于界面模块,不适用于通用模块
- 需要手动维护路由表,容易出现冗余和错误;
- 不能使用 designated initializer 声明必需参数
4.2. Target - Action
iOS组件化第二步:使用中间者模式(CTMediator)
CTMediator 是一个中间人模式(Mediator Pattern)的实现,用于 iOS 组件化开发中的模块间通信方案。
优点:
- 实现方式轻量级, 调用时区分了本地应用调用和远程应用调用。本地应用调用为远程应用调用提供服务。
- 组件仅通过Action暴露可调用接口,模块与模块之间的接口被固化在了Target-Action这一层,避免了实施组件化的改造过程中,对Business的侵入,同时也提高了组件化接口的可维护性。
- 方便传递各种类型的参数。
- 现在也支持swfit了。 CTMediator的Swift应用
如果你的工程是采用CTMediator方案做的组件化,看完本文以后,你就可以做到渐进式地迁移到Swift了。 CTMediator支持所有情况的调用,具体可以看文后总结。你的工程可以让Swift组件和Objective-C组件通过CTMediator混合调用 也就是说:以后再开新的组件,可以直接用Swift来写,旧有代码不会收到任何影响。
缺点:
- 需要在mediator和target中重新添加每一个接口,模块化时代码较为繁琐
- 在category中仍然要引入字符串硬编码,内部使用字典传参,一定程度上也存在和URL路由相同的问题
- 无法保证使用的模块一定存在,target在修改后,使用者只能在运行时才能发现错误
- 创建过多的target类,导致target类泛滥
4.3. Protocol - Class
protocol匹配的实现思路是:
- 将protocol和对应的类进行字典匹配
- 通过用protocol获取class,再动态创建实例 protocol比较典型的三方框架就是阿里的BeeHive。BeeHive借鉴了Spring Service、Apache DSO的架构理念,采用AOP+扩展App生命周期API形式,将业务功能、基础功能模块方式以解决大型应用中的复杂问题,并让模块之间以Service形式调用,将复杂问题切分,以AOP方式模块化服务
优点:
1、利用接口调用,实现了参数传递时的类型安全
2、直接使用模块的protocol接口,无需再重复封装
缺点:
1、用框架来创建所有对象,创建方式不同,即不支持外部传入参数
2、用OC runtime创建对象,不支持swift
3、只做了protocol 和 class 的匹配,不支持更复杂的创建方式 和依赖注入
4、无法保证所使用的protocol 一定存在对应的模块,也无法直接判断某个protocol是否能用于获取模块
URL Router、Target-Action 和依赖注入都是 iOS 组件化的常见方案。
URL Router 是一种通过定义一系列 URL Scheme,将各个组件对应到不同的 URL 上,然后在应用中注册路由表,在接收到特定 URL 时,根据路由表将请求转发到相应的组件中的方案。这种方案实现简单,易于理解和使用,但需要手动维护路由表,存在冗余和错误的风险。
Target-Action 是一种通过在编译期间生成各个组件之间的依赖关系,然后再通过 Runtime 动态加载组件,实现模块之间解耦的方案。这种方案可以保证类型安全,并且可以使用反射机制进行运行时注入,但需要手动处理每个模块之间的依赖关系,对于多层嵌套的依赖关系使用起来比较繁琐。
依赖注入是一种通过协议和遵循者实现依赖注入的方案。将各个组件之间需要使用的接口抽象出来,定义成协议,然后在应用启动时,通过注册表将不同的实例与相应的协议对应起来。这种方案可以解决组件之间依赖关系的问题,但过度依赖 DI 也会增加代码复杂度和维护成本。
从性能,可读性,功能性,代码量,规范约束 几个维度综合考虑我建议
CTMediator > 依赖注入 > URL-Router
当然选择哪种方案取决于具体的项目需求和开发团队的技术栈,需要权衡各种因素,选择最适合自己的方案,以上仅代表个人观点。
小结
模块的拆分需要划定好边界,梳理好依赖,做好模块解耦
组件的制作标准化,规范化,组件的集成平台化。
项目的平台化管理,包括项目的创建、构建,集成、回归、发布、hotfix、配置等等。
参考文章
百度App iOS工程化实践: EasyBox破冰之旅
百度 App 组件化之路
我所理解的组件化之路
Android组件化方案及组件消息总线modular-event实战
火掌柜 iOS 端基于 CocoaPods 的组件二进制化实践