关于ios:App-iOS-工程编译优化实践

47次阅读

共计 4448 个字符,预计需要花费 12 分钟才能阅读完成。

引言

开发效率的晋升,是开发者关注的一个永恒的话题。对于 iOS 而言,编译速度始终是影响 iOS 开发和集成测试效率要害的一环。

携程旅行 App iOS 工程编译,经验了从全源码编译到工程组件化,细分 Bundle,再到细分 Bundle 根底上的进一步优化四个阶段。每次的优化革新都是一直联合业务反馈,深刻理解 xcode 编译过程后的成绩。

iOS 开发交换技术群:563513413,不论你是大牛还是小白都欢送入驻,分享 BAT, 阿里面试题、面试教训,探讨技术,大家一起交流学习成长!

一、背景

简略回顾一下在做 Bundle 拆分之前的状况,过后整个 iOS 工程的所有代码都在一起,并未做工程拆分和解耦,编译时全都是源码编译,数百万行代码全副编译实现要将近一个小时。所有的开发人员都在一个工程里开发,如果因为某个人提交的代码有问题(这是经常会产生的),导致编译了很长时间之后才报错,更是耽误时间,重大影响开发效率。对于测试人员来说,每次须要验证一个性能时打包测试都须要至多期待几十分钟,这是极大的资源节约。

这个时候的 Build 过程是全源码 complie,几千上万个文件都须要编译、链接,效率可想而知。

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

所以为了进步开发和测试的效率,进步 iOS 工程的编译速度迫不及待。

二、优化计划

2.1 工程组件化

第一个优化是把整个工程的编译过程打散,把代码依照业务线拆分成一个个独立的子工程,每个子工程的编译过程都是独立的。每个子工程只须要保障本人工程的源码可能编译胜利,对外输入对立的动态库和资源文件包的产物。这个产物咱们叫做 Bundle。

单个业务工程(Bundle):

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

App Build:

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

对于单个业务来说,编译工夫大大缩短,整个 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

携程旅行 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 工程编译优化实际

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 目录,并且设置为非递归搜寻。

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

验证下来,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:

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

所有 Bundle 全量更新一次耗时:

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

针对这个问题,解决方案是建设地方缓存。

在用户根目录下,建设一个暗藏的目录.iOSBundleRepo,依照 Bundle 的版本号存储,同一 Bundle 可存在多个版本。工具下载 Bundle 时优先判断缓存,未命中时才开始下载并且缓存到 repo 中。

建设地方缓存还能带来其余益处:在公布平台做预缓存,应用定时工作更新地方缓存,进一步节俭下载耗时。

该计划实际上采纳的是空间换工夫的策略,随着时间推移,将会带来磁盘有余的问题,所以必须要实现清理机制。

针对不同应用场景须要采纳不同的缓存清理策略,具体如下:

  • 本地开发:该模式下,开发能够自由选择更新最新 Bundle 和仅更新配置,缓存应用不频繁。所以将同一 Bundle 版本个数调低,缓存有效期拉长。
  • 继续集成:公布平台打包较为频繁,缓存应用比拟频繁,并且 Bundle 版本变动较快,所以将同一 Bundle 个数调高,缓存过期工夫设置为一天。

最终,打包耗时由原来的 7 分钟降为 5 分钟。

三、存在的问题和思考

软件开发工程没有银弹,大家都是在焦油坑里挣扎。

Bundle 的计划节俭了编译的工夫,进步了开发的效率,不便了继续集成和测试。

为了进步单 Bundle 编译速度而导出头文件的计划,就义了肯定的灵活性换来了编译速度的进步。头文件没有了代码中的间接搜寻,框架开发人员从独特开发者真正变成了库提供者,这就要求每一次都接口的批改都要及时更新并导出。

任何一个技术计划必定是在衡量各方面之后做出取舍的后果。框架团队为了进步 iOS Build 速度,通过自研的计划,做了拆分 Bundle,优化头文件搜寻门路,增量编译,建设地方缓存等步骤,基本上满足了现有我厂各业务线的日常开发需要。

正文完
 0