干货 | 携程旅行App iOS工程编译优化实践
作者简介
天超,携程资深软件工程师,关注iOS研发,喜欢用脚本语言解决各种难题。
引言
开发效率的提升,是开发者关注的一个永恒的话题。对于iOS而言,编译速度一直是影响iOS开发和集成测试效率关键的一环。
携程旅行App iOS工程编译,经历了从全源码编译到工程组件化,细分Bundle,再到细分Bundle基础上的进一步优化四个阶段。每次的优化改造都是不断结合业务反馈,深入了解xcode编译过程后的成果。
一、背景
简单回顾一下在做Bundle拆分之前的情况,当时整个iOS工程的所有代码都在一起,并未做工程拆分和解耦,编译时全都是源码编译,数百万行代码全部编译完成要将近一个小时。所有的开发人员都在一个工程里开发,如果因为某个人提交的代码有问题(这是常常会发生的),导致编译了很长时间之后才报错,更是耽误时间,严重影响开发效率。对于测试人员来说,每次需要验证一个功能时打包测试都需要至少等待几十分钟,这是极大的资源浪费。
这个时候的Build过程是全源码complie,几千上万个文件都需要编译、链接,效率可想而知。
所以为了提高开发和测试的效率,提高iOS工程的编译速度刻不容缓。
二、优化方案
2.1 工程组件化
第一个优化是把整个工程的编译过程打散,把代码按照业务线拆分成一个个独立的子工程,每个子工程的编译过程都是独立的。每个子工程只需要保证自己工程的源码能够编译成功,对外输出统一的静态库和资源文件包的产物。这个产物我们叫做Bundle。
单个业务工程(Bundle):
App Build:
对于单个业务来说,编译时间大大缩短,整个Build过程变成单工程complie,多工程link,极大减少了Build过程中的complie花费的时间。
这样有两个好处:
1)对于开发人员,每个业务开发只需要把自己这个子工程切为源码引用,把其他非自己模块的子工程全部用静态库依赖,本地编译也只需要编译自己的子工程,可以大大提升本地开发编译速度。
2)对于测试人员,打包过程就变成了把所有已经编译好的子Bundle静态库链接到一个壳工程里,不需要对每个文件进行编译,可以很快的打包测试验证。
2.2 增量编译
在工程组件化之后,在持续集成平台上单个Bundle的打包时间还是过长。因此框架团队开始研究单个Bundle在持续集成平台上增量编译的可能性。
经过调研,最终选定CCache做为解决方案。CCache是一个编译工具,可以将Xcode编译文件缓存起来,从而达到编译提速。
针对本地开发该方案具有优势,但是在结合自研的移动发布平台MCD(Mobile Continuous Delivery)(后面简称【发布平台】)上使用时效果并没有达到预期,主要有两点原因:
1)同一Bundle多分支共存 :App会存在大小版本同时开发的情况,在发布平台中也就会存在不同版本、不同分支的情况。
2)缓存管理不便 :发布平台打包机器通常仅有250G磁盘空间,当面临磁盘压力时,需要灵活的清理策略。
最终框架团队采用了自管理,能做到缓存物理隔离,同时也就省去了环境配置的步骤。
增量编译具体实现:
1)合并有变动的文件
打包任务会根据新的 commitId 下载一份代码副本,不能直接使用该副本,因为代码文件内容没有变动,仅仅是文件属性的变动也会导致 xcodebuild 缓存不生效。因此需要副本和工作区内的源码做diff,仅仅合并内容有变动的文件。
使用 python 的 filecmp 实现合并代码逻辑,并且支持配置 ignore。
xcodebuild 指定 -derivedDataPath 设置缓存路径,并将该目录配置到 diff ignore中。
2)提供清除缓存的功能
xcodebuild的缓存有时候会出问题,比如修改了c++文件后有时并不会生效,这种需要提供清除缓存的功能,可以由开发自由选择使用。
截止到以上两步,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
2)使用Jenkins插件建立Bundle和节点的关联
基于Jenkins Label Parameter Plugin,并做改造,实现伪随机,以保证关联的节点下线之后,能使用候补节点正常工作。
发布平台前端提供关联配置,业务可以按需选择使用。
通过以上步骤就实现了增量编译,但是该方案针对swift不生效。swift在Release模式采用的全量编译(如下图),做整体优化。不过swift Bundle可以采用上述Bundle拆分的方案。
以某一个编译源码文件197个、资源文件142个的Bundle为例看下效果。
采用增量编译后,Bundle编译耗时由116s降为9s。
2.3 Bundle细分
最初携程旅行App的Bundle都是按照业务来拆分的,比如:酒店就一个Hotel Bundle,在当时编译速度已经不慢了。但是随着业务的发展,单个Bundle中业务代码越来越多,文件越来越多,导致编译又会变慢。这时,可以将单个Bundle按照功能做更细粒度的拆分,比如酒店拆分出了酒店主工程、酒店基础工程。
更细粒度的Bundle拆分还能带来以下其他收益:
加快本地开发编译:某个功能的开发人员只需要将自己这个功能模块切为源码,其他模块全用静态库,提高本地开发编译效率。
为其他独立app提供更细粒度的模块功能支持:我厂的很多独立App都是共用一套框架和基础组件的,按功能模块细粒度的拆分出独立的模块Bundle后,可以使独立app在选择基础组件时按需选择。
2.4 合理设置头文件搜索路径
业务工程往往会大量依赖基础库代码,在本工程编译过程中,也需要查找到引用的基础代码的头文件。
因为代码还是在同一个仓库里,之前的方案是头文件搜索设置还是指向本地的基础框架代码,使用循环搜索的方式。
这样的好处是任何一个头文件的修改,使用方可以马上感知到。
缺点就是头文件没有特意为方便调用进行组织,搜索起来特别费时。
经过统计,Hotel一个文件的编译往往都是秒级别。一整个工程编译下来就是十几分钟。
因此框架团队意识到必须要和第三方库一样,在目前的.a和资源文件之外,提交include目录包含所有会被外部使用的头文件。
同时,考虑到iOS开发向Swift转型的需要,如果在include目录的基础上,还能够提供一份基于include里头文件的module.mapmodule文件。将方便后期业务方向Swift的迁移。
具体方法是:
1)首先框架的Bundle,在工程设置中点击工程的Target→Build Phases→Copy Files点击+,输入.h把需要暴露的头文件都添加上。
这样会在输出产物的Build目录下,多一个include目录,再通过脚本去把这个目录里面的所有文件复制出来,同时生成module.mapmodule。
2)使用的时候,将头文件搜索路径设置到include目录,并且设置为非递归搜索。
验证下来,Hotel工程修改之后的Build时间为7分钟,相比修改之前的19分钟,时间减少了63%。
2.5 建立中央缓存
费雷德里克·布鲁克斯说软件工程领域没有银弹。通过以上优化后,减少了编译时间,提升了开发和集成测试的效率,但这也不是解决编译速度问题的银弹。随着业务的不断使用,又出现了新的问题:Bundle拉取时间过长。
Bundle化方案各个业务的静态库生成都是在发布平台上编译的,业务在本地开发的时候再使用框架的脚本拉取bundle到本地。发布平台上打测试包的时候也是需要拉取所有Bundle。
发布平台打包过程如下:
1)初始化Jenkins工作区,下载代码副本
2)下载Bundle
3)使用xcodebuild生成ipa
4)上传ipa和符号表
5)Job状态回调
整个过程共耗时7分钟,目前携程旅行App iOS最新的版本的上线Bundle将近70个,每个Bundle的静态库支持arm64、x84_64等指令集,所有Bundle加起来有4G大小,即使在内网全量下载耗时也要2~3分钟。
比如酒店某一Bundle:
所有Bundle全量更新一次耗时:
针对这个问题,解决方案是建立中央缓存。
在用户根目录下,建立一个隐藏的目录.iOSBundleRepo,按照Bundle的版本号存储,同一Bundle可存在多个版本。工具下载Bundle时优先判断缓存,未命中时才开始下载并且缓存到repo中。
建立中央缓存还能带来其他好处:在发布平台做预缓存,使用定时任务更新中央缓存,进一步节省下载耗时。
该方案实际上采用的是空间换时间的策略,随着时间推移,将会带来磁盘不足的问题,所以必须要实现清理机制。
针对不同使用场景需要采用不同的缓存清理策略,具体如下:
本地开发:该模式下,开发可以自由选择更新最新Bundle和仅更新配置,缓存使用不频繁。所以将同一Bundle版本个数调低,缓存有效期拉长。
持续集成:发布平台打包较为频繁,缓存使用比较频繁,并且Bundle版本变动较快,所以将同一Bundle个数调高,缓存过期时间设置为一天。
最终,打包耗时由原来的7分钟降为5分钟。
三、存在的问题和思考
软件开发工程没有银弹,大家都是在焦油坑里挣扎。
Bundle的方案节省了编译的时间,提高了开发的效率,方便了持续集成和测试。
为了提高单Bundle编译速度而导出头文件的方案,牺牲了一定的灵活性换来了编译速度的提高。头文件没有了代码中的直接搜索,框架开发人员从共同开发者真正变成了库提供者,这就要求每一次都接口的修改都要及时更新并导出。
任何一个技术方案肯定是在权衡各方面之后做出取舍的结果。框架团队为了提高iOS Build速度,通过自研的方案,做了拆分Bundle,优化头文件搜索路径,增量编译,建立中央缓存等步骤,基本上满足了现有我厂各业务线的日常开发需求。
【推荐阅读】
一波N折的携程酒店Swift-Objc混编实践
携程Android 10适配踩坑指南
暗黑模式在 Trip.com App 的实践
携程酒店安卓地图开发实践
《携程架构实践》《携程人工智能实践》上市啦!
“携程技术”公众号后台回复“新书”,
可免费获得两本书的试读样章~
《携程架构实践》
京东
当当
《携程人工智能实践》
京东
当当
“携程技术”公众号
分享,交流,成长
干货 | 携程旅行App iOS工程编译优化实践相关推荐
- 干货 | 携程机票 App KMM 跨端生产实践
作者简介 禹昂,携程移动端资深工程师,Kotlin 中文社区核心成员,图书<Kotlin 编程实践>译者. Derek,携程资深研发经理,专注于移动端开发,热衷于各种跨端技术的研究和实践. ...
- 干货 | 携程机票 Android Jetpack 与 Kotlin Coroutines 实践
作者简介 禹昂,携程机票移动端资深工程师,Kotlin 中文社区核心成员,图书<Kotlin 编程实践>译者. 一.前言 1.1 技术背景与选型 自 2017年 Google IO 大会以 ...
- 干货 | 携程火车票基于因果推断的业务实践
作者简介 Seven,数据分析师,专注用户增长.数据科学等领域. 一.背景 携程作为旅游平台,跟用户需求息息相关,理解和识别各个策略/系统对转化/收益的因果关系尤为重要,在这个过程中需要将影响因变量的 ...
- 干货 | 携程桌面应用的前端内存优化与监控
作者简介 吕萌萌,携程资深前端开发工程师,关注前端性能优化与前端框架建设. 一.背景 桌面应用的前端场景不同于传统前端,具有使用者停留时间长,功能复杂且高度聚集在单一页面等特征,因此带来了不同的技术挑 ...
- 携程移动端 UI 界面性能优化实践
UI 卡顿原理和原因 人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯,其帧率通常为 24fps:那么,用手机当然也需要感知屏幕操作的连贯性(尤其是动画过渡) ...
- 百度APP iOS端内存优化实践-大块内存监控方案
01 背景 内存不足引发的APP崩溃通常称为OOM(Out Of Memory),iOS端无法捕获OOM异常,也得不到任何堆栈信息,给我们排查和解决问题带来很多困扰.引起OOM的原因归根结底就是 ...
- 干货 | 携程APP Native/RN内嵌Flutter UI混合开发实践和探索
作者简介 Deway,携程资深工程师,iOS客户端开发,热衷于大前端和动态化技术: Frank,携程高级工程师,关注移动端热门技术,安卓客户端开发. 前言 随着各种多端技术的蓬勃发展,如今的移动端和前 ...
- 干货 | 携程移动直播探索
作者简介 鹏程,携程 Android 开发工程师,Android google jetpack和kotlin语言的拥护者. 一.背景 直播行业大概在10年前就开始兴起了,秀场直播和游戏直播是pc时代比 ...
- 携程移动App架构优化之旅
本文为携程移动开发总监陈浩然在2015年10月份的ArchSummit全球架构师峰会上的演讲总结.由于面向受众为架构师,因此不会涉及到很多技术细节.通过本文,你可以了解携程通过哪些手段来优化它的App ...
最新文章
- 已知长短轴求椭圆上任意一点的坐标_高中数学必修2:平面解析几何——椭圆(经典习题)...
- Dubbo--zookeeper面试中问题解答
- python数据结构之队列(一)
- 没有bug队——加贝——Python 练习实例 37,38
- opensource项目_最佳Opensource.com:艺术与设计
- Python入门--特殊属性
- Intouch/ifix语音报警系统制作(3-利用自定义过程和函数,重构先前版本)
- multiprocessing模块
- Hadoop安装部署的三种模式总结
- 本特利前置器330180-50-00
- 屏幕录像专家录制的内容中有“未注册”字样怎么办
- 秒读小说app带源码,开源阅读软件app,开源小说阅读app源码
- AndroidUI:Android的Holo Theme
- 一键生成自签名证书,为内网IP配置HTTPS访问来使用navigator.getUserMedia录音
- android服务实现播放器,Android实现简单音乐播放器(MediaPlayer)
- HDU-4567-思维-Brilliant Programmers Show -13长沙邀请赛
- Amazon Alexa通过云控制Bluetooth Mesh设备
- 转阿彪 PHP常见的安全问题
- ubuntu修改u盘权限_Ubuntu下的U盘只读文件系统,该图标已锁定,表明无法对其进行修改...
- Unity --- 导航网格 与 导航的使用
热门文章
- RGB通道和YCBCR通道相互转换——MATLAB实现
- 韩顺平SQL,雇员系统表.txt
- python根据点计算直线方程
- 18、包含网关(Inclusive Gateway)
- QQ返利圣诞8天购物狂欢节(12月19~12月26日)
- POI(HSSF)生成的excel文档,excel显示“文件已损坏,无法打开“解决方法
- 恐秋的C++学习笔记(完结)
- mysql idataparameter_asp.net中IDataParameter调用存储过程的实现方法
- 工具︱ Web3加密浏览器Brave有什么特别之处?
- python 应声虫怎么做_生活中要敢于表达自己的想法,永远不做“应声虫”!