iOS学习之入门组件化
写在前面
组件化是近年来比较火的一个概念,现在很多公司的 app 包含的内容和模块越来越多,代码的维护和迭代也会产生很大的困难,每个模块各司其职,并且可以做到很好的复用。当我们的工程越来越大,而我们想要测试某一个部分的功能时,就会产生很多不必要的额外工作,这时,我们想到了将整个 app 拆分成很多组件,每个组件可以单独编译运行进行测试,并且当我们参与项目的人员越来越多时,代码量越来越大时,单工程代码更加难以维护于是,也就有了组件化的概念,实际上组件化也就是模块化一种的表现方式,要理解组件化的思想其实并不困难。现在我就介绍一下我最近学习组件化时拜读的文章以及一些自己的想法。
了解
首先需要理解模块化与解耦的概念,推荐这篇文章:模块化与解耦 这篇文章的作者就以下几个问题探讨了一下模块化:
为什么需要模块化?
模块化的设计原则
模块化的优缺点
解耦的概念和原则
模块之间解耦的方式
在这篇文章中我很容易的理解了模块化的基本概念,为什么我们需要模块化,以及如何设计和实现模块化。
bang 神的分析 iOS组件化方案探索 ,这篇文章浅显易懂的向我们讲述了当前几个组件化方案的实行原理,也通俗易懂的对他们的不同之处和优缺点进行了比较说明,让我这种小白也能够豁然开朗。
浅析iOS组件化设计 作者由为什么要组件化?如何组件化?和组件化有什么优缺点?三个方面来阐述了自己对于组件化的观点。
相信看完这三篇文章的你已经对组件化有了一个基本的概念,接着我们可以看一看 bang 神博客中指的这三种组件化方案的原版究竟是怎样的
入门
方案1:
先来看一看蘑菇街的组件化实现方案:蘑菇街的组件化之路 就是在这篇文章中,limboy 提出了使用 URLRouter 的方式来进行组件化,实现组间通信。
这种方法的优点就是做法十分简便,我们甚至在不需要组件化的场景也可以随意实现两个界面之间的跳转,并且逻辑也十分容易理解。
我们只需要一个中间件 Mediator 类来提供注册 URL 和 openURL 两种接口即可。组件 A 持有 Mediator 类,他需要将自己注册到注册表中。而当组件 B 想调用组件 A 时,只需要调用 openURL 方法将组件 A 当时注册的 url 传递过去即可。
缺点也非常明显:URL之间的跳转本来应该是应用之间进行的调用,而我们在这里却让他服务于应用内之间的调用,造成了让远程应用之间的跳转服务于本地应用,而远程调用无法传递特殊的非常规对象,并且我们有时候在找不到页面时,针对远程跳转和应用内跳转需要作出不同的响应,这时URL调用就会出现问题。
并且我们需要在内存中维护一个 register 表,当我们不需要表中的某个元素时,忘记删除可能会带来一些比较严重的后果。
这种方式的硬编码也十分明显
方案2:
而随后 casatwy 大神便写了一篇文章来阐述自己所理解的组件化应有的样子,反驳了蘑菇街中的使用 URLRouter 进行处理的做法,并且解释了自己这样做的原因:iOS应用架构谈 组件化方案
casatwy 的这种方案逻辑上是很容易理解的,即我们有一个 Mediator 类,这个类作为中间件来帮助组件实现他们之间的调用,使得他们不必直接互相引用对方。每一个组件只要持有一个中间件,那么就可以联系任意它想要调用的组件。类似于这样
而这样产生的问题也十分明显,即 Mediator 也持有每个组件,如果组件一旦多了起来,Mediator 将会臃肿不堪。为了解决这个问题,他选择了利用 OC 语言的特性 runtime 来实现,具体的做法就是依赖反射,使得 Mediator 类中可以依靠 NSClassFromString(), NSSelectorFromString() 等方法正确的获取类和对象,也就不需要依赖组件了。
但是这样出现的问题就是一旦组件多了起来, Mediator 就会越来越臃肿,因为每个组件都需要 Mediator 来处理他们对应的方法,并且实际上每个处理的方法逻辑上都大致相同,使用反射机制,这样就产生了大量的重复代码。
所以在实际的代码中,casatwy 使用了 target - action 方案:即由每一个组件来具体实现关于 Mediator的 分类,由他们自己给 Mediator 添加对应的方法,这样就解决了代码量臃肿的问题,并且每个组件只需要维护自己那部分的关于 Mediator 的分类,工作量也有了减少。
并且 Mediator 中对外部调用和 app 内部调用做了区分,在对外部参数进行处理之后,到最后都是调用同样的 performSelector:withParam: 方法,实现了本地调用为外部调用服务的宗旨。
对于通过 Mediator 对各个组件的调用,实际上也由每个组件自己的 Target 类来维护,这个 Target_A 类附属于组件 A,并且实现了组件A所有能够对外提供的方法,最后的代码结构也就是这样的
组件化.png
这种方案的逻辑十分清晰,即 target - action 通过对 Category ModuleAActions 进行调用来实现对 Mediator 中方法的调用,进行解析转发最后实现特定组件中方法的调用。需要注意的是这里最后的特定组件即是图上的 Target_A,并不是 DemoModulADteailViewController,因为 Target_A 相当于 DemoModulADteailViewController 的门,只需要实例化了 Target_A ,就可以对 DemoModulADteailViewController 对外的方法进行调用,这样隐藏了 DemoModulADteailViewController 的内部结构,保持它原本的逻辑不被破坏,符合黑盒模型。
这种方案的优点在于可以完全针对不同的情况,各种类型的参数进行调用,并且不需要在系统内维护路由表,只需要在用到的时候进行 runtime 转发即可。
缺点在于中间件中其实也有部分硬编码,但是都被封装到中间件中了,外界并看不到。个人感觉理解起来相对较难,使用起来也比较麻烦。
方案3:
之后 limboy 也迅速更新了一篇文章来作为回应:蘑菇街组件化之路续 在这篇文章中,为了解决 casatwy 所提出来的问题,使用了与 target - action 类似的方案:protocol - class 方案来实行组件化。
所谓 protocol - class 方案即中间件 Mediator 首先需要有 registerClass:forProtocol: 方法来注册实现了这个协议的类,在 Mediator 内部维护一个 map,来注册这个 class 和 protocol,这本质上和 URLRouter 的注册类似,因为 Mediator 为单例,所以也会造成内存常驻 。Mediator 同时向外提供 classForProtocol: 方法,来返回特定的类,并没有人关心这个 class 是什么,它只需要实现了这个 protocol 即可。
然后定义一个公共的 Protocol.h 文件,这个文件中定义了所有的接口和他们所声明的方法,而具体组件需要做的就是实现这个接口,并且把自己注册到 Mediator 中。
调用时,其他组件只需要声明 Mediator 头文件,然后调用 Class class = [Mediator classForProtocol:XXXProtocol] 即可获取对象。这个被获取的对象应该为一个组件的 Entry 类,类似方案2 的 Target,在这个 Entry 类中将自己注册到 Mediator 中并且实现协议中的方法。
总的来说这种方案是方案1的一个拓展,满足了方案1中组件调用时不适用条件下的使用,总的来说和方案1的原理大致相同,理解起来也比较简单,并且也不存在方案1和2中的硬编码问题。
缺点在于,当我们想要修改某一个组件对外方法时,我们还需要先修改 protocol,当我们想获取 Class 时,我们还要先将 class 的实例化传递给调用方,然后再由调用方来实例化对象。非常不方便。
扩展
对于组件化这一部分,我也阅读了一些比较好的文章,在这里分享一下:
组件化----路由设计思路分析 ,这篇文章比较长,系统的分析了一下路由设计的思想和每个组件化方案的基本原理。
组件架构漫谈 在这篇文章中,作者首先详细分析了上述三种方案的原理差异以及使用上的便利度,最后作者也基于自己的想法总结分享了他们公司组件化的方案。
iOS组件化思路-大神博客研读和思考 作者首先分析了三种组件化方案的原理,接着描述了组件化过程中需要注意的问题,最后也有自己的总结和针对于自身项目组件化思路。
总结
到现在总算是对组件化入了门,相信看完这几篇文章的你们也跟我是同样的感觉。实际上我对于组件化并没有实际的经验,因为曾经做过的项目都比较小,最多用到了路由来实现几个控制器之间的跳转。通过这次学习,我也对项目的结构有了一个重新的认识,在以后的学习中也会在这方面不断的探索。