前言
隨着業務的擴展、項目體積的增大,CocoaPods
組件庫越來越多,每次重新編譯的時候速度越來越慢,這給我們提出了需要提高編譯速度的需求。
為了提高項目編譯速度,對於大量使用組件化開發的項目組而言,組件二進制化是必然要走的路線,雖然中心思想就是要將各個組件打包成.a
二進制庫,但是各個公司可能方案都不太相同,網上的方案也有很多可供選擇,這里我大體總結成以下幾種:
- 分倉庫管理
Carthage
管理podspec
環境變量(宏管理)podspec
分tag
管理(只針對私有庫)
前兩個就不在這里討論了可以看看這篇講解。今天重點給大家分享一下第三和第四種方案的實施,但是目前只能針對私有庫實施,對於一些第三方的公有庫目前沒有什么好的方案(😁 有好方法的同學可以在評論區推薦一下)。
實施
1、創建pod私有庫
😝 如果您對這一塊很了解請跳過這一步直接看第二步
對於私有庫的創建,一般我們會采用pod lib create XXX
模板來進行構建(如果還不知道這條命令是干嘛的同學可以先移步了解一下理解CocoaPods的Pod Lib Create)
這里我們拿ABC
這個項目進行舉例,首先我們執行pod lib create ABC
創建ABC
的私有庫 CocoaPods
會從https://github.com/CocoaPods/pod-template.git
下載模板文件,並詢問你一些構建信息,正常填就好了。
作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:413038000,不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!
以下資料在群文件可自行下載!
推薦閱讀
iOS開發——最新 BAT面試題合集(持續更新中)
[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 ]
>
復制代碼
一般如果我們構建好了的話工程目錄會類似這樣一個結構:
.
├── 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
、源文件目錄、Podfile
、podspec
、.gitignore
文件等(真是一個貼心的小家伙),而且很規范,Demo
文件在Example
目錄下
窺視一下podspec
文件你就明白了源碼需要指定在./Classes/**/*
路徑下
s.source_files = 'ABC/Classes/**/*'
復制代碼
為了演示效果,我們創建兩個源文件ABC.h
與ABC.m
並放入Classes
路徑下,同時將默認的ReplaceMe.m
刪除
[圖片上傳中...(image-b08f0c-1594108050326-4)]
接着在Example
下執行pod install
,可以發現ABC.h/m
已經導入成功
[圖片上傳中...(image-e75b0-1594108050326-3)]
至此,我們就明白了私有庫的創建過程,需要編寫源代碼需要放入指定目錄下並在執行pod install
進行同步
2、創建靜態庫
組件二進制其實指的就是打包成動態庫/靜態庫,由於過多的動態庫會導致啟動速度減慢得不償失,此外iOS
對於動態庫的表現形式只有framework
,若想做源碼與二進制切換時,引入頭文件的地方也不得不進行更改,例如:
import <ABC.h> // 源碼引用
import <ABCBinary/ABC.h> // 動態庫引用
復制代碼
而打包成靜態庫.a
文件(注意不要打包成framework
形式)則不需要更改引用代碼,所以綜上所述,我們選擇打包成靜態庫的方式不需修改引用代碼、縮小體積提升編譯速度。
確定目標之后,就是實施了,一般而言我們私有庫都會在遠程托管地址有git
倉庫,然后再上傳到指定的私有源(specs)上,那么就會引申出幾個問題:
- 要不要將靜態庫上傳到
git
(如果包體積很大會很占用git
空間) - 怎么做到一套代碼同時管理源碼和二進制
- 為了能夠調試源碼,如何在源碼及二進制間切換(下一步驟會講到)
針對這幾個問題,一一回答:
3、靜態庫與源碼如何用同一套代碼管理?
其實這個很簡單,我們接着拿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
,只做引用即可
[圖片上傳中...(image-d11b04-1594108050325-2)]
[圖片上傳中...(image-a6b089-1594108050324-1)]
之后我們需要到ABCBinary
的Build Setting
中指定靜態庫所能運行的最低版本:
Build Setting->Deployment->iOS Deployment Target
復制代碼
並在Build Phases
中指定頭文件,將ABC.h
拖入Public中,具體步驟:
TARGETS->ABCBinary->Build Phases->New Header Phase
復制代碼
至此我們完成了一套代碼管理二進制與源碼,但有個小細節需要注意:就是如果源代碼有變動需要在XXXBinary
文件中重新導入一遍,不然二進制的文件不會自動更新(同學們有好的建議可以評論區討論下)
4、是否需要將二進制上傳至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
倉庫空間壓力了(運維小哥哥們表示“尼瑪松了一口氣”)
5、如何在源碼與二進制間切換
在提升編譯速度的前提下,還需要考慮到能隨時進行源碼調試,這就涉及到了如何在源碼與二進制間切換的問題,網上的思路有很多:環境變量、白名單、tag切換等。
這幾種方式在前言部分我們已經講過了,接下來我們介紹一下“環境變量”和“tag切換”這兩種方式:
5.1、 如何利用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 😝~
[圖片上傳中...(image-84413-1594108050322-0)]
接下來驗證本地podspec
(若有問題按照提示更改,ssh://xxx.git
是你私有源的地址):
pod lib lint --sources=ssh://xxx.git --allow-warnings --verbose --use-libraries
復制代碼
若沒問題,在ABC
的git
倉庫打一個0.1.0
的版本tag
,並上傳ABC.podspec
至私有源,上傳成功后修改podspec.version
為0.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.2、如何利用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目錄下移除
復制代碼
6、對比兩種方式
方式 | 優點 | 缺點 |
---|---|---|
Ruby環境變量切換 | 1、不需要上傳兩份podspec | |
2、切換時不需要修改Podfile | 1、需要清除私有庫的緩存 |
2、需要手動刪除/Pods/XXX
3、不能針對單獨庫進行切換,除非自定義白名單之類的規則 |
| tag切換 | 1、可以針對單獨某個庫進行切換 | 1、需要執行pod update(需等待repo master源的更新)
2、私有庫的tag需要打兩個,podspec上傳時需要傳兩次
3、切換時需要手動修改Podfile文件的版本信息 |
7、總結
好,至此iOS組件二進制方案就介紹完了,我們通過ABC
項目的實踐了解了整個過程:
- 創建pod私有庫
- 在私有庫Demo中創建靜態庫target,並配置頭文件及最低iOS版本支持
- 創建打包腳本
- 設置
.gitignore
忽略輸出的二進制包 - 配置podspec根據tag版本判斷或根據環境變量判斷
- 驗證並上傳源碼及二進制的podspec
- 在實際項目中切換時需要執行
pod update
或刪除Podfile.lock
中相關庫信息
8、鏈接
本文demo相關鏈接如下,另附自動上傳podspec腳本地址(相關文章),喜歡的朋友點個star
- 組件化方案demo地址:CocoaPodsBinary
- 自動上傳podspec腳本:upload_podspec
作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:413038000,不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!
推薦閱讀
iOS開發——最新 BAT面試題合集(持續更新中)
作者:被帥醒的吳寶寶
鏈接:https://juejin.im/post/5efaf0655188252e42157e8a