ios developers

一款可能适合你的iOS二进制插件

2023-02-01  本文已影响0人  一万次番茄炒蛋

本文主要内容:
一、背景
二、效果展示
三、接入插件及demo地址
四、聊聊开发插件前期的编译优化调研
五、技术选型后二进制插件的开发
六、实际使用后遇到的问题
七、总结
八、参考文献

一、背景

由于团队也是组件化的开发模式,而且有较多的app。大的C端产品打包要十几二十分钟,小的业务app打包也要八九分钟。那如何提升项目的编译效率,和打包速度,就是一个需要去处理的课题。这款插件灵活便捷,可能符合你需要的提效能力。

二、先看效果

执行pod install后的展示


image.png

二进制后工程内pod组件结构

image.png

缓存结构目录


image.png

编译前后对比


image.png image.png

具体提效程度看不同工程组件化情况,可以拿去到自己项目里感受下。我们的工程多以OC和OC+Flutter为主,也有集成Swift的组件,还没有试过纯Swift的项目,如果插件使用有问题可以提供demo,我来优化下插件。

三、先去立刻体验回来再看后续内容

gem install cocoapods-zjbinary

可配合demo工程一起食用,如果插件好用,可以点个⭐️

https://github.com/Byte10/BinaryDemo

四、聊聊开发插件前期的编译优化调研

iOS 微信编译速度优化分享

https://cloud.tencent.com/developer/article/1564372
他们的方案总结是以下:
A、优化头文件搜索路径
B、关闭 Enable Index-While-Building Functionality
C、优化 PB/模版,减少冗余代码
D、使用 PCH 预编译
E、使用工具优化头文件引入;尽量避免头文件里包含 C++ 标准库

pod层的优化方案:

https://www.dandelioncloud.cn/article/details/1498967543334379522 贝聊科技如何将 iOS 项目的编译速度提高5倍这片文章。
他们调研了下面几种方案:
1、cocoapods-packager
cocoapods-packager 可以将任意的 pod 打包成 Static Library,省去重复编译的时间,一定程度上可以加快编译时间,但是也有自身的缺点:
优化不彻底,只能优化第三方和私有 Pod 的编译速度,对于其他改动频繁的业务代码无能为力
私有库和第三方库的后续更新很麻烦,当有源码修改后,需要重新打包上传到内部的 Git 仓库
过多的二进制文件会拖慢 Git 的操作速度(目前还没部署 Git 的 LFS)
难以调试源码

2、Carthage
这个方案跟 cocoapods-packager 比较类似,优缺点都差不多,但 Carthage 可以比较方便地调试源码。
因为我们目前已经大规模使用 CocoaPods,转用 Carthage 来做包管理需要做大量的转换工作,所以不考虑这个方案了。

3、Buck
Buck 是一套通用的构建系统,由 Facebook 开源。最大的特色是智能的增量编译可以极大地提高构建速度。最早听说 Buck 的时候,它还只能用在安卓上,现在已经适配了 iOS。

它能增快构建速度的主要原因是缓存了编译结果,通过持续监视项目目录的文件变化,每次编译时只编译有改动的文件。另外一个让我很受启发的功能是 HTTP Cache Server,通过一台缓存文件服务器来保存大家的编译结果,这样只要团队里其中一人编译过的文件,其他人就不用再编译了,直接下载就行。

Buck 是个相当完备的解决方案,很多国外的大公司例如 Uber 都已经用上。他们也花了很多时间来研究,最终还是认为对他们的项目和团队来说,目前并不是很适合,主要原因是:

Buck 抛弃了 Xcode 的项目文件,需要手工编写配置文件来指定编译规则,这要对现有项目作出大幅度的调整。他们目前还在快速迭代新功能,没有余暇和人手来实施。
开发和调试的流程都得做出很大的改变。因为 Buck 接管了项目编译的过程,想调试项目不能简单地在 Xcode 里面 ⌘+R 了,得先反过来让 Buck 生成 Xcode 的项目文件。Uber 的工程师甚至推荐使用 Nuclide 来代替 Xcode 作为开发环境。虽然原理上是可行的,但是团队需要花不少时间来适应,短期内效率降低无可避免。
用 Xcode 调试代码享受不到加快编译速度的好处。虽然可以用 buck 命令启动 App,然后在命令行里启动 lldb 来调试,但那就无法使用 Xcode 的调试工具 例如 View Debugging 和 Memory Graph Debugger。

4、Bazel
Bazel 跟 Buck 很相似,是 Google 开源的,优缺点跟 Buck 都差不多,不再详细说了。

5、distcc 分布式编译
原理是把一部分需要编译的文件发送到服务器上,服务器编译完成后把编译产物传回来。他们尝试了一下比较出名的 distcc,搭建过程比较简单,最后也能成功地把编译任务分派到内网的多台服务器上。但是其他编译服务器的 CPU 占用总是很低,只有 20% 左右;也就是说分派任务的速度甚至还赶不上服务器编译的速度,分派任务然后回传编译产物这个过程所耗费的时间超过了本地直接编译。不停调整参数反复试验了很多次,最后发现编译时间完全没有变快,甚至还有点变慢了。可能以他们目前项目的规模并不适合使用分布式编译。

6、CCache 是他们最后选择的方案
大致原理就是将上次的编译产物缓存起来,在下一次编译时会检查是否命中缓存,如果命中缓存会优先取上一次的编译产物

经过在工程中的一番尝试,总结出了以下几个特点

7、最后看到了有赞的二进制方案
https://tech.youzan.com/you-zan-ji-yu-er-jin-zhi-de-bian-yi-ti-xiao-ce-lue/

