七、组件化架构

组件

组件是软件系统中可独立部署的最小实体,组件化架构就是一套定义组件划分以及组件间相互协作方式的设计规则。

在编译后的二进制文件可进行外部链接之后,就有了组件的概念,它是将软件系统拆分后获得的部件,它比模块的划分要高一个级别,拆分时重视组件的重用性,拆分后强调组件的独立部署能力。

组件可被独立部署的特性,也意味着组件可以被独立开发,在iOS开发中,对于需要多人合作的大型APP,组件可被独立开发的特性能极大的提升整体开发效率,设计良好的组件能被跨APP的重用,这也满足了大型APP团队的实际需求,同时将单工程APP拆分成多个组件,配合组件化架构中组件间的解耦方案,也能最低程度的限制APP中代码的耦合度。

PS:基于动态链接,在组件的概念上进一步演进的插件化技术,在iOS系统中出于对安全性的考虑,一直被苹果限制使用。

组件化

1. 组件构建原则

在iOS APP的组件化架构中,架构都是有模式可循的,如何将APP组件化,即如何对代码进行划分以构建一个组件,才是开发实践中需要反复思考,并真正考验设计能力的问题。组件的划分需要兼顾研发性和复用性,还需要考虑组件间的依赖关系,并且具备时效性,是随项目而不断演化的,组件构建原则(出自《Clean Architecture》)能够指导组件化的决策过程。

组件聚合

  1. 复用/发布等同原则(REP):组件复用的最小粒度应等同于其发布的最小粒度,也就是说,有一个共同的主题或者大方向的类和模块应该放到同一个组件中;
  2. 共同闭包原则(CCP):会同时修改且因为相同目的而修改的类和模块应该放到同一个组件中,不会同时修改且不会因为相同目的而修改的类和模块应该放到不同组件中;
  3. 共同复用原则(CRP):不要强迫一个组件的用户依赖它们不需要的东西,也就是说,不是紧密相连的类和模块不应该放在同一个组件中。

以上原则从不同角度指导了组件聚合,彼此间是相互拉扯的,构建组件时需要根据项目当前的实际情况进行综合考虑,没有最佳,只有更适合。

组件耦合

  • 无依赖环原则(ADP):组件依赖关系图中不应该出现环,依赖环最直接的后果便是会破坏组件的独立性,使组件独立维护、测试和发布都变得非常困难;
  • 稳定依赖原则(SDP):依赖关系必须要指向更稳定的方向,容易变更的组件不应该被太多的其它组件所依赖,被许多组件所赖的组件必须得是稳定的;
  • 稳定抽象原则(SAP):组件的抽象化程度通常和其稳定性保持一致,即抽象化程度越高的组件其稳定性通常也越高,同时还更利于扩展。

在iOS组件化架构的实践中,为组件引入一个中间件的中介者模式,是被广泛使用的架构方案,中间件打破了组件间的依赖关系,同时让组件的依赖都指向了中间件,但组件间实际的依赖逻辑依然存在,减少组件耦合的原则,在组件化设计时依然具有指导意义。

根据无依赖环原则,需要避免组件间直接或间接的循环依赖,打破循环依赖的主要手段包括:将相互依赖的部分放入一个组件中(或者为它们新建一个组件);以及在其中一方的组件中引入抽象,实现依赖反转。结合稳定依赖原则和稳定抽象原则可以知道,一个被多方依赖的组件应该是一个稳定的组件,而通常抽象程度越高的组件就越稳定。

PS:《Clean Architecture》中提出了组件稳定性和抽象程度的量化方法,根据稳定性和抽象程度的指标可以综合分析系统中组件设计与“优秀”设计模式之间的契合度,这种衡量在某些情况中可能具有不错的参考价值。

2. iOS组件化工具

在iOS8之后苹果允许APP有自己的Embedded Frameworks(详见APP的编译过程),所以iOS开发中能将源码打包成静态库或动态库的包管理工具(CocoaPods、Carthage、Swift Package Manager等),都可以作为实现iOS组件化的工具,甚至利用Xcode的Workspace与Projects,或者Project与Targets,也都能实现iOS项目的组件化,这里仅以使用最多的CocoaPods简述创建一个组件的过程。

