开发效率的提高,是开发者关注的一个永恒的话题。对于 iOS 而言,编译速度一直是影响 iOS 开发和集成测试效率关键的一环。前端
携程旅行 App iOS 工程编译,经历了从全源码编译到工程组件化,细分 Bundle,再到细分 Bundle 基础上的进一步优化四个阶段。每次的优化改造都是不断结合业务反馈,深刻了解 xcode 编译过程后的成果。python
iOS开发交流技术群:563513413,无论你是大牛仍是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 你们一块儿交流学习成长!c++
简单回顾一下在作 Bundle 拆分以前的状况,当时整个 iOS 工程的全部代码都在一块儿,并未作工程拆分和解耦,编译时全都是源码编译,数百万行代码所有编译完成要将近一个小时。全部的开发人员都在一个工程里开发,若是由于某我的提交的代码有问题(这是经常会发生的),致使编译了很长时间以后才报错,更是耽误时间,严重影响开发效率。对于测试人员来讲,每次须要验证一个功能时打包测试都须要至少等待几十分钟,这是极大的资源浪费。面试
这个时候的 Build 过程是全源码 complie,几千上万个文件都须要编译、连接,效率可想而知。swift
携程旅行App iOS工程编译优化实践xcode
因此为了提升开发和测试的效率,提升 iOS 工程的编译速度刻不容缓。缓存
第一个优化是把整个工程的编译过程打散,把代码按照业务线拆分红一个个独立的子工程,每一个子工程的编译过程都是独立的。每一个子工程只须要保证本身工程的源码可以编译成功,对外输出统一的静态库和资源文件包的产物。这个产物咱们叫作 Bundle。app
单个业务工程(Bundle):框架
携程旅行App iOS工程编译优化实践工具
App Build:
携程旅行App iOS工程编译优化实践
对于单个业务来讲,编译时间大大缩短,整个 Build 过程变成单工程 complie,多工程 link,极大减小了 Build 过程当中的 complie 花费的时间。
这样有两个好处:
1)对于开发人员,每一个业务开发只须要把本身这个子工程切为源码引用,把其余非本身模块的子工程所有用静态库依赖,本地编译也只须要编译本身的子工程,能够大大提高本地开发编译速度。
2)对于测试人员,打包过程就变成了把全部已经编译好的子 Bundle 静态库连接到一个壳工程里,不须要对每一个文件进行编译,能够很快的打包测试验证。
在工程组件化以后,在持续集成平台上单个 Bundle 的打包时间仍是过长。所以框架团队开始研究单个 Bundle 在持续集成平台上增量编译的可能性。
通过调研,最终选定 CCache 作为解决方案。CCache 是一个编译工具,能够将 Xcode 编译文件缓存起来,从而达到编译提速。
针对本地开发该方案具备优点,可是在结合自研的移动发布平台 MCD(Mobile Continuous Delivery)(后面简称【发布平台】)上使用时效果并无达到预期,主要有两点缘由:
1)同一 Bundle 多分支共存 :App 会存在大小版本同时开发的状况,在发布平台中也就会存在不一样版本、不一样分支的状况。
2)缓存管理不便 :发布平台打包机器一般仅有 250G 磁盘空间,当面临磁盘压力时,须要灵活的清理策略。
最终框架团队采用了自管理,能作到缓存物理隔离,同时也就省去了环境配置的步骤。
增量编译具体实现:
1)合并有变更的文件
2)提供清除缓存的功能
截止到以上两步,Native 已经基本实现了增量编译,可是实际使用还不够。由于打包主要是在集成系统平台上面完成的,集成平台打包有多台机器。
携程旅行 App 的打包 Jenkins 采用的是 master-slave 模式,一个 Job 下会有多个节点,Job 是随机抽取的节点。为了提升增量编译的命中率,必需要让 Bundle 和节点关联起来。好比:有 ABCD 四个节点,HotelBundle 每次都落到 A 节点,这样才能保证 A 节点中 HotelBundle 的 xcodebuild 缓存有效,而且代码 diff 差别最小。
具体实现:
1)保留 Jenkins Job 的工做区
该步骤是在 Jenkins Job 的配置中操做,取消勾选下图中的 Delete workspace before build starts
携程旅行App iOS工程编译优化实践
2)使用 Jenkins 插件创建 Bundle 和节点的关联
基于 Jenkins Label Parameter Plugin,并作改造,实现伪随机,以保证关联的节点下线以后,能使用候补节点正常工做。
发布平台前端提供关联配置,业务能够按需选择使用。
携程旅行App iOS工程编译优化实践
经过以上步骤就实现了增量编译,可是该方案针对 swift 不生效。swift 在 Release 模式采用的全量编译(以下图), 作总体优化。不过 swift Bundle 能够采用上述 Bundle 拆分的方案。
携程旅行App iOS工程编译优化实践
以某一个编译源码文件 197 个、资源文件 142 个的 Bundle 为例看下效果。
携程旅行App iOS工程编译优化实践
采用增量编译后,Bundle 编译耗时由 116s 降为 9s。
携程旅行App iOS工程编译优化实践
最初携程旅行 App 的 Bundle 都是按照业务来拆分的,好比:酒店就一个 Hotel Bundle,在当时编译速度已经不慢了。可是随着业务的发展,单个 Bundle 中业务代码愈来愈多,文件愈来愈多,致使编译又会变慢。这时,能够将单个 Bundle 按照功能作更细粒度的拆分,好比酒店拆分出了酒店主工程、酒店基础工程。
更细粒度的 Bundle 拆分还能带来如下其余收益:
业务工程每每会大量依赖基础库代码,在本工程编译过程当中,也须要查找到引用的基础代码的头文件。
由于代码仍是在同一个仓库里,以前的方案是头文件搜索设置仍是指向本地的基础框架代码,使用循环搜索的方式。
这样的好处是任何一个头文件的修改,使用方能够立刻感知到。
缺点就是头文件没有特地为方便调用进行组织,搜索起来特别费时。
通过统计,Hotel 一个文件的编译每每都是秒级别。一整个工程编译下来就是十几分钟。
所以框架团队意识到必需要和第三方库同样,在目前的.a 和资源文件以外,提交 include 目录包含全部会被外部使用的头文件。
同时,考虑到 iOS 开发向 Swift 转型的须要,若是在 include 目录的基础上,还可以提供一份基于 include 里头文件的 module.mapmodule 文件。将方便后期业务方向 Swift 的迁移。
具体方法是:
1)首先框架的 Bundle,在工程设置中点击工程的 Target→Build Phases→Copy Files 点击 +,输入.h 把须要暴露的头文件都添加上。
这样会在输出产物的 Build 目录下,多一个 include 目录,再经过脚本去把这个目录里面的全部文件复制出来,同时生成 module.mapmodule。
2)使用的时候,将头文件搜索路径设置到 include 目录,而且设置为非递归搜索。
携程旅行App iOS工程编译优化实践
验证下来,Hotel 工程修改以后的 Build 时间为 7 分钟,相比修改以前的 19 分钟,时间减小了 63%。
费雷德里克·布鲁克斯说软件工程领域没有银弹。经过以上优化后,减小了编译时间,提高了开发和集成测试的效率,但这也不是解决编译速度问题的银弹。随着业务的不断使用,又出现了新的问题:Bundle 拉取时间过长。
Bundle 化方案各个业务的静态库生成都是在发布平台上编译的,业务在本地开发的时候再使用框架的脚本拉取 bundle 到本地。发布平台上打测试包的时候也是须要拉取全部 Bundle。
发布平台打包过程以下:
1)初始化 Jenkins 工做区,下载代码副本
2)下载 Bundle
3)使用 xcodebuild 生成 ipa
4)上传 ipa 和符号表
5)Job 状态回调
整个过程共耗时 7 分钟,目前携程旅行 App iOS 最新的版本的上线 Bundle 将近 70 个,每一个 Bundle 的静态库支持 arm6四、x84_64 等指令集,全部 Bundle 加起来有 4G 大小,即便在内网全量下载耗时也要 2~3 分钟。
好比酒店某一 Bundle:
携程旅行App iOS工程编译优化实践
全部 Bundle 全量更新一次耗时:
携程旅行App iOS工程编译优化实践
针对这个问题,解决方案是创建中央缓存。
在用户根目录下,创建一个隐藏的目录.iOSBundleRepo,按照 Bundle 的版本号存储,同一 Bundle 可存在多个版本。工具下载 Bundle 时优先判断缓存,未命中时才开始下载而且缓存到 repo 中。
创建中央缓存还能带来其余好处:在发布平台作预缓存,使用定时任务更新中央缓存,进一步节省下载耗时。
该方案实际上采用的是空间换时间的策略,随着时间推移,将会带来磁盘不足的问题,因此必需要实现清理机制。
针对不一样使用场景须要采用不一样的缓存清理策略,具体以下:
最终,打包耗时由原来的 7 分钟降为 5 分钟。
软件开发工程没有银弹,你们都是在焦油坑里挣扎。
Bundle 的方案节省了编译的时间,提升了开发的效率,方便了持续集成和测试。
为了提升单 Bundle 编译速度而导出头文件的方案,牺牲了必定的灵活性换来了编译速度的提升。头文件没有了代码中的直接搜索,框架开发人员从共同开发者真正变成了库提供者,这就要求每一次都接口的修改都要及时更新并导出。
任何一个技术方案确定是在权衡各方面以后作出取舍的结果。框架团队为了提升 iOS Build 速度,经过自研的方案,作了拆分 Bundle,优化头文件搜索路径,增量编译,创建中央缓存等步骤,基本上知足了现有我厂各业务线的平常开发需求。