1. 程式人生 > >CocoaPods元件平滑二進位制化解決方案

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。