你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> iOS CocoaPods組件平滑二進制化解決方案及詳細教程

iOS CocoaPods組件平滑二進制化解決方案及詳細教程

編輯:IOS開發基礎

winter-road-arrow-74780-medium.jpeg

什麼是組件二進制化?

在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是這樣的

target 'jryMobile' do  
    pod 'AFNetworking', '~> 2.6.3'
    pod 'Mantle', '~> 1.5.7'
    pod 'DateTools', '~> 1.7.0'
    pod 'ReactiveCocoa', '~> 2.3.1'
    pod 'CocoaAsyncSocket', '~> 7.4.1'
    pod 'FMDB', '~> 2.5'
    pod 'MWPhotoBrowser', '~> 1.4.1'
    pod 'MZFormSheetController', '~> 2.3.6'
    pod 'HMSegmentedControl', '~> 1.5.1'
    pod 'UMengAnalytics', '~> 3.5.8'
    pod 'UMengFeedback', '~> 2.3.4'
    pod 'TSMessagesNW', '~> 0.9.15'
    pod 'TPKeyboardAvoiding', '~> 1.2.9'
    pod 'SDWebImage', '~> 3.7'
    pod 'JHChainableAnimations', '~> 1.3.0' 
    pod 'BarrageRenderer', '~> 1.7.0'
    pod 'MJRefresh', '~> 3.1.7'
    pod 'YTXAnimations', '~> 1.2.4', :subspecs => ["AnimateCSS", "Transformer"]
    pod 'YTXMediaIJKPlayer', '~> 0.2.1'

    pod 'YTXTradeBusinessType', '~> 1.1.0'
    pod 'YTXServerId', '~> 0.1.4'
    pod 'YTXUtilCategory','~> 1.2.0'
    pod 'YTXScreenShotManager', '~> 0.1.7'
    pod 'YTXRequest', '~> 1.0.0'
    pod 'YTXCommonSocket', '~> 0.1.9'
    pod 'YTXChartSocket', '~> 0.5.1'
# 希望是二進制化的
    pod 'YTXChart', '~> 0.17.0'

    pod 'YTXRestfulModel', '~> 1.2.2', :subspecs => ["RACSupport", "YTXRequestRemoteSync", "FMDBSync", "UserDefaultStorageSync"]
    pod 'YTXWebViewJavaScriptBridge', '~> 0.1.2'
    pod 'YTXCheckForAppUpdates', '~> 1.0.0'
    #    pod 'YTXVideoAVPlayer', '~> 0.5.0'
    pod 'YTXChatUI', '~> 0.3.2'
    pod 'PNChart', '~>0.8.9'

    #pod 'EaseMobSDKFull', :git => 'https://github.com/easemob/sdk-ios-cocoapods-integration.git', :tag => '2.2.0'
    # EaseMobSDKFull 更新地址'https://github.com/easemob/sdk-ios-cocoapods-integration.git'
    #pod 'AFgzipRequestSerializer', '~> 0.0.2'

    pod 'AdhocSDK', '~> 2.2.1'

    pod 'FLEX', '~> 2.0', :configurations => ['Debug']

    pod 'React', :path => './ReactComponent/node_modules/react-native', :subspecs => [
    'Core',
    'RCTImage',
    'RCTNetwork',
    'RCTText',
    'RCTWebSocket',
    # 添加其他你想在工程中使用的依賴。
    ]

    pod 'CodePush', :path => './ReactComponent/node_modules/react-native-code-push'
end

平滑二進制方案需求點

  • 其他的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' do

end

然後pod install

解釋:[email protected]會在Header Search Path自動加入內容。如果你用[email protected]則需要自己加Header Search Path保證依賴庫YTXServerId和AFNetwork能夠被找到。如圖:

headersearchpath

然後把Pod/Classes中的源碼拖入到YTXChartBinary中,這樣選擇(這樣會link源碼而不是復制):

default

然後變成這樣子:

