cocoapods-binary工作原理及改进
「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战」
在iOS
开发中,如果能够对一些稳定的组件能够二进制化,那么将大大的缩减我们在开发过程中的编译时间。在基于Cocaopods工程,快速实现Swift组件二进制一文中,我们讲述了,借助Pods
工程和Shell
脚本,一步实现二进制打包,但需要我们手动更改podspec
文件,采用这种方式,如果作为依赖项
加入到其他工程中,还会出现二进制
和源码
共存的情况,今天介绍一个cocoapods
插件 cocoapods-binary
,可以实现组件预编译,该工程已经两年多没维护了,随着pods
更新,有了一些小bug
。基于源码,我对该插件做了几点更改,又可以开心的玩耍了.在了解该插件之前,我们先大概了解下,输入pod install
之后发生了什么?
Pod install
如果你想调试cocoapods
工程,可以查看之前的文章Ruby和Cocoapods文章合集 。
一图胜千言,当输入Pod install
后
- 1,首先校验是否有
Podfile
文件,并解析为Podfile
对象 - 2,准备阶段,安装插件,调用
pre_install
阶段的插件 - 3,解析依赖,通过 当前的
Podfile
文件 和 上一次的Pofile.Lock
,Manifest.Lock
文件进行比对,确认哪些文件需要更新。 - 4,下载依赖,更新需要更改的文件,并执行
Podfile
里面定义的pre_install
hook函数。 - 5,集成阶段,生成新的
Pods.xcodeproj
工程文件,并执行Podfile
里面的post_install
hook函数。 - 6,写入新的
Lockfile
信息。 - 7,执行
post_install
阶段的插件,并输出安装信息。
cocoapods-binary
是以插件的形式,在Pod
工程的pre_install
阶段进行预编译
的,
cocoapods-binary工作流
pre_install 插件
在 cocoapods-binary
中的,通过HookManager
来注册插件的执行时机
Pod::HooksManager.register('cocoapods-binary', :pre_install) do |installer_context|
end
主流程
主要流程如下图所示
- 1,必须使用
framework
的形式,也就是use_frameworks
。 - 2,在
Pods
文件夹下,创建一个名为_Prebuild
的文件夹,作为预编译沙箱
。 - 3,在当前环境下,读取
Podfile
文件,并创建Podfile
对象。 - 4,读取
Podflie.lock
文件,创建Lockfile
对象。 - 5,创建
预编译安装器
,沙箱地址为预编译沙箱
。 - 6,对比
Podfile
和Podfile.lock
,得到已更改的pod_name
。 - 7,使用
预编译安装器
将pod_name
的源代码下载到预编译沙箱
中,并生成新的Pods.xcodeproj
文件。开始编译需要更新的framework
。 - 8,回到主工程,继续执行
pod install
的后续流程,在这一步修该需要二进制文件的podspec
文件。
解析自定义参数
在插件中,有两个自定义的参数 :binary
和all_binary!
,是通过自定义DSL
来实现的,有对这一块不熟悉的,可以参考我的这篇文章Cocoapods之 Podfile文件。
创建Podfile
对象时,通过 method swizzling
来hook:parse_inhibit_warnings
方法,拿到我们在Podfile
文件中写入的配置选项。将需要预编译
的pod
,保存到数组中。
old_method = instance_method(:parse_inhibit_warnings)
define_method(:parse_inhibit_warnings) do |name, requirements|
variables = requirements
parse_prebuild_framework(name, requirements)
old_method.bind(self).(name, variables)
end
复制代码
在Ruby
中,Method Swizzling
主要分为三步:
- 1,获取
parse_inhibit_warnings
实例方法。 - 2,定义一个相同名字的方法。
- 3,调用原来的方法。
对比Lockfile
在这里 Podfle.lock
和预编译沙箱
中Manifest.lock
是一样的,通过对比可以一个Hash
对象
<Pod::Installer::Analyzer::SpecsState:0x00007f83370c61a8 @added=#<Set: {}>, @deleted=#<Set: {}>, @changed=#<Set: {}>, @unchanged=#<Set: {"Alamofire", "SnapKit"}>>
可以很清楚的知道哪些pod
库发生了更改。如果有改动,则就在预编译沙箱
进行install
。
binary_installer.install!
在这一阶段,主要是在预编译沙箱
中拉取framework源码
和修改Pods.xcodeproj
文件,在 Manifest.lock
成功写入预编译沙箱
,通过hook run_plugins_post_install_hooks
函数,在预编译沙箱
中,使用 xcodebuild
命令,编译每一个需要更新的pod_target
,并将编译好的framework
放至GeneratedFrameworks
目录下。
回到主工程执行pod install
编译完成后,就回到了主工程里面的 Pod install
流程中。对:resolve_dependencies
方法进行Method Swizzling
,对需要更改的pod_target
进行修改。通过修改内存中的Pod::Specification
对象的vendored_frameworks
、source_files
、resource_bundles
和resources
属性,来引用已经编译好的framework
。
工作流总结
通过对每一个阶段的了解,我们了解了作者的思路是这样的:
1,先将源码和Pods.project
安装到预编译沙箱
中 。
2,借助于Pods.project
工程,使用xcodebuild
编译需要预编译的scheme
。
3,巧妙的利用Method Swizzling
,在分析依赖阶段,修改Pod::Specification
对象,完成二进制的引用等工作。
现有问题
1,:binary => true
无效
在 ruby 2.6.8p205 (2021-07-07 revision 67951) [universal.x86_64-darwin21]
版本中,使用:binary => true
无效,无法编译为framework
。在D ebug
模式下生效,在发布
后就失效了。
def set_prebuild_for_pod(pod_name, should_prebuild)
Pod::UI.puts("pod_name: #{pod_name} prebuild:#{should_prebuild}")
if should_prebuild == true
@prebuild_framework_pod_names ||= []
@prebuild_framework_pod_names.push pod_name
else
@should_not_prebuild_framework_pod_names ||= []
@should_not_prebuild_framework_pod_names.push pod_name
end
end
在ruby 2.6.8
中,release
模式下,执行了两次,参数还不一致,导致 @prebuild_framework_pod_names
和@should_not_prebuild_framework_pod_names
相等,最终需要预编译的数组为[]
pod_name: SnapKit, should_prebuild:true
pod_name: SnapKit, should_prebuild:
2,pod update
没有及时更新最新的framework
在frameworkA
依赖frameworkB
,在 Podfile
中,只引入了 frameworkA
target xxx do
pod "frameworkA", :binary => true
end
当 frameworkB
有新版本时,没有更新最新的frameworkB
,对于我们自己的组件,我们期望有新版本发布时,能及时更新。
解决办法:
在检测更新的方法中,读取最新的Manifest.lock
文件,读取已编译的framework
的plist
文件,比较两个版本号是否一致
,不一致则重新编译。
cocoapods-binary-bel
为了能充分利用该插件,我根据实际产生的问题,对源码进行了一些修改,增加了 plist
版本校验。使用方式和cocoapods-binary
一致。
源码github链接cocoapods-binary-bel
安装
sudo gem install cocoapods-binary-bel
流程图
在 cocoapods-binary
的基础上增加了版本校验工程,总的流程图如下所示:
一键转源码
1,:binary => false
,指定某一个framework
为源码
2, 新增 --hsource
选项,输入 pod install --hsource
,可以将所有的framework
转为源码。
去除了依赖分析
在实际运用的工程中,如果 A
依赖 B
和C
, B
又依赖D
,如果 A
需要预编译,那么 B
、C
和 D
都需要重新编译,实际上此时B
、C
、D
已经有了已经编译好的版本,无需重新编译。在Podfile
指明 B
、C
、D
即可。
pod "A"
pod "B"
pod "C"
pod "D"
增加静态库的resource处理
在 cocoapods中,如果使用 resource_bundle
处理资源文件,会生成一个相对应的target
来处理资源文件,如果是动态库,会在动态库
的Target
,添加资源工程依赖,使用 xcodebuild
命令制作二进制会将bundle
文件编译至xxx.framework
目录下,使用 resources
同样也会将资源文件编译至xxx.framework
目录下。
对于静态库
而言,如果是使用resource_bundle
,也同样生成一个会生成一个相对应的target
来处理资源文件,但对资源文件的拷贝,是由主工程做的,无需静态库工程处理, 如果使用 resources
,则需要将资源文件从源码中,拷贝到 xxx.framework
下,在主工程编译时,由主工程处理即可。
作者:Bel李玉
链接:https://juejin.cn/post/7035628418972516360