CocoaPods元件平滑二進位制化解決方案
什麼是元件二進位制化?
在iOS開發中,事實標準是我們使用CocoaPods生成、管理和使用library。這裡的library就是一個模組、元件或庫。二進位制化指的是通過編譯把元件的原始碼轉換成靜態庫或動態庫,以提高該元件在App專案中的編譯速度。
我們的方案是轉換成靜態庫,也就是.a格式的檔案加上暴露出來的標頭檔案。
為什麼我們需要二進位制化呢?
在我們App開發中,我們逐漸的抽象了很多模組、業務、UI等把他轉換成私有CocoaPod庫。其中有一個是用C++和Objective-C混寫的,原始碼格式為.mm。在app專案編譯時.mm部分程式碼編譯非常慢。這作為一個契機讓我們去考慮如何加快編譯速度。
這個混寫的CocoaPod庫叫做YTXChart,之後會以此庫為例反覆提到。
另外隨著業務的擴充套件,私有CocoaPod庫和第三方CocoaPod庫越來越多,App專案中的檔案也越來越多。每次pod install安裝新庫或pod update更新庫的時候,重新編譯的過程需要等待很長時間。這也向我們提出了加快編譯速度的需求。
另外如果想要做元件化的話,一定要做二進位制化。
所以我們想到了二進位制化的方案來解決這個問題,並且很多大公司也是這麼做的。
這帶來一個新問題:一步就位還是平滑過度
對我們來說,這是一個嘗試,不可能開始就決定把所有的私有CocoaPod庫二進位制化,也不可能決定把所有第三方CocoaPod庫二進位制化。當務之急的情況是加快YTXChart庫編譯速度。所以必須找到一個方案平滑過度。
我們的App中的podflie是這樣的
(點選放大影象)
(點選放大影象)
平滑二進位制方案需求點
-
其他的CocoaPod庫都還是原始碼。YTXChart為二進位制化。
-
以後能夠逐步迭代把更多的以YTX開頭的CocoaPod庫進行二進位制化,而不影響主App。
-
能夠提供一種方式把二進位制化CocoaPod庫切換回原始碼CocoaPod庫以便除錯。儘量做的方便。
-
解決YTXChart引用依賴的問題。(YTXChart還依賴了第三方AFNetworking和私有YTXServerId。保證生成的靜態庫中不會含有AFNetworking的內容和YTXServerId的內容並且能夠編譯通過)
-
利用原來的YTXChart.git,不建立新專案,不建立新的git庫。因為我們的二進位制化庫的生成還是來自於原始碼,當原始碼更新時,我們需要一種非常快捷的方式去生成二進位制的東西,不希望copy原始碼到某處,或者增加一個git submodule。
-
希望App原始碼和YTXChart中的原始碼儘量少或者沒有改動。
-
希望App中的Podfile儘量少或者沒有改動。
-
希望Podfile中的版本號保持風格一致,不會出現'~> 2.2.1.binary'這種情況。
-
用原來的那一個CocoaPods Repo Spec。
以下這個解決方案的教程滿足了以上所有需求點
注意,以下的例子基於[email protected],而且目前只能是1.0.1
第一步:原始碼生成靜態庫
如果你是通過命令pod lipo create建立的CocoaPod庫並且pod install的話,它的目錄結構應該像這樣子(只列出重要的):
YTXChart |-Example |-YTXChart |-Pods |-YTXChart.xcodeproj |-YTXChart.xcworkspace |-Podfile \-Podfile.lock |-Pod |-Assets \-Classes \-YTXChart.podspec
在xcode中建立新Target:
YTXChartBinaryFile->New->Target->Framework & Library->Cocoa Touch Static Library
如果你們的專案最低支援到iOS8可以建立Dynamic Framework
注意在Podfile中加入以下這段
target 'YTXChartBinary' doend
然後pod install
解釋:[email protected]會在Header Search Path自動加入內容。如果你用[email protected]則需要自己加Header Search Path保證依賴庫YTXServerId和AFNetwork能夠被找到。如圖:
(點選放大影象)
然後把Pod/Classes中的原始碼拖入到YTXChartBinary中,這樣選擇(這樣會link原始碼而不是複製):
(點選放大影象)
然後變成這樣子:
(點選放大影象)
Headers需要自己加,裡面是你需要暴露的標頭檔案
在YTXChartBinary Target中的Build Settings下找到iOS Deployment Target選擇和YTXChart.podspec中的s.platform保持一致。這裡是7.0:
YTXChartBinary Target->Build Settings->iOS Deployment Target
在根目錄建立shell指令碼buildbinary.sh
你也可以建立一個Aggregate Target用來執行shell指令碼
程式碼如下:
(點選放大影象)
這個指令碼寫的並不是很好。說說主要做了什麼。 Release不同的靜態庫,真機和模擬器的。只構建x86_64,不構建i386加快速度
xcodebuild -configuration "Release" -workspace "${PROJECT_NAME}. xcworkspace" -scheme "${BINARY_NAME}" -sdk iphoneos clean build CONFIGURATION_BUILD_DIR="${WRK_DIR}/${RE_OS}" LIBRARY_SEARCH_PATHS ="./Pods/build/${RE_OS}" xcodebuild ARCHS=x86_64 ONLY_ACTIVE_ARCH=NO -configuration "Release" -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${BINARY_NAME}" -sdk iphonesimulator clean build CONFIGURATION_BUILD_DIR="${WRK_DIR}/${RE_SIMULATOR}" LIBRARY_SEARCH_PATHS="./Pods/build/${RE_SIMULATOR}"
*通過lipo命令合併。新.a使用project name是因為要和App專案的OTHER_LDFLAGS相容-l"YTXChart"
lipo -create "${DEVICE_DIR}" "${SIMULATOR_DIR}" -output "${INSTALL_LIB_DIR}/lib${PROJECT_NAME}.a"
結果:
(點選放大影象)
為什麼要刪除i386
實際上,二進位制化方案就是以空間換時間。我們這個YTXChart庫生成的.a去除i386之後大小有166.3M。上傳到git倉庫後,git會壓縮。增加了33M左右。而作為二進位制檔案,git是沒法做增量的。所以每次上傳.a都會大大增加git庫大小,增加硬碟使用量。考慮到我們的伺服器硬碟只有60個G能用,以後還會二進位制化很多元件。
所以得出:儘量壓縮二進位制檔案大小;儘量不上傳.a,直到釋出某個版本時才上傳。
當然,如果你的伺服器硬碟是1T的話,我覺得你也可以隨便搞。
現在檔案目錄是這樣子的:
YTXChart |-Example |-YTXChart |-Pods |-YTXChart.xcodeproj |-YTXChart.xcworkspace |-YTXChartBinary //空的 |-Podfile \-Podfile.lock |-Pod |-Assets |-Classes //裡面是原始碼 \-Products |-include |-xxx.h |-... \-xxx.h \-lib \- libYTXChartBinary.a \-YTXChart.podspec
第二步:測試生成的靜態庫
修改YTXChart.podspec如下:
(點選放大影象)
注意s.sourcefiles和s.publicheaderfiles和s.ios.vendoredlibraries的路徑
Exampl/Podfile是長這樣子的:
(點選放大影象)
執行pod install後應該是這樣子的,然後跑起來沒問題
(點選放大影象)
執行pod lib lint --sources='http://gitlab.baidao.com/ios/ytx-pod-specs.git,master'--verbose --use-libraries --fail-fast也是好的
至此我們構建出一個靜態庫,只包含YTXChart的內容,不包含依賴AFNetwork和YTXServerId的內容
證明:把s.dependency 'AFNetworking', '~> 2.0'去除再執行pod lib lint 'http://gitlab.baidao.com/ios/ytx-pod-specs.git,master' --verbose --use-libraries --fail-fast會報出找不到AFNetwork相關檔案。
題外話:因為CocoaPods1.0.1不支援C++專案的lint(這是一個defect),所以這個時候我會切回[email protected]來lint和publish。而前面pod instal增加Search Path是依靠[email protected]。如果你不是.mm混寫的,是不會有這個問題的。儘管使用[email protected]。強行當作沒看到這個題外話。
下一步解決如何在原始碼和二進位制中切換 修改YTXChart.podspec為以下內容:
(點選放大影象)
注意這段if ENV['IS_SOURCE']。我們的需求是優先使用二進位制,偶爾才會切回原始碼。
刪除Example/Pods目錄。
執行IS_SOURCE=1 pod install。你會看到Example/Pods/YTXChart/裡面都是原始碼
輸出Notice:YTXChart Now is source
進一步跑起模擬器,因為是原始碼編譯用了很長時間,模擬器起來,一切也是好的
再試下pod cache clean --all && IS_SOURCE=1 pod lib lint也是好的
再試下pod cache clean --all && pod lib lint也是好的
現在我們通過if else簡單地實現了本地Example App專案切換原始碼和二進位制。
釋出到自己的pod repo spec
釋出就和正常釋出沒有任何區別。
檢查從spec repo的source中安裝
Podfile修改為pod 'YTXChart', '~> 0.17.7'
以下兩步很重要
pod cache clean --all
刪除Example/Pods
然後pod install
檢查Example/Pods/YTXServerId/和Example/Pods/AFNetwork/發現都是.h .m原始碼。
檢查Example/Pods/YTXChart/裡的是二進位制.a和標頭檔案。跑起App並沒有問題。
嘗試切回原始碼
如果你直接IS_SOURCE=1 pod install你會發現Example/Pods/YTXChart/裡的內容都變成了空。
這是為什麼呢,因為pod cache了一個podspec.json。可以通過pod cache list檢視。他cache了一個描述如何從s.source中找到相關檔案。現在的描述還是從Pod/Products/下去找,自然為空。
為了避免這個問題,所以必須執行上面兩步。這個是唯一的問題,目前我還找不到更好的解決方案。切換的行為只是偶爾發生,這是可以接受的。
執行2步。再次IS_SOURCE=1 pod install你就發現Example/Pods/YTXChart/裡的內容都變成了.h .mm原始碼。跑起App也是好的。
為什麼lint之前要cache clean。原理是一樣的。如果YTXChart依賴的YTXServerId也被做成了二進位制化就需要cache clean。不過你也可以這樣pod cache clean YTXServerId
特別注意IS_SOURCE應當作為一個所有非二進位制化Pod庫的統一標識,並且通知你們的專案組裡所有成員。pod install可能會有某幾個已經二進位制化的庫使用二進位制的內容。IS_SOURCE=1 pod install時,所有的庫都將會是原始碼的內容。
版本管理
請參考這篇我的文章CocoaPod版本規範:
http://www.yiqixiabigao.com/2016/07/14/yin-tian-xia-cocoapodshi-yong-gui-fan/
完整分析
當你釋出完成之後,檢視。我們發現在Spec Repo中對應版本的podspec就是我們的YTXChart/podspec。 CocoaPod從s.sourcegit地址和tag下載對應的程式碼,Pod/Products和Pod/Classes裡的內容都存在 當你使用IS_SOURCE=1時ENV['IS_SOURCE']會為true。CocoaPods通過s.source_files從下載程式碼的路徑找到原始碼構建Example/Pods和YTXChart.xcworkspace
(點選放大影象)
明白了上面的過程,來再分析下為什麼要在切換原始碼和二進位制化時刪除cache和Pods目錄。放幾張圖就明白了
(點選放大影象)
(點選放大影象)
刪除cache和Pods目錄。IS_SOURCE=1 pod install觀察json。
(點選放大影象)
總結:
-
沒有使用submodule或新的git倉庫來構建出一個不包含依賴內容的靜態庫。一份原來的git倉庫。
-
沒有因為構建二進位制庫而需要增加冗餘的原始碼。所以當你修改Pod/Classes中的原始碼,可以方便簡單地執行buildbinary.sh指令碼來構建出靜態庫。一份原始碼。
-
共用了一份YTXChart.podspec
-
沒有大量修改YTXChart.podspec
-
使用pod lib lint和IS_SOURCE=1 pod lib lint檢查通過。
-
沒有修改Podfile。這個Example的Podflie只是測試需要才改的。主App專案中的Podfile可以一行都不改。不會出現'~> 2.2.1.binary'。
-
App中的原始碼不會因為使用了二進位制CocoaPods元件而做任何修改。
-
沒有手動配置Search Path,這樣更容易。
-
在Example App中可以通過IS_SOURCE靈活地切換原始碼和二進位制靜態庫。唯一一個問題每次切換要刪除Pods目錄和pod cache clean --all
-
跑起Example App總是好的。
-
沒有影響到其他庫,我可以逐步平滑地把YTXXXX一個一個做成二進位制。
下一步目標
逐步平滑地把YTXXXX一個一個做成二進位制;
進一步的把第三方如AFNetwork在私有spec repo中做份映象也提供二進位制化;
把Podfile中絕大部分元件都做成二進位制(RN這種本地安裝模式和有sub spec的庫目前不打算二進位制化)。
關於資原始檔,資原始檔在二進位制化中的配置是一樣的。
另外,使用二進位制化的CocoaPods庫不會增加ipa的大小。所以我們應當優先用二進位制化的東西,這可以加快Archive速度。
關於有sub spec的CocoaPods元件
兩個方案:
-
提供一個全集
-
對每一個sub spec都做份二進位制並保持它們之間依賴的相互關係
走過的彎路!!!
現在這個解決方案看起來簡單,但在當初的探索過程中並不是那麼順利。以下是不成功的嘗試!
建立另一個YTXChartBinary.podspec
-
把生成的Products目錄放到YTXChartBinary下
-
把YTXChartBinary.podspec目錄放到YTXChartBinary下
-
Podfile中通過增加Binary欄位安裝二進位制化如pod 'YTXChartBinary', '~> 0.17.7'
問題
-
要維護2個podspec。版本號很可能不統一。
-
當pod spec lint報錯:找不到相關檔案。
-
Podfile中通過增加Binary欄位來切換,非常不方便。
-
要改App原始碼。當安裝二進位制的時候<YTXChart/YTXChart.h>需要改成<YTXChartBinary/YTXChart.h>。來回切換都需要改,極不方便。
-
要改App原始碼。這次一勞永逸。直接這樣使用"YTXChart.h"。但這樣也不好。
建立另一個專門放二進位制化的Spec Repo,通過不同的Source來區分
解決了要改App原始碼的問題。只需要在Podfile中加個source。
不同的source例子:
source 'http://gitlab.baidao.com/ios/ytx-binary-pod-specs.git'
source 'http://gitlab.baidao.com/ios/ytx-pod-specs.git'
問題
-
釋出兩次,lint兩次。
-
建立了2個Spec Repo。