default

Headers需要自己加,裡面是你需要暴露的頭文件

在YTXChartBinary Target中的Build Settings下找到iOS Deployment Target選擇和YTXChart.podspec中的s.platform保持一致。這裡是7.0YTXChartBinary Target->Build Settings->iOS Deployment Target

在根目錄創建shell腳本buildbinary.sh

你也可以創建一個Aggregate Target用來執行shell腳本

代碼如下:

PROJECT_NAME="YTXChart"

BINARY_NAME="${PROJECT_NAME}Binary"

cd Example

INSTALL_DIR=$PWD/../Pod/Products  
rm -fr "${INSTALL_DIR}"  
mkdir $INSTALL_DIR  
WRK_DIR=build

BUILD_PATH=${WRK_DIR}

DEVICE_INCLUDE_DIR=${BUILD_PATH}/Release-iphoneos/usr/local/include  
DEVICE_DIR=${BUILD_PATH}/Release-iphoneos/lib${BINARY_NAME}.a  
SIMULATOR_DIR=${BUILD_PATH}/Release-iphonesimulator/lib${BINARY_NAME}.a  
RE_OS="Release-iphoneos"  
RE_SIMULATOR="Release-iphonesimulator"

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}"

if [ -d "${INSTALL_DIR}" ]  
then  
rm -rf "${INSTALL_DIR}"  
fi  
mkdir -p "${INSTALL_DIR}"

cp -rp "${DEVICE_INCLUDE_DIR}" "${INSTALL_DIR}/"

INSTALL_LIB_DIR=${INSTALL_DIR}/lib  
mkdir -p "${INSTALL_LIB_DIR}"

lipo -create "${DEVICE_DIR}" "${SIMULATOR_DIR}" -output "${INSTALL_LIB_DIR}/lib${BINARY_NAME}.a"  
lipo -remove i386 "${INSTALL_LIB_DIR}/lib${BINARY_NAME}.a" -output "${INSTALL_LIB_DIR}/lib${PROJECT_NAME}.a"  
rm -r "${WRK_DIR}"

這個腳本寫的並不是很好。說說主要做了什麼。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"

結果:

default

為什麼要刪除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如下:

Pod::Spec.new do |s|  
  s.name             = "YTXChart"
  s.version          = "0.17.7"
  s.summary          = "YTXChart for pod"

# This description is used to generate tags and improve search results.
#   * Think: What does it do? Why did you write it? What is the focus?
#   * Try to keep it short, snappy and to the point.
#   * Write the description between the DESC delimiters below.
#   * Finally, don't worry about the indent, CocoaPods strips it!
  s.description      = "銀天下Chart, 依賴AFNetworking"

  s.homepage         = "http://gitlab.baidao.com/ios/YTXChart.git"
  # s.screenshots     = "www.example.com/screenshots_1", "www.example.com/screenshots_2"
  s.license          = 'MIT'
  s.author           = { "caojun-mac" => "[email protected]" }
  s.source           = { :git => "http://gitlab.baidao.com/ios/YTXChart.git", :tag => s.version }
  # s.social_media_url = 'https://twitter.com/'

  s.platform     = :ios, '7.0'
  s.requires_arc = true


  s.source_files = 'Pod/Products/include/**'
  s.public_header_files = 'Pod/Products/include/*.h'
  s.ios.vendored_libraries = 'Pod/Products/lib/libYTXChart.a'


  s.libraries = 'sqlite3', 'c++'

  s.dependency 'YTXServerId'
  s.dependency 'AFNetworking', '~> 2.0'
end

注意s.sourcefiles和s.publicheaderfiles和s.ios.vendoredlibraries的路徑

Exampl/Podfile是長這樣子的:

source 'http://gitlab.baidao.com/ios/ytx-pod-specs.git'  
source 'https://github.com/CocoaPods/Specs.git'

