iOS开发之组件化

2,137 阅读10分钟

近几年组件化大家吵的沸沸扬扬的,它其实也不是什么黄金圣衣,穿上立马让你的小宇宙提升几个档次,也不是海皇的三叉戟,入手就能呼风唤雨,它不过就是一种app的架构思路。其实真的很简单,如果你的项目从发布之初就是用组件化,那么在开发的过程当中势必会少很多麻烦,难点其实是对我们庞大古老的工程进行组件化的改造。

一.下面我们来谈谈组件化到底是什么

其实组件化说白了就是将一个单一的工程分解为各个独立的组件,然后按照某种方式任意组织成为一个拥有完整业务逻辑的工程。

举个例子大家就明白了,一辆完整的汽车从来都不是由一个工厂将所有的零部件生产完成的,而是轮胎厂生产轮胎,发动机厂生产发动机,玻璃厂生产玻璃等等,然后再组装成为一辆完整的汽车。我们的各个组件或者说模块就是汽车的各个零部件,我们的整个工程我们也把它叫做宿主工程就是我们的汽车,我们按照一定的规则把他们拼装起来就是一个业务逻辑完整的工程。

二.组件化产生的原因

那么组件化为何应运而生,其实在我们的开发过程中,如果本身项目的规模不大,业务线比较少,人员也比较少,我们使用一般的单一开发模式就好了。但是随着我们的项目不断的迭代更新,业务线越来越多,发开人员也组件增多,这个时候就会暴露各种各样的问题

  • 耦合性严重

  • 编译速度慢

  • 测试不独立

  • 无法使用自己擅长的设计模式 ......

  • 耦合性严重 关于耦合性严重这点举个大家深有体会的例子,我们对接手二手甚至N手项目的时候都是深恶痛绝的,因为我们不知道以前的开发人员的思路和架构,这个时候我们往往面临着三类问题 1.代码的重构 2.增加新功能 3.改bug 就拿我们改bug来说,我们由于不了解人家的思路,我们把面前的bug改掉了,结果我们出了更多得bug,越改越多,头疼至极,因为我们可能将眼前bug改掉的同时,其他同样依赖改动地方的代码却不适用了,这就非常尴尬了。 同样地对我们的新功能开发和代码重构也是如此,我们好不容易将自己的功能模块搞定的时候,由于对老模块有依赖,一旦老模块中存在某些bug,会导致我们整个工程都跑不起来,我们不但测试不了自己新写的功能模块,而且我们可能连bug在哪都不是一时半会儿就找到的,再加上解决的时间我们将耗费大量的时间和精力,大大降低了我们的发开效率。

  • 编译速度慢 随着工程的业务线越来越多,发开人员不断增加,我们的项目越来越庞大,往往项目编译少则一两分钟多则几分钟,虽然并不影响我们的开发工作,但是我们使用组件化的开发配合二进制化,我们完全可以提高整个项目的编译时间。

  • 测试不独立

从这张图我们能看到我们在发开完毕我们自己的模块之后,我们需要对自己的模块进行测试,但是主工程里面的其他模块存在一个bug,导致我们的工程编译不了,那么我们开发的模块势必也是测试不了的,真的很尴尬。

  • 无法使用自己擅长的设计模式 这个我们稍微说一下大家应该能明白,如果你的公司主要是使用MVVM的架构模式进行开发的,而你只会使用MVC进行开发,是不是很尴尬,当然我们可以按照MVC去套用MVVM进行开发,但是我给大家画一张图大家就明白了

假设说我们的设计模式按照模块划分的话,我们没法使用MVC去套用我们的MVVM,这样我们除了去学习MVVM之外别无他法,可关键是你真的有足够的时间在短时间内上手吗? 当然如果我们按照功能模块来划分的话,我们的MVC倒是可以套用我们的MVVM,但是你能保证不出问题吗?

三.组件化的优势

那么我们使用组件化之后到底能达到什么样的效果呢?

  • 组件的独立 我们终于可以独立编写我们的模块,独立编译而不用漫长的等待主工程长达数分钟的编译,我们再也不用担心因为各种非自己功能模块中的bug让我们寸步难行无法单独测试了。
  • 资源的重用 我们项目中各种分类,宏定义,基础配置这些基础的代码,以及我们的轮播器,选项卡等等这些功能性的自定义UI组件再也不用重复的拖取或者重写,我们只需要以pod库的形式直接导入到工程中就OK啦。
  • 高效的迭代 当我们需要增加或者删除某些模块,我们只需要将对应的路径删除掉,就可以一次性将整个模块增加或者移除又不会影响宿主工程的正常运行,十分高效。 ※配合二进制,可大大提高项目的编译速度 我们把业务性、功能性、基础性的模块拆分成组件以后,可以采用静态库打包,framework库的形式二进制化组件,这样将大大提高我们的编译速度。

