AICon全球人工智能与机器学习技术大会周四开幕,点击查看完整日程>> 了解详情
写点什么

携程旅行 App iOS 工程编译优化实践

  • 2020 年 9 月 10 日
  • 本文字数:3929 字

    阅读完需:约 13 分钟

携程旅行App 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,优化头文件搜索路径,增量编译,建立中央缓存等步骤,基本上满足了现有我厂各业务线的日常开发需求。


作者介绍


天超,携程资深软件工程师,关注 iOS 研发,喜欢用脚本语言解决各种难题。


本文转载自公众号携程技术(ID:ctriptech)。


原文链接


携程旅行App iOS工程编译优化实践


2020 年 9 月 10 日 10:051376

评论 1 条评论

发布
用户头像
赞一个
2020 年 12 月 15 日 11:08
回复
没有更多了
发现更多内容

“区块链+营销”:科技力量助力行业前行

Geek_987812

市场营销

分分钟玩转SpringBoot自定义注解

比伯

Java 大数据 编程 架构 编程语言

架构师入门学习感悟四

莫问

架构师训练营 week4 课后作业

花果山

极客大学架构师训练营

推荐好书:《使用Python进行图像处理和采集》第二版(附下载方式)

计算机与AI

Python 图像处理

Java8引入新的日期和时间库,你应该知道

Silently9527

java8

脱钩!打工人不配拥有Java程序员306道面试秘笈吗?真香

996小迁

Java 学习 架构 面试 笔记

极客时间架构师训练营 1 期 - 第 8 周总结

Kaven

Architecture Phase1 Week8:HomeWork

phylony-lu

极客大学架构师训练营

极客时间架构师培训 1 期 - 第 8 周作业

Kaven

架构师训练营第 4 周学习总结

菜青虫

极客大学架构师训练营

架构师训练营第 1 期 -- 第八周学习总结

发酵的死神

极客大学架构师训练营

一个典型的大型互联网应用系统使用了哪些技术方案和手段,主要解决什么问题?请列举描述

幸福小子

互联网系统架构

网络时间协议介绍以及服务器同步网络时间

MySQL从删库到跑路

ntp 时间同步

架构师训练营 - 第 8 周课后作业(1 期)

阿甘

Architecture Phase1 Week8:Summarize

phylony-lu

极客大学架构师训练营

架构师训练营 W04 作业

Geek_f06ede

极客大学架构师训练营

架构师训练营第 4 周课后练习

菜青虫

极客大学架构师训练营

架构师训练营第 1 期 -- 第八周作业

发酵的死神

极客大学架构师训练营

区块链治理的真实价值在哪里

Geek_987812

区块链 治理 治理机制

腾讯强推Redis大神之路成长手册!原理+应用+集群+拓展+源码五篇齐飞

Java架构追梦

Java 数据库 redis 架构 面试

架构师训练营第 1 期第 8 周作业

好吃不贵

极客大学架构师训练营

「八大排序算法」16张图带你彻底搞懂基数排序

bigsai

排序算法 基数排序

架构作业--相交链表

Nick~毓

ebay支付核心账务系统架构演进之路

贾奇 (Jacky)

支付系统 共识机制 系统稳定高可用 Event Sourcing 异地多活容灾

四、应用系统探讨

Geek_28b526

产品发布 | 准备好提升你的 ITSM 了吗?

Atlassian

DevOps Atlassian ITSM ITIL

苏州崛起为我国区块链产业高地

Geek_987812

区块链 社区矫正

年轻人的第一个MyBatis项目就要这样来学习,不走弯路

小Q

Java 学习 架构 面试 mybatis

LeetCode题解:169. 多数元素,哈希表,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

架构师训练营 week4 学习总结

花果山

极客大学架构师训练营

数据cool谈(第2期)寻找下一代企业级数据库

数据cool谈(第2期)寻找下一代企业级数据库

携程旅行App iOS工程编译优化实践-InfoQ