iOS 一步步帶你實踐組件二進制方案

前言

隨着業務的擴展、項目體積的增大,CocoaPods組件庫愈來愈多,每次從新編譯的時候速度愈來愈慢,這給咱們提出了須要提升編譯速度的需求。html

爲了提升項目編譯速度,對於大量使用組件化開發的項目組而言,組件二進制化是必然要走的路線,雖然中心思想就是要將各個組件打包成.a二進制庫,可是各個公司可能方案都不太相同,網上的方案也有不少可供選擇,這裏我大致總結成如下幾種:ios

  • 分倉庫管理
  • Carthage管理
  • podspec環境變量(宏管理)
  • podspectag管理(只針對私有庫)

前兩個就不在這裏討論了能夠看看這篇講解。今天重點給你們分享一下第三和第四種方案的實施,可是目前只能針對私有庫實施,對於一些第三方的公有庫目前沒有什麼好的方案(😁 有好方法的同窗能夠在評論區推薦一下)。git

實施

一、建立pod私有庫

😝 若是您對這一塊很瞭解請跳過這一步直接看第二步github

對於私有庫的建立,通常咱們會採用pod lib create XXX模板來進行構建(若是還不知道這條命令是幹嗎的同窗能夠先移步瞭解一下理解CocoaPods的Pod Lib Createxcode

這裏咱們拿ABC這個項目進行舉例,首先咱們執行pod lib create ABC建立ABC的私有庫 CocoaPods會從https://github.com/CocoaPods/pod-template.git下載模板文件,並詢問你一些構建信息,正常填就行了。緩存

[MichaeldeMacBook-Pro:~ michaelwu$ pod lib create ABC
Cloning `https://github.com/CocoaPods/pod-template.git` into `ABC`.
Configuring ABC template.

------------------------------

To get you started we need to ask a few questions, this should only take a minute.

If this is your first time we recommend running through with the guide: 
 - https://guides.cocoapods.org/making/using-pod-lib-create.html
 ( hold cmd and double click links to open in a browser. )


What platform do you want to use?? [ iOS / macOS ]
 > 

複製代碼

通常若是咱們構建好了的話工程目錄會相似這樣一個結構:ruby

.
├── ABC
│   ├── Assets
│   └── Classes
├── ABC.podspec
├── Example
│   ├── ABC
│   ├── ABC.xcodeproj
│   ├── ABC.xcworkspace
│   ├── Podfile
│   ├── Podfile.lock
│   ├── Pods
│   └── Tests
├── LICENSE
├── README.md
└── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj
複製代碼

這裏你會發現,CocoaPods已經幫咱們建立好了Demo、源文件目錄、Podfilepodspec.gitignore文件等(真是一個貼心的小傢伙),並且很規範,Demo文件在Example目錄下bash

窺視一下podspec文件你就明白了源碼須要指定在./Classes/**/*路徑下運維

s.source_files = 'ABC/Classes/**/*'
複製代碼

爲了演示效果,咱們建立兩個源文件ABC.hABC.m並放入Classes路徑下,同時將默認的ReplaceMe.m刪除 ssh

接着在Example下執行pod install,能夠發現ABC.h/m已經導入成功

至此,咱們就明白了私有庫的建立過程,須要編寫源代碼須要放入指定目錄下並在執行pod install進行同步

二、建立靜態庫

組件二進制其實指的就是打包成動態庫/靜態庫,因爲過多的動態庫會致使啓動速度減慢得不償失,此外iOS對於動態庫的表現形式只有framework,若想作源碼與二進制切換時,引入頭文件的地方也不得不進行更改,例如:

import <ABC.h> // 源碼引用
import <ABCBinary/ABC.h> // 動態庫引用
複製代碼

而打包成靜態庫.a文件(注意不要打包成framework形式)則不須要更改引用代碼,因此綜上所述,咱們選擇打包成靜態庫的方式不需修改引用代碼、縮小體積提高編譯速度。

肯定目標以後,就是實施了,通常而言咱們私有庫都會在遠程託管地址有git倉庫,而後再上傳到指定的私有源(specs)上,那麼就會引伸出幾個問題:

  • 要不要將靜態庫上傳到git(若是包體積很大會很佔用git空間)
  • 怎麼作到一套代碼同時管理源碼和二進制
  • 爲了可以調試源碼,如何在源碼及二進制間切換(下一步驟會講到)

針對這幾個問題,一一回答:

三、靜態庫與源碼如何用同一套代碼管理?

其實這個很簡單,咱們接着拿ABC這個項目舉例子,進入Example打開咱們的ABC.xcworkspace工程,而後建立新的Target爲靜態庫,並取名爲ABCBinary(必定要取這個名字,後面我會解釋)

File->New->Target->Static Library
複製代碼

此時在Example目錄下會增長剛剛建立的Target文件夾,結構以下:

├── ABCBinary
│   ├── ABCBinary.h
│   └── ABCBinary.m
複製代碼

Xcode默認會幫咱們生成兩個文件,咱們將.h更名爲placeholder.h.m刪除,這裏爲何要將.h換成placeholder.h呢?先賣個關子,待會咱們再做解釋。

咱們把剛纔寫的ABC.h/m的源碼拖到ABCBinary中,注意不要勾選Copy items if needed,只作引用便可

以後咱們須要到ABCBinaryBuild Setting中指定靜態庫所能運行的最低版本:

Build Setting->Deployment->iOS Deployment Target
複製代碼

並在Build Phases中指定頭文件,將ABC.h拖入Public中,具體步驟:

TARGETS->ABCBinary->Build Phases->New Header Phase
複製代碼

至此咱們完成了一套代碼管理二進制與源碼,但有個小細節須要注意:就是若是源代碼有變更須要在XXXBinary文件中從新導入一遍,否則二進制的文件不會自動更新(同窗們有好的建議能夠評論區討論下)

四、是否須要將二進制上傳至git?

其實git對代碼管理時會將不一樣的diff作備份(在.git這個文件夾下),可是對於二進制文件來講git就沒用那麼友好了,會將二進制的每一次提交都作磁盤備份,以便於隨時版本回滾,假若咱們每次都對私有庫進行更新時都將二進制包傳至git,那麼時間久了無疑是對git倉庫空間的一個挑戰(若是大家公司空間足夠大不須要考慮,那麼請忽略這一步)

網上有不少針對這個問題給出的解決方案,但都不是很完美,大致上都是說將二進制包單獨傳到另外一份靜態資源地址,以此解決git過大問題,不過我以爲沒有解決痛點,能不能不上傳二進制包呢?

結論固然是能夠,CocoaPods本地的緩存目錄在

~/Library/Caches/Cocoapods
複製代碼

其實每次咱們更新pod庫時,CocoaPods都會先從指定源去拉源代碼再根據該庫的podspec文件指定輸出目標文件,那麼咱們若是能把靜態庫打包推遲到pod install階段就不須要上傳二進制包到git了,可是如何作到延遲打包呢?

很幸運,CocoaPods提供了針對podspec的預執行腳本,prepare_command(戳我進官網)命令,該命令能夠指定相應的腳本在pod install時去執行,那麼咱們就能夠將編譯打包的腳本放入其中,從而完成延遲打包

好了,理論上貌似可行了,實踐出真知啊(😄 絕對不能作一個理論性選手啊),具體怎麼作?

首先咱們須要一個能一鍵打靜態庫包的腳本(一刀99級那種),帥氣的我這邊已經爲你們準備好了,只修改一下PROJECT_NAME便可,拷貝腳本至根目錄並賦予執行權限:

# 當前項目名字,須要修改!
PROJECT_NAME='ABC'

# 編譯工程
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${PROJECT_NAME}.a"
rm -r "${WRK_DIR}"
複製代碼

咱們仍是拿ABC的項目來接着實踐,拷貝腳本後,先來看一下咱們ABC目前的結構:

.
├── ABC
│   ├── Assets
│   └── Classes
├── ABC.podspec
├── Example
│   ├── ABC
│   ├── ABC.xcodeproj
│   ├── ABC.xcworkspace
│   ├── ABCBinary
│   │   └── placeholder.h
│   ├── Podfile
│   ├── Podfile.lock
│   ├── Pods
│   └── Tests
├── LICENSE
├── README.md
├── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj
└── build_lib.sh
複製代碼

能夠看到最下面多了一個build_lib.sh腳本(就是剛剛拷貝的那個腳本),另外ABCBinary裏面有一個placeholder.h,這裏解釋一下以前埋下的懸念:由於ABCBinary文件夾裏對於源碼的引用沒有copy,因此在提交到git時會自動將文件夾清空(也就是說在git目錄裏找不到),所以須要加一個佔位防止文件夾不上傳到git,可是切記不要編譯到靜態庫裏!

好的,至此一鍵打包腳本也準備好了,經過查看腳本咱們發現這個二進制包最終會輸出到根目錄下的./Pod/Products/目錄中,那不仍是得傳到git嗎?別急,你忘了gitignore了嗎?

配置.gitignore忽略Pod/文件不就好了嘛,在.gitignore最下面增長忽略

Pod/
複製代碼

好了至此,咱們完成了自動打包腳本及git忽略二進制包,不再用擔憂咱們的git倉庫空間壓力了(運維小哥哥們表示「尼瑪鬆了一口氣」)

五、如何在源碼與二進制間切換

在提高編譯速度的前提下,還須要考慮到能隨時進行源碼調試,這就涉及到了如何在源碼與二進制間切換的問題,網上的思路有不少:環境變量、白名單、tag切換等。

這幾種方式在前言部分咱們已經講過了,接下來咱們介紹一下「環境變量」和「tag切換」這兩種方式:

5.一、 如何利用tag進行切換:

首先咱們須要約定好規則:當version中包含.Binary關鍵字時執行prepare_command命令並輸出source爲靜態庫,具體操做以下(podspec是用ruby寫的,支持條件判斷):

if s.version.to_s.include?'Binary'
    
    puts '-------------------------------------------------------------------'
    puts 'Notice:ABC is binary now'
    puts '-------------------------------------------------------------------'
    s.prepare_command = '/bin/bash build_lib.sh'
    s.source_files = 'Pod/Products/include/**'
    s.ios.vendored_libraries = 'Pod/Products/lib/*.a'
    s.public_header_files = 'Pod/Products/include/*.h'    
else
    s.source_files = 'ABC/Classes/**/*'
end
複製代碼

因爲tag是根據version走的(tag => s.version.to_s),所以只須要咱們修改s.version = '0.1.0.Binary'便可實現二進制打包

好,咱們貼一段此時ABC.podspec完整的代碼:

Pod::Spec.new do |s|
  s.name             = 'ABC'
  s.version          = '0.1.0.Binary'
  s.summary          = 'A short description of ABC.'

  s.description      = <<-DESC TODO: Add long description of the pod here. DESC
  
  s.homepage         = 'https://github.com/609223770@qq.com/ABC'
  # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { '609223770@qq.com' => '609223770@qq.com' }
  s.source           = { :git => 'https://github.com/609223770@qq.com/ABC.git', :tag => s.version.to_s }
  # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'

  s.ios.deployment_target = '8.0'
  
  if s.version.to_s.include?'Binary'    
    puts '-------------------------------------------------------------------'
    puts 'Notice:ABC is binary now'
    puts '-------------------------------------------------------------------'
    s.prepare_command = '/bin/bash build_lib.sh'
    s.source_files = 'Pod/Products/include/**'
    s.ios.vendored_libraries = 'Pod/Products/lib/*.a'
    s.public_header_files = 'Pod/Products/include/*.h'    
  else
    puts '-------------------------------------------------------------------'
    puts 'Notice:ABC is source code now'
    puts '-------------------------------------------------------------------'
    s.source_files = 'ABC/Classes/**/*'
  end
end
複製代碼

讓咱們來看看效果,在Example下執行pod install,發現切換過來了,Nice 😝~

接下來驗證本地podspec(如有問題按照提示更改,ssh://xxx.git是你私有源的地址):

pod lib lint --sources=ssh://xxx.git --allow-warnings --verbose --use-libraries
複製代碼

若沒問題,在ABCgit倉庫打一個0.1.0的版本tag,並上傳ABC.podspec至私有源,上傳成功後修改podspec.version0.1.0.Binary再次執行上傳:

pod repo push XXXSpecs ABC.podspec --allow-warnings --verbose --use-libraries
複製代碼

✅ 若是一切順利,咱們已經將Binary和源碼的ABC上傳到了私有源。

接下來咱們在實際項目實驗一下,Podfile中指定,並執行安裝

pod 'ABC', '~> 0.1.0' # source code

pod install
複製代碼

不出意外源碼ABC安裝成功,這時咱們修改tag版本後面加.Binary,再次執行pod install,以下所示:

pod 'ABC', '~> 0.1.0.Binary' # source code

pod install
複製代碼

很遺憾,你可能會發現源碼並無切換成功,爲何呢?

原來Pod的版本管理是放在Podfile.lock中,每次執行pod install時若Podfile.lock中已經存在此庫,則只下載Podfile.lock文件中指定的版本進行安裝,不然去搜索這個pod庫在Podfile文件中指定的版原本安裝。

所以,解決辦法有兩種,一種是從Podfile.lock中將包含ABC的地方所有刪除或是乾脆直接刪除Podfile.lock,再次執行pod install會發現切換變過來了。

還有一種方法是執行pod update,這也是 update 和 install 的區別,update會讀取Podfile中的版本去更新Podfile.lock文件。(戳我查看pod install和pod update區別

pod update ABC
複製代碼

執行後,先是會更新一下master和其餘私有源,再去更新ABC,發現此時切換成功。(缺點就是若是Podfile中若是某些庫沒有指定版本就會更新到最新版本)

5.二、如何利用Ruby環境變量進行切換:

Ruby語法支持一些環境變量的讀取,所以能夠在pod install時增長參數以此判斷是否要切換源碼:

IS_BINARY=1 pod install # 1 表明二進制
IS_BINARY=0 pod install # 0 表明源碼
pod install # 默認也是0 源碼
複製代碼

podspec中作修改:

Pod::Spec.new do |s|
  s.name             = 'ABC'
  s.version          = '0.1.0.Binary'
  s.summary          = 'A short description of ABC.'

  s.description      = <<-DESC TODO: Add long description of the pod here. DESC
  
  s.homepage         = 'https://github.com/609223770@qq.com/ABC'
  # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { '609223770@qq.com' => '609223770@qq.com' }
  s.source           = { :git => 'https://github.com/609223770@qq.com/ABC.git', :tag => s.version.to_s }
  # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'

  s.ios.deployment_target = '8.0'
  
  if s.version.to_s.include?'Binary' or ENV['IS_BINARY']    
    puts '-------------------------------------------------------------------'
    puts 'Notice:ABC is binary now'
    puts '-------------------------------------------------------------------'
    s.prepare_command = '/bin/bash build_lib.sh'
    s.source_files = 'Pod/Products/include/**'
    s.ios.vendored_libraries = 'Pod/Products/lib/*.a'
    s.public_header_files = 'Pod/Products/include/*.h'    
  else    
    puts '-------------------------------------------------------------------'
    puts 'Notice:ABC is source code now'
    puts '-------------------------------------------------------------------'
    s.source_files = 'ABC/Classes/**/*'  
  end
end
複製代碼

同tag切換同樣,這種方式在實際項目中切換也存在問題,須要兩個必要步驟:

pod cache clean ABC # 先清理ABC的pod緩存
rm Pods/ABC # 再把ABC從實際項目中的Pods目錄下移除
複製代碼

六、對比兩種方式

方式 優勢 缺點
Ruby環境變量切換 一、不須要上傳兩份podspec
二、切換時不須要修改Podfile
一、須要清除私有庫的緩存
二、須要手動刪除/Pods/XXX
三、不能針對單獨庫進行切換,除非自定義白名單之類的規則
tag切換 一、能夠針對單獨某個庫進行切換 一、須要執行pod update(需等待repo master源的更新)
二、私有庫的tag須要打兩個,podspec上傳時須要傳兩次
三、切換時須要手動修改Podfile文件的版本信息

七、總結

好,至此iOS組件二進制方案就介紹完了,咱們經過ABC項目的實踐瞭解了整個過程:

  • 建立pod私有庫
  • 在私有庫Demo中建立靜態庫target,並配置頭文件及最低iOS版本支持
  • 建立打包腳本
  • 設置.gitignore忽略輸出的二進制包
  • 配置podspec根據tag版本判斷或根據環境變量判斷
  • 驗證並上傳源碼及二進制的podspec
  • 在實際項目中切換時須要執行pod update或刪除Podfile.lock中相關庫信息

八、連接

本文demo相關連接以下,另附自動上傳podspec腳本地址(相關文章),喜歡的朋友點個star

相關文章
相關標籤/搜索