target 'YTXChart_Example' do  
  pod "YTXChart", :path => "../"
  pod 'ReactiveCocoa', '~> 2.5'
  pod 'YTXChartSocket'
  pod 'AFNetworking', '~> 2.0'
end

target 'YTXChartBinary' do

end

target 'YTXChart_Tests' do  
  pod "YTXChart", :path => "../"
  pod 'Kiwi'
end

執行pod install後應該是這樣子的,然後跑起來沒問題

staticbuildresult

執行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為以下內容:

#
# Be sure to run `pod lib lint YTXChart.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#

Pod::Spec.new do |s|  
  s.name             = "YTXChart"
  s.version          = "0.17.7"
  s.summary          = "YTXChart for pod"

# This description is used to generate tags and improve search results.
#   * Think: What does it do? Why did you write it? What is the focus?
#   * Try to keep it short, snappy and to the point.
#   * Write the description between the DESC delimiters below.
#   * Finally, don't worry about the indent, CocoaPods strips it!
  s.description      = "銀天下Chart, 依賴AFNetworking"

  s.homepage         = "http://gitlab.baidao.com/ios/YTXChart.git"
  # s.screenshots     = "www.example.com/screenshots_1", "www.example.com/screenshots_2"
  s.license          = 'MIT'
  s.author           = { "caojun-mac" => "[email protected]" }
  s.source           = { :git => "http://gitlab.baidao.com/ios/YTXChart.git", :tag => s.version }
  # s.social_media_url = 'https://twitter.com/'

  s.platform     = :ios, '7.0'
  s.requires_arc = true


 if ENV['IS_SOURCE']
    puts '-------------------------------------------------------------------'
    puts 'Notice:YTXChart is source now'
    puts '-------------------------------------------------------------------'
      s.source_files  = "Pod/Classes/painter/*.{h,m,mm}", "Pod/Classes/painterview/*.{h,m,mm}", "Pod/Classes/chart/*.{h,m,mm}", "Pod/Classes/core/*.{h,mm}", "Pod/Classes/core/**/*.{h,m,mm,inl}"  
  else
    puts '-------------------------------------------------------------------'
    puts 'Notice:YTXChart is binary now'
    puts '-------------------------------------------------------------------'
    s.source_files = 'Pod/Products/include/**'
    s.public_header_files = 'Pod/Products/include/*.h'
    s.ios.vendored_libraries = 'Pod/Products/lib/libYTXChart.a'
  end

  s.libraries = 'sqlite3', 'c++'

  s.dependency 'YTXServerId'
  s.dependency 'AFNetworking', '~> 2.0'
end

注意這段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版本規范

完整分析

當你發布完成之後,查看。我們發現在Spec Repo中對應版本的podspec就是我們的YTXChart/podspec。CocoaPod從s.sourcegit地址和tag下載對應的代碼,Pod/Products和Pod/Classes裡的內容都存在當你使用IS_SOURCE=1ENV['IS_SOURCE']會為true。CocoaPods通過s.source_files從下載代碼的路徑找到源碼構建Example/Pods和YTXChart.xcworkspace

publishspec

明白了上面的過程,來再分析下為什麼要在切換源碼和二進制化時刪除cache和Pods目錄。放幾張圖就明白了

podcachelist

cachejson

刪除cache和Pods目錄。IS_SOURCE=1 pod install觀察json。

cachejsonsource

總結:

  • 沒有使用submodule或新的git倉庫來構建出一個不包含依賴內容的靜態庫。一份原來的git倉庫。

  • 沒有因為構建二進制庫而需要增加冗余的源代碼。所以當你修改Pod/Classes中的源碼,可以方便簡單地執行buildbinary.sh腳本來構建出靜態庫。一份源碼。

  • 共用了一份YTXChart.podspec

  • 沒有大量修改YTXChart.podspec

  • 使用pod lib lintIS_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源碼。當安裝二進制的時候需要改成。來回切換都需要改,極不方便。

  • 要改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。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved