2018 iOS 组件化
一. 组件化的需求
在 iOS Native app 前期开发的时候,如果参与的开发人员也不多,那么代码大多数都是写在一个工程里面的,这个时候业务发展也不是太快,所以很多时候也能保证开发效率。
但是一旦项目工程庞大以后,开发人员也会逐渐多起来,业务发展突飞猛进,这个时候单一的工程开发模式就会暴露出弊端了。
- 项目内代码文件耦合比较严重
- 容易出现冲突,大公司同时开发一个项目的人多,每次 pull 一下最新代码就会有很多冲突,有时候合并代码需要半个小时左右,这会耽误开发效率。
- 业务方的开发效率不够高,开发人员一多,每个人都只想关心自己的组件,但是却要编译整个项目,与其他不相干的代码糅合在一起。调试起来也不方便,即使开发一个很小的功能,都要去把整个项目都编译一遍,调试效率低。
为了解决这些问题,iOS 项目就出现了组件化的概念。所以 iOS 的组件化是为了解决上述这些问题的,这里与前端组件化解决的痛点不同。
iOS 组件化以后能带来如下的好处:
- 加快编译速度(不用编译主客那一大坨代码了,各个组件都是静态库)
- 自由选择开发姿势(MVC / MVVM / FRP)
- 方便 QA 有针对性地测试
- 提高业务开发效率
iOS 组件化的封装性只是其中的一小部分,更加关心的是如何拆分组件,如何解除耦合。前端的组件化可能会更加注重组件的封装性,高可复用性。
二. 如何封装组件
iOS 的组件化手段非常单一,就是利用 Cocoapods 封装成 pod 库,主工程分别引用这些 pod 即可。越来越多的第三方库也都在 Cocoapods 上发布自己的最新版本,大公司也在公司内部维护了公司私有的 Cocoapods 仓库。一个封装完美的 Pod 组件,主工程使用起来非常方便。
具体如果用 Cocoapods 打包一个静态库 .a 或者 framework ,网上教程很多,这里给一个链接,详细的操作方法就不再赘述了。
image最终想要达到的理想目标就是主工程就是一个壳工程,其他所有代码都在组件 Pods 里面,主工程的工作就是初始化,加载这些组件的,没有其他任何代码了。
三. 如何划分组件
iOS 划分组件虽然没有一个很明确的标准,因为每个项目都不同,划分组件的粗粒度也不同,但是依旧有一个划分的原则。
App之间可以重用的 Util、Category、网络层和本地存储 storage 等等这些东西抽成了 Pod 库。还有些一些和业务相关的,也是在各个App之间重用的。
原则就是:要在App之间共享的代码就应该抽成 Pod 库,把它们作为一个个组件。不在 App 间共享的业务线,也应该抽成 Pod,解除它与工程其他的文件耦合性。
常见的划分方法都是从底层开始动手,网络库,路由,MVVM框架,数据库存储,加密解密,工具类,地图,基础SDK,APM,风控,埋点……从下往上,到了上层就是各个业务方的组件了,最常见的就类似于购物车,我的钱包,登录,注册等。
四. 组件化原理
iOS 的组件化原理是基于 Cocoapods 的。关于 Cocoapods 的具体工作原理,可以看这篇文章《CocoaPods 都做了什么?》。
这里简单的分析一下 pod进来的库是什么加载到主工程的。
pod 会依据 Podfile 文件里面的依赖库,把这些库的源代码下载下来,并创建好 Pods workspace。当程序编译的时候,会预先执行2个 pod设置进来的脚本。
image在上面这个脚本中,会把 Pods 里面的打包好的静态库合并到 libPods-XXX.a 这个静态库里面,这个库是主工程依赖的库。
<figure> image上图就是给主项目加载 Pods 库的脚本。
Pods 另外一个脚本是加载资源的。见下图。
image这里加载的资源是 Pods 库里面的一些图片资源,或者是 Boudle 里面的 xib ,storyboard,音乐资源等等。这些资源也会一起打到 libPods-XXX.a 这个静态库里面。
image上图就是加载资源的脚本。
五. 组件分类
iOS 的组件主要分为2种形式:
- 静态库
- 动态库
静态库一般是以 .a 和 .framework 结尾的文件,动态库一般是以 .dylib 和 .framework 结尾的文件。
这里可以看到,一个 .framework 结尾的文件仅仅通过文件类型是无法判断出它是一个静态库还是一个动态库。
静态库和动态库的区别在于:
-
.a文件肯定是静态库,.dylib肯定是动态库,.framework可能是静态库也可能是动态库;
-
静态库在链接其他库的情况时,它会被完整的复制到可执行文件中,如果多个App都使用了同一个静态库,那么每个App都会拷贝一份,缺点是浪费内存。类似于定义一个基本变量,使用该基本变量是是新复制了一份数据,而不是原来定义的;静态库的好处很明显,编译完成之后,库文件实际上就没有作用了。目标程序没有外部依赖,直接就可以运行。当然其缺点也很明显,就是会使用目标程序的体积增大。
-
动态库不会被复制,只有一份,程序运行时动态加载到内存中,系统只会加载一次,多个程序共用一份,节约了内存。而且使用动态库,可以不重新编译连接可执行程序的前提下,更新动态库文件达到更新应用程序的目的。
六. 组件间的消息传递和状态管理
之前我们讨论过了,iOS 组件化十分关注解耦性,这算是组件化的一个重要目的。iOS 各个组件之间消息传递是用路由来实现的。关于路由,笔者曾经写过一篇比较详细的文章,感兴趣的可以来看这篇文章《iOS 组件化 —— 路由设计思路分析》。
七. 组件注册方式
iOS 组件注册的方式主要有3种:
- load方法注册
- 读取 plist 文件注册
- Annotation注解方式注册
前两种方式都比较简单,容易理解。
第一种方式在 load 方法里面利用 Runtime 把组件名和组件实例的映射关系保存到一个全局的字典里,方便程序启动以后可以随时调用。
第二种方式是把组件名和组件实例的映射关系预先写在 plist 文件中。程序需要的时候直接去读取这个 plist 文件。plist 文件可以从服务器读取过来,这样 App 还能有一定的动态性。
第三种方式比较黑科技。利用的是 Mach-o 的数据结构,在程序编程链接成可执行文件的时候,就把相关注册信息直接写入到最终的可执行文件的 Data 数据段内。程序执行以后,直接去那个段内去读取想要的数据即可。
关于这三种做法的详细实现,可以看笔者之前的一篇文章《BeeHive —— 一个优雅但还在完善中的解耦框架》,在这篇文章里面详细的分析了上述3种注册过程的具体实现。
文中链接都已失效,想看的话留言发给你新的地址
作者:一缕殇流化隐半边冰霜