前幾天 Cocoapods 1.7.0 正式版發(fā)布了,我最期待的一個(gè)功能是 Multiple Pod Projects,昨天順手就給接入了,項(xiàng)目解析和索引效率有了非常明顯的提升,過(guò)程中踩了些坑,這次一起把之前 debug 的經(jīng)驗(yàn)分享一下。
generate_multiple_pod_projects 選項(xiàng)
之前 Cocoapods 會(huì)把每個(gè)依賴作為 target 放到 Pods 項(xiàng)目里,但 xcodeproj 本身的編碼不太能適應(yīng)這種情況,在引入幾十個(gè) pod 的情況下,項(xiàng)目解析的效率會(huì)急劇下降。
以我公司其中一個(gè)主項(xiàng)目為例,Pods 項(xiàng)目的大小達(dá)到了 5.2 MB(這可都是純文本),在第一次打開(kāi)項(xiàng)目,解析項(xiàng)目構(gòu)建索引時(shí),就能明顯聽(tīng)到風(fēng)扇開(kāi)始狂轉(zhuǎn),這個(gè)過(guò)程會(huì)持續(xù)好幾分鐘才會(huì)結(jié)束。
Cocoapods 這次更新引入了一個(gè) generate_multiple_pod_projects 的選項(xiàng),可以讓每個(gè)依賴都作為一個(gè)單獨(dú)的項(xiàng)目引入,大大增加了解析速度:

開(kāi)啟的方式很簡(jiǎn)單,只要在 Podfile 里加入這一行就可以了:
install! 'cocoapods', generate_multiple_pod_projects: true
拆分后每個(gè)項(xiàng)目的大小都差不多是 40 - 100 kb 左右:

這個(gè)選項(xiàng)開(kāi)啟之后的效果非常顯著,我在 Xcode 里執(zhí)行了 clean,之后 indexing 的過(guò)程在幾秒鐘里就結(jié)束了,而且風(fēng)扇也沒(méi)有狂轉(zhuǎn)。
至于為什么這樣可以提升項(xiàng)目的解析速度,我大概看了一下 xcodeproj 的編碼,所有的 Item 都會(huì)按照類別存放到各自的 section 里,最終在項(xiàng)目的結(jié)構(gòu)樹(shù)里會(huì)以引用的形式呈現(xiàn)。
所以文件引用查找的范圍是所有 Pod 引用庫(kù)的文件的集合,而每次索引的構(gòu)建都至少會(huì)遍歷一次項(xiàng)目樹(shù),這就會(huì)導(dǎo)致索引時(shí)間的暴增,除此之外單個(gè)龐大的項(xiàng)目解析也不利于多線程執(zhí)行,拆分成多個(gè)項(xiàng)目的話就能有效地解決這些問(wèn)題。
install! 函數(shù)只能調(diào)用一次
需要注意 install! 是個(gè)用來(lái)配置的函數(shù),由于之前我還開(kāi)啟了另一個(gè)選項(xiàng),所以接入時(shí)是這么做的:
install! 'cocoapods', generate_multiple_pod_projects: true
install! 'cocoapods', disable_input_output_paths: true
但是這么做之后發(fā)現(xiàn)不生效,后來(lái)才想起來(lái) install! 是一個(gè)用來(lái)配置的函數(shù),重復(fù)調(diào)用的話,只會(huì)以最后一次的調(diào)用為準(zhǔn)。所以應(yīng)該在一次調(diào)用里把它們都傳入進(jìn)去:
install! 'cocoapods',
disable_input_output_paths: true,
generate_multiple_pod_projects: true
Swift 版本控制
另一個(gè)坑就是在 post_install 時(shí),為了一些版本的兼容,需要遍歷所有 target,調(diào)整一部分庫(kù)的 Swift 版本:
post_install do |installer|
swift_4_0_compatible = [ ... ]
swift_4_2_compatible = [ ... ]
installer.pod_targets.each do |t|
t.build_configurations.each do |c|
c.build_settings['SWIFT_VERSION'] = '4.0' if swift_4_0_compatible.include? t.name
c.build_settings['SWIFT_VERSION'] = '4.2' if swift_4_2_compatible.include? t.name
end
end
end
但是如果開(kāi)啟了 generate_multiple_pod_projects 的話,由于項(xiàng)目結(jié)構(gòu)的變化,installer.pod_targets 就沒(méi)辦法獲得所有 pods 引入的 target 了。
Podfile 里的代碼如何 debug
查了 Xcodeproj 和 Cocoapods 的文檔之后我都沒(méi)有得到很好的解答,所以我就想用 xcodeproj 本身的接口去處理這件事情。
由于 Podfile 本質(zhì)上是 Ruby 腳本,所以這里我通常會(huì)使用 Ruby 的 debugger 去操作,通過(guò) Ruby 強(qiáng)大的自省能力,在 debugger 里進(jìn)行嘗試然后找到我們需要的接口,開(kāi)始之前我們需要安裝一個(gè) Ruby 的工具,步驟如下:
- 首先是安裝 debugger gem install pry
- 接著在 Podfile 的開(kāi)頭導(dǎo)入 require 'pry'
- 然后在我們想要插入斷點(diǎn)的地方插入 binding.pry 語(yǔ)句就可以了
查找能用的接口
我在 post_install 里插入了斷點(diǎn),接著運(yùn)行 pod install,就看到斷點(diǎn)生效了:

Ruby 的自省能力非常強(qiáng)大,而且 pry 也基于此做了很多實(shí)用的功能,在這里我直接輸入了 installer 回車,就能看到它所有屬性都被遞歸打印出來(lái)。
這里面我找了一下之后,發(fā)現(xiàn)一個(gè)文檔里沒(méi)有記錄的屬性,叫做 pod_target_subprojects,包含了所有 Pods 的項(xiàng)目,似乎可以滿足我們的需求:

接著 Ctrl + d 退出 pry,回到 Podfile 修改即可:
post_install do |installer|
swift_4_0_compatible = [ ... ]
swift_4_2_compatible = [ ... ]
installer.pod_target_subprojects.flat_map { |p| p.targets }.each do |t|
t.build_configurations.each do |c|
c.build_settings['SWIFT_VERSION'] = '4.0' if swift_4_0_compatible.include? t.name
c.build_settings['SWIFT_VERSION'] = '4.2' if swift_4_2_compatible.include? t.name
end
end
end
最后 pod install 一下,打開(kāi) Xcode 查看對(duì)應(yīng)的 target 的編譯設(shè)置,確實(shí)有效。
這里介紹的 debug 方法在 fastlane 里也適用,非常建議大家在編寫復(fù)雜腳本時(shí)先用 debugger 去提前踩坑。
結(jié)語(yǔ)
用慣了 Ruby 的 debug 方式之后,回到 LLDB 感覺(jué)開(kāi)發(fā)體驗(yàn)瞬間差了很多??。