四.组件化应该考虑的问题

  • 组件的划分 我们一般将组件划分为三类 基础组件: 包含基本配置(常量,宏)、分类(各种系统类的扩展)、网络(AFN、SDWebImage封装)、工具(日期时间处理、文件处理、设备信息等)

功能组件: 包含控件(弹幕、轮播器、选项菜单、图文菜单等)、功能(断点续传、音频处理等)

业务组件: 业务线一(子业务线一,子业务线二.....) 业务线二(子业务线一,子业务线二.....)

  • 组件层级之间的关系

从这张图我们可以看出来,我们三大组件类,其实是有层级关系的,我们的业务组件既要使用我们的基础组件也要使用我们的功能组件,它属于我们基础组件和功能组件的上一层,而我们的基础组件和功能组件属于同一层级,他们之间是不能互相产生依赖关系的。

如果说我们的功能组件的弹幕需要使用到基础组件中的有关布局View的分类,我们这个时候最好的做法并不是将让我们的功能组件依赖于我们的基础组件,这样的话别人要使用我们的弹幕却要将我们整个基础组件都下载下来,那么我们的组件化就失去了原有的意义。我们在这里推荐的做法是讲我们所需要的那块代码直接拷贝到我们的功能组件当中去,这样做的好处在于我们的功能组件不需要依赖我们的基础组件。

同样在我们三大组件类的内部,组件之间也不能产生依赖关系,好比我们的弹幕不能依赖于我们的播放器,总不能别人要使用我们的弹幕还得把播放器给下载下来把,这样也是不合理的,对我们业务组件也是一样的,我们要增加或者删除某个业务线,结果导致其他的业务线没法正常的使用了,这都是不可取的。 但是某些时候我们确实需要使用其他组件里面的内容,但是他们之间又不能产生依赖关系,这个时候我们就要使用到组件间的通讯,这个在后面会讲到组件间如何通讯。

  • 组件的存在形式 组件内部:根据设计模式划分文件夹结构 组件形式(对外):每个组件都是以pod库的形式存在 组件测试:单独的测试工程(这里我们可以通过创建pod模板库形式,直接拥有测试工程)

  • 我们是以Cocoapods的形式安装各个组件的

这张图我们可以看到,我们的业务组件是可以依赖我们的基础组件和我们的功能组件的,而我们的业务组件都是以pod库的形式借助我们Cocoapod安装到我们宿主工程中去,我们的宿主工程面向的都是我们的业务组件。

  • 组件间的通讯 上面提到了我们同层次间的组件或者是我们三大组件内部的组件之间是不能有依赖关系的,但是确实有些时候我们一个组件内部发生了一些事件想要告诉其他组件,或者需要调用某些组件的服务,这个时候我们就需要用到组件之间的通讯。 这里我们来讲讲其中的一种方式--中间件,我们用一张示意图来描述一下

在这里我们看到我们组件都是通过中间件来进行交互的,组件将内部发生的变化告诉给中间件,中间件在通知其他组件。我们组件把各自的服务给中间件,需要对应服务的组件就会去找中间件拿,这样的话我们组件之间不会产生依赖关系,同时又能进行通讯。

五.分离组件的难点--解耦

一般在组件化的分离各个组件的时候,解耦这个话题我们是回避不了的,但是其实我们一般会遇到两种情况 1.组件里面依赖其他公共功能 2.组件内部需要对接某个服务

组件里面依赖其他公共功能 对于这种情况,我们一般最快的方式就是直接copy代码,虽然这个过程比较恶心,但是好处就是不会有额外的依赖,对于一些不重要的工具方法,我们都可以拷贝到内部来使用。 举个例子大家都明白了,我们使用获取屏幕尺寸的方法,而这个方法我们一般写成宏定义放在我们的基础组件中,我们的业务组件中要用到这个方法没这个时候我们没必要把我们基础组件也整个下载下来,我们直接复制粘贴这短代码就好了。 我们也可以把组件依赖的代码先做成一个pod库,然后依赖这个pod库就好了,这样我们的问题就迎刃而解了。

组件内部需要对接某个服务 比如我们控件的内部涉及到加载网络图片,我们一般会用到我们的SDWebImage的框架,虽然我们可以在使用远程私有索引库的时候添加依赖,那么我们在下载我们的私有库里面组件的时候我们可以将SDWebImage一并集成到我们的宿主工程中。如果开发过程中,公司用得不是SDWebImage不是会很尴尬吗? 所以我们使用的方式就是使用block或者代理把这部分职责丢出去,那么我们就可以自用的选择我们所需要使用的第三方框架或者公司内部写的框架,不用再纠结了。