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

本文主要内容:
一、背景
二、效果展示
三、接入插件及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 是他们最后选择的方案
大致原理就是将上次的编译产物缓存起来,在下一次编译时会检查是否命中缓存,如果命中缓存会优先取上一次的编译产物

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

  • CCache 确实能够很大程度上提高编译速度
  • CCache 的缓存命中率相对稳定
  • CCache 不支持 PCH 文件
  • CCache 不支持 Clang-Moudle 的方式
  • CCache 目前不支持 Swift

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

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

五、动手搞二进制插件

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

  • 即时生成二进制包并缓存

    类 Carthage 的实现思路,以 Cocoapods-Binary(Cocoapods 官方推荐的二进制插件),在 pod install 后,对本次编译,即时生成二进制包并缓存,缺点是在没有对应二进制包版本时,pod install 后会额外去做二进制包的生成,一定程度上会影响 pod install的速度,并且如果开发者切回源码调试,二进制缓存会一并清空

  • 单私有源,PodSpec 包含源码及二进制信息

    单私有源指的是 Pod 库均包含在同一个 Repo 内,二进制的 vendor_libraries / vendor_frameworks 等信息会存在于各个 Pod库对于 PodSpec 的 SubSpec 中,在 Podfile 中读取二进制相关配置去决定是否使用二进制SubSpec

    缺点是源码与二进制并存与一处,不仅会让 PodSpec 显得臃肿,并且会增大 Source 源的体积,降低 Pod 库的 Download 速度以及 Lint 速度,以及多 SubSpec 的模式也会影响最终生成 xcworkspace 的速度

  • 多私有源

    多私有源指的是源码与二进制分别独立,使用两个不同的 Source,二进制文件一般压缩存于静态服务器中,以空间去换取时间效率,同时存在的问题是,Source 之间的切换问题,二进制包以及 Spec 的生成时机问题

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

  • 开发同学无需额外改动组件
  • 开发同学接入无需太多成本
  • 支持源码切换和调试
  • 支持部分组件不用二进制
  • 二进制一次编译后续直接从缓存读取

1、Cocoapods-Binary接入初体验

  • pod install 后进行做二进制包的生成,但是只支持framework
  • 开发者切回源码调试,二进制缓存会一并清空,重新编译
  • 由于只支持framework,引入方式变更为 #import <aaa/aaa.h>,这个对于我们团队大部分是以#import "aaa.h"会是个很大的改造量

2、要改造的内容

  • 二进制包生成后,添加拷贝到缓存的逻辑
  • 除了framework还支持.a,就可以解决头文件引用的问题,目前的项目就不需要大量时间成本进行头文件引用改造

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

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

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

  • cocoapods-packager 插件

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即可
// 构建真机架构
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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容