感觉还是相对契合我们团队一些,不过没有开源,那我们自己搞下好了。

五、动手搞二进制插件

Cocoapods-Binary(Cocoapods 官方推荐的二进制插件)他提供了很好的思路,以及可以教会我如何整一个cocoapods插件。
万事开头难,这个插件的源码,已经可以让我开头了。
经过对业界常用方法的探索,总结出了以下三种二进制化使用的常见方案:

那结合我们团队现状,我采取多私有源的方案,不过不一样的是,打算将二进制的缓存放在本地,而非服务器中,这样可以做到不依赖服务器,而且省去了制作二进制repo的开发消耗,只需要本地消耗一些硬盘空间,这对于我们团队是非常低的成本。可以有打包机一次构建后,缓存下来的二进制文件,同步给开发同学后,即可省去第一次接入二进制的大部分时间。而且除了缓存机制外,预期还需要达到以下效果:

1、Cocoapods-Binary接入初体验

2、要改造的内容

是不是看起来好像只要改完这两点就大功告成了,确实是这样。

3、添加编译成.a的逻辑

二进制包的构建有以下方式

cocoapods-packager 是一款开源的二进制打包的 pod 插件,通过源码 podspec 生成 Podfile,pod install 生成包含对应 Pod 库的工程,之后通过 xcodebuild 去构建 .a / .framework,在看过该库的源码后发现该逻辑并不复杂,但是在尝试之后会发现几个问题:

当选择 .a 形式作为产物时,我们 podspec 中所指定的 .h 并不会被正确拷贝到目标文件夹
该组件对 Subspec 的处理较为暴力,会将多个 Subspec 合并为一个,例如我一个组件库,Phone 工程需要引用SubSpecA,Pad工程需要引用 SubSpecB,在使用该组件打包时,会将 SubSpecA 与 SubSpecB 合并为一个 framework/.a,这种情况显然不是我们所需要的,更为合理的做法是可通过配置去设置,是否将 SubSpec 进行合并或拆分
cocoapods-packager 已经停止维护,在对 Cocoapods 新特性或者 Swift 的支持上无法达到同步更新

// 构建真机架构
xcodebild -project Pods.xcodeproj -scheme xxx -configuration Release -sdk iphoneos
// 构建模拟器架构
xcodebild -project Pods.xcodeproj -scheme xxx -configuration Release -sdk iphonesimulator

再合成下我们的两个库文件

lipo -create '模拟器架构文件' '真机架构文件' -output '目标库'.a

那至此,我们编译.a的逻辑就完成了。

4、添加缓存的逻辑

这个需要在对外提供的属性添加一个变量,如

set_local_binary_cache_path '/Users/xxx/podBinaryCache'

然后根据变量设置情况,判断该组件版本是否在缓存库里,不在的话,编译完二进制后,拷贝一份,下次直接从缓存里读取该组件。
考虑到这个目录不同电脑上地址取不到,于是添加了这个属性,设置二进制默认缓存地址,开启后默认地址为:/Users/xxx/Desktop/podCache,方便打包机及团队内同学读取同一份二进制缓存地址

set_default_desktop_cache_path!  

当然因为有.a和framework的区别,将缓存添加了区分分别为library和framework。framework下再区分静态库和动态库。

5、源码调试的逻辑

这个参考了这位大兄弟的思路
https://github.com/Jacky-LinPeng/cocoapods-xlbuild

6、资源的获取

可以根据resource_paths拿到这个组件的资源目录

resources = target.resource_paths
resource_paths = resources[target_name]
resource_paths.each do |path|
  path_names = path.to_s.split('${PODS_ROOT}')
end

六、实际使用后遇到的问题

1、.a的生成要以lib开头不然会有问题,比如AFNetwroking.a要是libAFNetworking.a

2、.a以xcodebuild生成后,没有头文件,这没法用。经过一段时间的分析,找到了这个属性,这里面的东西不就是我要的头文件嘛

pod_target_header_mappings = target.header_mappings_by_file_accessor.values
image.png

3、资源文件的问题,处理资源文件需要放入组件文件夹中
1)如果组件本身包含bundle文件,直接拷贝进去
2)如果组件本身资源文件在assets中,则打包后,拷贝bundle文件放入

4、处理本地pod导入问题
本地引入组件会有Local Podspecs 需要提前在Pods/ 中生成该文件夹,然后拷贝才不会有问题

5、文件目录带有空格符号的问题
处理路径的时候加了.shellescape即可解决

七、总结

处理完那些问题后,这个二进制插件基本运行没有什么问题了,也打到了前面提到的几个预期,在团队中至今也稳定运行了半年,编译效率确实有不少提升。借鉴了不少业内大佬的经验,尝试了不少编译优化方案,学习了ruby插件的开发,解决了一些使用上遇到的问题,熟悉了iOS库文件的相关知识和cocoapods的源码,后续将分享静态库、动态库的学习经验。关于cocoapods插件的学习,大家可以看下cocoapods-binary的源码。

具体的使用方法都在这里了,好用的话可以点下⭐️ 谢谢!

https://github.com/Byte10/BinaryDemo

掘金地址:https://juejin.cn/post/7188803862612934713

八、参考文献

https://cloud.tencent.com/developer/article/1564372

https://www.dandelioncloud.cn/article/details/1498967543334379522

https://tech.youzan.com/you-zan-ji-yu-er-jin-zhi-de-bian-yi-ti-xiao-ce-lue/

https://github.com/Jacky-LinPeng/cocoapods-xlbuild

https://github.com/leavez/cocoapods-binary

上一篇 下一篇

猜你喜欢

热点阅读