上篇 《大话大前端时代:Vue 与 iOS 的组件化(上)》主要描述了 Vue 的组件化,下篇将描述 iOS 的组件化,并对两者做一个对比。精彩继续。
在 iOS Native app 前期开发的时候,如果参与的开发人员也不多,那么代码大多数都是写在一个工程里面的,这个时候业务发展也不是太快,所以很多时候也能保证开发效率。
但是一旦项目工程庞大以后,开发人员也会逐渐多起来,业务发展突飞猛进,这个时候单一的工程开发模式就会暴露出弊端了。
项目内代码文件耦合比较严重
容易出现冲突,大公司同时开发一个项目的人多,每次 pull 一下最新代码就会有很多冲突,有时候合并代码需要半个小时左右,这会耽误开发效率。
业务方的开发效率不够高,开发人员一多,每个人都只想关心自己的组件,但是却要编译整个项目,与其他不相干的代码糅合在一起。调试起来也不方便,即使开发一个很小的功能,都要去把整个项目都编译一遍,调试效率低。
为了解决这些问题,iOS 项目就出现了组件化的概念。所以 iOS 的组件化是为了解决上述这些问题的,这里与前端组件化解决的痛点不同。
iOS 组件化以后能带来如下的好处:
加快编译速度(不用编译主客那一大坨代码了,各个组件都是静态库)
自由选择开发姿势(MVC / MVVM / FRP)
方便 QA 有针对性地测试
提高业务开发效率
iOS 组件化的封装性只是其中的一小部分,更加关心的是如何拆分组件,如何解除耦合。前端的组件化可能会更加注重组件的封装性,高可复用性。
iOS 的组件化手段非常单一,就是利用 Cocoapods 封装成 pod 库,主工程分别引用这些 pod 即可。越来越多的第三方库也都在 Cocoapods 上发布自己的最新版本,大公司也在公司内部维护了公司私有的 Cocoapods 仓库。一个封装完美的 Pod 组件,主工程使用起来非常方便。
具体如果用 Cocoapods 打包一个静态库 .a 或者 framework ,网上教程很多,这里给一个链接 http://www.cnblogs.com/brycezhang/p/4117180.html
,详细的操作方法就不再赘述了。
最终想要达到的理想目标就是主工程就是一个壳工程,其他所有代码都在组件 Pods 里面,主工程的工作就是初始化,加载这些组件的,没有其他任何代码了。
iOS 划分组件虽然没有一个很明确的标准,因为每个项目都不同,划分组件的粗粒度也不同,但是依旧有一个划分的原则。
App 之间可以重用的 Util、Category、网络层和本地存储 storage 等等这些东西抽成了 Pod 库。还有些一些和业务相关的,也是在各个 App 之间重用的。
原则就是:要在 App 之间共享的代码就应该抽成 Pod 库,把它们作为一个个组件。不在 App 间共享的业务线,也应该抽成 Pod,解除它与工程其他的文件耦合性。
常见的划分方法都是从底层开始动手,网络库,路由,MVVM 框架,数据库存储,加密解密,工具类,地图,基础SDK,APM,风控,埋点……从下往上,到了上层就是各个业务方的组件了,最常见的就类似于购物车,我的钱包,登录,注册等。
iOS 的组件化是借助 Cocoapods 完成的。关于 Cocoapods 的具体工作原理,可以看这篇文章《CocoaPods 都做了什么?》 http://draveness.me/cocoapods.html
。
这里简单的分析一下 pod 进来的库是什么加载到主工程的。
pod 会依据 Podfile 文件里面的依赖库,把这些库的源代码下载下来,并创建好 Pods workspace。当程序编译的时候,会预先执行2个 pod 设置进来的脚本。
在上面这个脚本中,会把 Pods 里面的打包好的静态库合并到 libPods-XXX.a 这个静态库里面,这个库是主工程依赖的库。
上图就是给主项目加载 Pods 库的脚本。
Pods 另外一个脚本是加载资源的。见下图。
这里加载的资源是 Pods 库里面的一些图片资源,或者是 Boudle 里面的 xib ,storyboard,音乐资源等等。这些资源也会一起打到 libPods-XXX.a 这个静态库里面。
上图就是加载资源的脚本。
iOS 的组件主要分为2种形式:
静态库
动态库
静态库一般是以 .a 和 .framework 结尾的文件,动态库一般是以 .dylib 和 .framework 结尾的文件。
这里可以看到,一个 .framework 结尾的文件仅仅通过文件类型是无法判断出它是一个静态库还是一个动态库。
静态库和动态库的区别在于:
.a文件肯定是静态库,.dylib肯定是动态库,.framework可能是静态库也可能是动态库;
静态库在链接其他库的情况时,它会被完整的复制到可执行文件中,如果多个App都使用了同一个静态库,那么每个 App 都会拷贝一份,缺点是浪费内存。类似于定义一个基本变量,使用该基本变量是是新复制了一份数据,而不是原来定义的;静态库的好处很明显,编译完成之后,库文件实际上就没有作用了。目标程序没有外部依赖,直接就可以运行。当然其缺点也很明显,就是会使用目标程序的体积增大。
动态库不会被复制,只有一份,程序运行时动态加载到内存中,系统只会加载一次,多个程序共用一份,节约了内存。而且使用动态库,可以不重新编译连接可执行程序的前提下,更新动态库文件达到更新应用程序的目的。
之前我们讨论过了,iOS 组件化十分关注解耦性,这算是组件化的一个重要目的。iOS 各个组件之间消息传递是用路由来实现的。关于路由,笔者曾经写过一篇比较详细的文章,感兴趣的可以来看这篇文章《iOS 组件化 —— 路由设计思路分析》https://github.com/halfrost/Halfrost-Field/blob/master/contents/iOSRouter/iOS%20%E7%BB%84%E4%BB%B6%E5%8C%96%20%E2%80%94%E2%80%94%20%E8%B7%AF%E7%94%B1%E8%AE%BE%E8%AE%A1%E6%80%9D%E8%B7%AF%E5%88%86%E6%9E%90.md
。
iOS 组件注册的方式主要有3种:
load 方法注册
读取 plist 文件注册
Annotation 注解方式注册
前两种方式都比较简单,容易理解。
第一种方式在 load 方法里面利用 Runtime 把组件名和组件实例的映射关系保存到一个全局的字典里,方便程序启动以后可以随时调用。
第二种方式是把组件名和组件实例的映射关系预先写在 plist 文件中。程序需要的时候直接去读取这个 plist 文件。plist 文件可以从服务器读取过来,这样 App 还能有一定的动态性。
第三种方式比较黑科技。利用的是 Mach-o 的数据结构,在程序编程链接成可执行文件的时候,就把相关注册信息直接写入到最终的可执行文件的 Data 数据段内。程序执行以后,直接去那个段内去读取想要的数据即可。
关于这三种做法的详细实现,可以看笔者之前的一篇文章《BeeHive —— 一个优雅但还在完善中的解耦框架》 https://halfrost.com/beehive/
,在这篇文章里面详细的分析了上述3种注册过程的具体实现。
经过上面的分析,我们可以看出 Vue 的组件化和 iOS 的组件化区别还是比较大的。
主要体现在单页应用和类多页应用的差异。
现在前端比较火的一种应用就是单页Web应用(single page web application,SPA),顾名思义,就是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。
浏览器从服务器加载初始页面,以及整个应用所需的脚本(框架、库、应用代码)和样式表。当用户定位到其他页面时,不会触发页面刷新。通过 HTML5 History API 更新页面的 URL 。浏览器通过 AJAX 请求检索新页面(通常以 JSON 格式)所需的新数据。然后, SPA 通过 JavaScript 动态更新已经在初始页面加载中已经下载好的新页面。这种模式类似于原生手机应用的工作原理。
但是 iOS 开发更像类 MPA (Multi-Page Application)。
往往一个原生的 App ,页面差不多应该是上图这样。当然,可能有人会说,依旧可以把这么多页面写成一个页面,在一个 VC 里面控制所有的 View,就像前端的 DOM 那样。这种思路虽然理论上是可行的,但是笔者没有见过有人这么做,页面一多起来,100多个页面,上千个 View,都在一个 VC 上控制,这样开发有点蛋疼。
iOS 的组件化一部分也是解决了代码复用性的问题,但是更多的是解决耦合性大,开发效率合作性低的问题。而 Vue 的组件化更多的是为了解决代码复用性的问题。
iOS 平台由于有 UIKit 这类苹果已经封装好的 Framework,所以基础控件已经封装完成,不需要我们自己手动封装了,所以 iOS 的组件着眼于一个大的功能,比如网络库,购物车,我的钱包,整个业务块。前端的页面布局是在 DOM 上进行的,只有最基础的 CSS 的标签,所以控件都需要自己写,Vue 的组件化封装的可复用的单文件组件其实更加类似于 iOS 这边的 ViewModel。
所以从封装性上来讲,两者可以相互借鉴的地方并不多。iOS 能从前端借鉴的东西在状态管理这一块,单向数据流的思想。不过这一块思想虽然好,但是如何能在自家公司的app上得到比较好的实践,依旧是仁者见仁智者见智的事了,并不是所有的业务都适合单向数据流。
Vue.js 官方文档
https://cn.vuejs.org/