一、为什么要组件化

1.实现之间解耦、减少项目的编译时间,提升业务开发效率。

通常一个工程中会有多个模块,而模块之间会有依赖关系,比如A调用B,那么在A模块中就会引用B模块的头文件,同时可能B模块又会依赖C模块,C模块又会依赖A模块等等,最终的结果是各模块高度耦合,特别是大型的工程,耦合特别严重。如下图所示




iOS模块化方案 ios模块化开发_组件化


如果想避免耦合,那么我们需要设计一种结构避免,各模块之间耦合,可以如下图所示结构:


iOS模块化方案 ios模块化开发_版本管理_02


这种结构有什么好处呢?1)首先是将模块拆分成组件单独管理,拆分成单独的库,这样各组件在进行开发的时候,只需要编译自己的库文件,大大减少文件的编译时间,我们的工程,之前编译需要20分钟左右,现在只需几分钟,这个对效率提高,非常明显。
2)各个库单端管理,可以进行权限设置,只有各组件相关人员才可以修改组件代码,不会出现一个大的工程中,A修改了B的代码的情况,可以一定程度上保证代码的安全性。

2.合并项目容易出现冲突。

在每次发版以后,release分支会向dev分支合并代码,由于各组件会提交许多代码,不同的开发者可能会同时修改同一个文件,引起冲突,这样一个工程中就会出现多处的合并冲突,比如一个几十人的团队,这个冲突的概率非常大,需要各组件的人去dev分支修改自己组件的冲突,如果冲突没有解决,整个工程都无法运行,影响整个团队的开发,非常影响效率。

3.模块拆分成组件,方便对外付能

假设一个项目中有多个组件,相互耦合,这个时候想单独将一个组件拆分出来,提供给它人使用,几乎是不可能的,而组件化接触这种耦合之后,我们可以直接将某个组件单独提供给它人使用,各个组件可以像积木一样,相互组合起来,形成一个新的APP对外付能。

二、组件化方案

组件化方案,主要分为两个部分,一是代码如何解耦,二是如何进行版本管理,下面分别进行详细讲解。

1.代码解耦

1)组件原则
组件通常分为两种类型的组件:基础组件,业务组件。
*业务组件依赖基础组件
*基础组件不可依赖业务组件。
*业务组件间不可相互依赖。

2)组件间的通信方式:
组件间通信方式,业内主要有两种实现方式:
1.协议式框架,比如蘑菇街的这种方案。蘑菇街 App 的组件化之路
2. 中间者架构,比如casatwy的方案。casatwy关于蘑菇街组件化的讨论

我们来总结一下目前的方案
蘑菇街方案-基于URL和协议
1)通过URL传递简单参数(组件间简单参数调用)
2)通过协议调用传递复杂参数(组件间复杂参数调用)

缺点:
1)页面间调用不需要URL形式,对于复杂参数,组件间调用通过URL无法满足需求。
2)注册URL时充分不必要条件,完全可以用runtime来解决这个问题。
3)使用多种方案,不利于管理和维护。
4)protocol主要存在的问题就是分散调用导致耦合。
5)需要组件向ModuleManager注册Url(因为URL调用时通过注册时的callBack来实现的,不是基于runtime,所以需要注册),会导致浪费内存。

casatwy的方案-基于Category和Target-Action

主要是基于Mediator模式和Target-Action模式,中间采用了runtime来完成调用。通过mediator的Category来输出组件的对外调用方法。使用同一种方案,实现组件间调用。
优点:
1)不需要注册URL
2)使用同一种方案,基于Target-Action模式实现组件间的调用。
3)区分app内部调用和外部调用。
4)同一种方法可以实现简单参数和复杂参数的调用。

我们的方案-基于URL和Target-Action

利用runtime实现Target-Action模式设计,通过定义URL协议确定Target和Action,通过params来传递所有页面间调用的参数,error返回调用的错误信息,completion来实现调用后的回调。类似以下形式
组件调用方式:[XXMediator openURL:@"mediator://Target/Action" arg:params error:error completion:callBack]
组件输出调用方法:每个组件有一个XXModule的类,在该类中实现对外输出的方法,提供XXMediator中Target和Action。类的头文件中包含组件可以调用的类以及需要传递的参数。

优点:
1)一种调用方式,可以实现简单和复杂参数调用。
2)不需要注册,调用简单
3)所有调用使用同一个方法实现,只需要引用Mediator,不需要引用Mediator 的category。
4)通过URL实现Target-Action,可以利用URL规则解析出Target和Action,实现调用。

2.版本管理

版本管理使用 cocoapods,每个组件都拆成独立的pod库,并生成一个配置表,来进行组件间的依赖组件的版本管理,具体内容这里就不叙述了。

三、组件化遇到问题

1.使用Target-Action方式进行调用,有个问题就是runtime的时候,如果对应Target-Action修改了,怎么提示调用方

1)静态检测URL,判断Target-Action是否存在。
2)动态调用router,debug模式下,Target-Action不存在就crash。

2.调用方如何知道接收方需要哪些key的参数?调用方如何知道有哪些target可以被调用?
1)category方式下,可以直接通过category中的方法来进行确认。
2)我们的方案中,每个组件的对应moudle类中,类的头文件中包含组件可以调用的类以及需要传递的参数。

3.调用参数解析处理在那里比较好?

各组件内部,因为这些参数的使用,各组件开发者自己最清楚。如果在category解析,Mediator中会参杂业务逻辑。

4.A调用B时,如果B要回传参数给,怎么做?

通过callBackBlock回调回传,参数是id类型,可以传任意对象,比如是一个UIViewController或者一个NSDictionary都可以。

四.组件化的缺点:

1.会增加开发者的学习成本
2.组件拆分颗粒度把握不好,会产生很多中间代码和冗余。
3.图片资源管理不好,会产生很多冗余图片。
4.组件化过程中,会带来一些组件之间的调试问题,前期会影响团队效率。

总结:

组件化是一把双刃剑,如果在工程规模较小的时候,进行组件化,可能反而会影响团队开发效率,带来一些问题,比如上面提到的缺点,只是在工程和团队达到一定规模的时候,进行组件化才会有更大的收益。