利用CocoaPods来实现iOS项目的组件化,将拆分后的组件代码做成CocoaPods的私有库,一个组件就是一个私有库,私有库的索引推送在私有源上,在组件使用者的Podfile中指明私有源,再引入私有库,即引入了组件:

  1. 创建一个远程仓库作为私有源,通过pod repo add命令(Useage: pod repo add MYSpecs https://xxxx/myspecs.git)将私有源添加到自己的CocoaPods中;
  2. 通过pod lib create命令(Useage: pod lib create MYComponent)为组件创建CocoaPods库,再为CocoaPods库创建远程仓库,根据组件的实际情况和远程仓库地址配置CocoaPods库的podspec文件(Podspec语法参考(翻译));
  3. 在CocoaPods库中的组件代码编译通过之后,可对库的podspec文件进行本地验证(pod lib lint)和远程验证(pod spec lint),最后将podspec文件推送(pod repo push)到私有源上。验证和推送podspec文件时,需要cd到文件所在的目录,常用的可选参数有:
    • –sources:如果组件库依赖了其他私有库/组件,需要通过此参数指明私有源;
    • –use-libraries:如果组件库依赖了静态的三方库,需要添加此参数,否则会提示找不到这些库;
    • –use-modular-headers:如果组件库的podspec中声明自己以静态库的方式被导入,而组件库依赖的某些三方库没有指明自己的moudle name,需要添加此参数;
    • –skip-import-validation:如果组件库依赖的某些库,缺少对x86芯片指令集的支持(模拟器),需要通过此命令跳过组件库的导入验证;
  4. 在组件使用者的podfile中通过source指明私有源,再通过pod正常引入私有库,即引入了组件;

podfile其实是简写的Ruby代码,在pod install和update时可以设置环境变量,然后在podfile中通过ENV['key']获取,再根据变量做if-else的条件判断,然后执行不同的逻辑,同时podfile中引入一个库时,可以为库指定地址,根据这两点,就可以让一个podfile根据不同参数而去分别引入开发/发布的组件库,一个工程既可作为开发时的壳工程,也可以作为发布时的主工程。

PS:podfile中还可以为库指定本地地址,从而让正在开发中的组件跳过lint和push的步骤,直接集成到整个项目中,进行调试,提高开发效率,podfile语法参考:Podfile语法中文参考文档

架构模式

拆分后的组件可以被独立部署(开发),但组件间可能存在复杂的依赖关系,这些依赖让拆分后项目的架构异常复杂且难以维护,组件化之后的项目需要一套架构方案,来简化组件间的依赖,同时还要有方便的组件间协作方式。iOS组件化架构由蘑菇街团队分享之后而广为流传,在蘑菇街的组件化架构方案中,通过为组件添加中间件以实现的中介者模式,来解决组件间依赖和协作的问题,在后续开发者的实践中,中介者模式也基本被认为是iOS组件化架构的最佳解决方案。

iOS组件化架构中,通常引入中间件来作为组件的中介者,组件间的协作方法都被抽象后由中间件直接响应,组件都只需要单向依赖中间件,中间件内部再将协作方法转发给具体的组件,iOS组件化架构实际上就是对中介者模式的应用。

中间件方案

蘑菇街分享了自己组件化架构的中间件MGJRouter蘑菇街 App 的组件化之路),casatwy指出其有多项不足(没必要的注册、注册后block对内存的占用、以及基于openUrl设计导致传参困难等),并分享了自己的中间件方案CTMediator,后续蘑菇街又说明MGJRouter还存在一套基于protocol-class的补充方案,casatwy继续评判…

在那场论战中蘑菇街方案的问题肯定是客观存在的,但casatwy的部分观点也有待商榷,总之通过那场论战可以知道,iOS组件化架构的中间件并没有一套业内统一认可和使用的方案,只要抓住iOS中基于中间件的组件化架构,就是对中介者模式的应用这一本质,完全可以设计或修改出适合自己项目的中间件。

AWMaster

AWMaster是一套基于protocol来抽象组件间协作方法,通过消息转发的备援接收者来转发协作方法的中间件方案,它有以下特点:

  1. 不将协作方法高度抽象为中间件的一个通用方法,而是仅抽象为协议(但保留了对openUrl远程调用的支持),方便协作方法的维护、调用和传参;
  2. 保留了对协作方法的注册,通过protocol和class进行注册,注册后维护方法和class的映射关系,CTMediator也并非不需要注册,而是将注册隐藏在了强制的命名规范之中(Target_targetName),这种隐晦的方式方便使用,但不利于理解,考虑在后续大版本中再更新为此方式;
  3. 基于消息转发的备援接收者来完成协作方法的转发,方法的调用和转发都更自然,也比CTMediator中完整的消息转发(NSInvocation)性能更优;
  4. 额外提供协作方法的群发功能,满足一个协作方法需要被多个组件响应的特殊需求。

总结

iOS组件化架构的关键就两点:1. 对单工程项目的组件化拆分,以及拆分后利用包管理等工具对组件进行管理;2. 根据中介者模式,为组件引入中间件,解决组件间依赖和协作的问题。熟悉架构中使用的包管理(或其它)工具、理解中介者模式并清楚架构中选用的中间件的工作方式,便能掌握项目中的组件化架构,组件化架构最终需要反复思考的问题依然是组件的设计与演进。

-------------This article is over, thank you for reading -------------