本文來自拉勾網課程整理。
前言
在 iOS App
開發方面,幾乎所有的 App
都需要使用到第三方依賴庫。依賴庫不僅能為我們提供豐富的功能,還能避免我們從頭開發,在節省時間的同時也減少許多 Bug
。
但伴隨著軟件功能越來越豐富,依賴庫數量越來越多,由此也出現了“依賴地獄”,比如依賴庫循環依賴,底層依賴庫版本沖突等。為了解決此類問題,于是,依賴庫管理工具也就出現了。
目前流行的依賴庫管理工具主要有:Git Submodules
、Carthage
、 Swift Package Manager
和 CocoaPods
。在這里我們選擇 CocoaPods
。為什么呢?原因有三:
-
CocoaPods
非常成熟,十分穩定,并且簡單易用,學習成本低,效果明顯; -
CocoaPods
會自動整合Xcode
項目,使得其他項目成員在使用第三方庫時無須任何額外的手工操作; -
CocoaPods
已經成為iOS
業界標準,支持幾乎所有的開源庫和商業庫,即便是Objective-C
的依賴庫以及二進制文件(binary
)依賴庫,CocoaPods
也提供支持。
那么,怎樣使用 CocoaPods
來管理第三方依賴庫呢?接下來我會從語義化版本管理、Pod
版本管理、Pod
版本更新三個方面展開介紹。
語義化版本管理
開發軟件,免不了要更新迭代,所以每一次更新的版本號管理變得很重要。并且,一旦版本號混亂,就會導致一系列問題,比如很難查找和修改線上崩潰,沒辦法支持多團隊并行開發,等等。為了避免此類問題,我們可以使用語義化版本管理(Semantic Versioning
)來統一版本號的定義規范。
語義化版本號是一種通用的版本號格式規范,目前絕大部分優秀的第三方依賴庫都遵循這一規范來發布版本。
具體來說,語義化版本號的版本號一般包括四部分:MAJOR、MINOR、PATCH、BUILD
。每一部分都由遞增的數值組成,例如 1.2.3.4
,其中 1
是MAJOR
,2
是 MINOR
。如果我們更新 MINOR
版本號,那么下一個版本就是 1.3.0.0
。接下來我詳細介紹下這四部分。
MAJOR
是指主版本號,通常在重大更新的時候才會需要更新主版本號。例如iOS
每年都會更新一個主版本號。而對于第三方庫來說,主版本號的更新,表示該庫的 API 新增了重大功能,或者引入了不可兼容的更新 (breaking changes
)。MINOR
是指副版本號,用于小功能的改善。例如iOS 14
在發布主版本后,在一年內可能發布多個副版本如14.1
、14.2
來完善其系統功能。一般對于第三方庫來說,副版本的更新就是新增一些 API,但不包含不可兼容的更新。PATCH
是指補丁版本號,一般用于bug fix
以及修復安全性問題等。對于第三方庫來說,補丁版本號的更新也不應該有不可兼容的更新。雖然實際操作中這會有些困難,但我們可以通過把原有API
標記為deprecated
,或者為新API
參數提供默認值等辦法來解決。BUILD
是指構建版本號,通常在內部測試時使用。一般當我們使用 CI 服務器進行自動構建時,構建版本號會自動更新。
1.3.4.7
Pod 版本管理
要使用 CocoaPods
管理第三方依賴庫,首先要新建一個 Podfile
文件,然后執行bundle exec pod install
命令來安裝所有依賴庫。這時候 CocoaPods
會自動幫我們建立一個 Podfile.lock
文件和一個 Workspace
文檔。
注意,在第一講我們說過,由于是通過 Bundler
來安裝 CocoaPods
,每次執行pod
命令前,都需要加上bundle exec
。不過為了簡潔,后面涉及pod
命令時,我會省略bundle exec
部分。
接下來,我詳細介紹下 Podfile
文件、 Podfile.lock
和 Workspace
文檔到底是什么,以及如何使用。
Podfile 文件
Podfile 文件
是一個配置文件,它主要是用來描述 Xcode
項目里各個 target
的依賴庫。我們項目的 Podfile
文件可以在倉庫中找到。在這里,我主要和你介紹一下 Podfile
文件中的幾個重要配置。
source 配置
source
用于指向 PodSpec
(Pod 規范)文件的 Repo
,從而使得 CocoaPods
能查詢到相應的 PodSpec
文件。
具體來說,當使用公共依賴庫的時候,source
需要指向 CocoaPods Master Repo
,這個主倉庫集中存放所有公共依賴庫的 PodSpec
文件。 由于 CocoaPods
經常被開發者吐槽 Pod
下載很慢,因此CocoaPods
使用了 CDN (Content Delivery Network,內容分發網絡)來緩存整個 CocoaPods Master Repo
, 方便開發者快速下載。具體的配置方法就是使source
指向 CND
的地址,代碼示例如下:
復制代碼
source 'https://cdn.cocoapods.org/'
如果使用的是私有依賴庫,我們也需要把source
指向私有庫的 PodSpec Repo
,以使得 CocoaPods
能找到相應的 PodSpec
文件。 代碼示例如下:
復制代碼
source 'https://my-git-server.com/internal-podspecs'
注意,當我們使用私有庫時,執行pod install
命令的機器必須能訪問到source
所指向的 Repo
。
project
和 workspace
project
用于指定我們的主項目文檔。該項目文檔會使用到 CocoaPods
管理的所有第三方依賴庫。
workspace
用于指定要生成和更新的 Workspace
文檔。和其他依賴庫管理工具不一樣,CocoaPods
會自動生成一個 Workspace
文檔,然后我們只能使用該文檔而不是 Xcode
項目文檔來進行后續開發。
代碼示例如下:
復制代碼
project './Moments/Moments.xcodeproj'
workspace './Moments.xcworkspace'
這其中 Moments.xcodeproj
就是我們的主項目文檔,它一般放在和項目名字相同的下一層目錄下。
而 Moments.xcworkspace
是 CocoaPods
為我們生成的 Workspace
文檔,為了統一,我建議名字也是和主項目相同。
-
platform
和use_frameworks
先看示例,它表示什么呢?
復制代碼
platform :ios, '14.0'
use_frameworks!
為了保證所有依賴庫與主項目在編譯和運行時兼容,我們指定的系統版本號需要和主項目所支持的系統版本號保持一致。而platform
就是用于指定操作系統以及所支持系統的最低版本號。比如,例子中的platform :ios, '14.0'
就表示支持 iOS 14.0
以上的所有iOS
版本。
另外一行的use_frameworks!
這一配置會讓 CocoaPods
把所有第三方依賴庫打包生成一個動態加載庫
,而不是靜態庫
。因為動態庫是我們經常用到的,它能有效地加快編譯和鏈接的速度。
組織同類型的第三方依賴庫
復制代碼
def dev_pods
pod 'SwiftLint', '0.40.3', configurations: ['Debug']
pod 'SwiftGen', '6.4.0', configurations: ['Debug']
end
其中configurations: ['Debug']
用于指定該依賴庫只是使用到Debug
構建目標(target)里面,而不在其他(如Release)構建目標里面,這樣做能有效減少App Store
發布版本的體積。
def dev_pods end
代碼塊是“復用同一類依賴庫方式”的意思,我們可以把同類型的依賴庫都放進這個代碼塊里面。比如,我們的 Moments
項目中就分別有dev_pods
(開發相關的庫),core_pods
(核心庫)以及thirdparty_pods
(第三方庫)等代碼塊定義。
target 配置
有了這些復用庫定義以后,怎樣使用到項目的構建目標(target)里面呢?下面就是一個例子。
復制代碼
target 'Moments' do
dev_pods
core_pods
# other pods...
end
我們可以把構建目標所使用的所有依賴庫放進target
代碼塊中間,上面中的Moments
就是我們的App
構建目標。該構建目標依賴了dev_pods
和core_pods
等各組依賴庫。執行pod install
的時候,CocoaPods
會把dev_pods
代碼塊自動展開為SwiftLint
和SwiftGen
,那么Moments
構建目標能使用SwiftLint
和SwiftGen
依賴庫了。
依賴庫的版本
復制代碼
pod 'RxSwift', '= 5.1.1'
pod 'RxRelay', '= 5.1.1'
在 CocoaPods
里面,每一個依賴庫稱為一個 Pod
(注意這里首字母大寫,Pod
指一個庫),指定一個Pod
的命令是pod
(注意這里是小寫,表示一條命令)。在 Podfile 里面我們可以通過這樣的格式pod 'RxSwift', '= 5.1.1'
來配置依賴庫的版本號。其中,RxSwift
或者RxRelay
是依賴庫的名字,5.1.1
為版本號。這些庫的名字以及版本號都可以在 CocoaPods
官網上找到。
為了統一管理第三方依賴庫的版本,我建議統一使用 = 來鎖定該依賴庫的版本,這樣就能保證每次執行pod install
的時候都可以為同一個庫下載同一個版本。
除了 =
操作符以外,CocoaPods
還支持其他操作符來指定版本:
-
> 0.1
表示大于 0.1 的任何版本,這樣可以包含 0.2 或者 1.0; -
>= 0.1
表示大于或等于 0.1 的任何版本; -
< 0.1
表示少于 0.1 的任何版本; -
<= 0.1
表示少于或等于 0.1 的任何版本; -
~> 0.1.2
表示大于 0.1.2 而且最高支持 0.1.* 的版本,但不包含 0.2 版本。
這幾個操作符相里面,~>
(Squiggy arrow)操作符更為常用,它是以最后一個部分的版本號(例子中 0.1.2 的最后一個部分是補丁版本號 ..2)來計算可以支持的最高版本號。
例如~> 0.1.2
表示 >= 0.1.2
并且 < 0.2.0
,但不能等于0.2.0
, 因為0.2.0
已經更新了副版本號而不僅僅是補丁版本號了。另外一個例子是~> 0.1
,表示 >= 0.1
并且 < 1.0
,舉例來說,我們可以更新到 0.9
但不能更新到1.0
。
前面我介紹的是引用外部的第三方依賴庫,如果我們的項目有自己的內部依賴庫,要怎樣在 CocoaPods 引用它呢?其實很簡單,我們可以執行以下命令:
復制代碼
pod 'DesignKit', :path => './Frameworks/DesignKit', :inhibit_warnings => false
和其他外部依賴庫不一樣,我們需要使用:path
來指定該內部庫的路徑。
Podfile.lock 文件
Podfile.lock
文件是由 CocoaPods
自動生成和更新的,該文件會詳細列舉所有依賴庫具體的版本號。比如,
復制代碼
DEPENDENCIES:
- Alamofire (= 5.2.0)
- Firebase/Analytics (= 7.0.0)
PODFILE CHECKSUM: 400d19dbc4f5050f438797c5c6459ca0ef74a777
當執行pod install
后,CocoaPods
會根據 Podfile
文件解釋出各依賴庫的特定版本號,然后一一列舉在 DEPENDENCIES
下面。在上述的例子中,我們的App
在構建過程中使用了5.2.0
的 Alamofire
庫以及 7.0.0
的Firebase Analytics
庫。
PODFILE CHECKSUM
用于記錄Podfile
的驗證碼,任何庫的版本號的更改,都會改變該驗證碼。這樣能幫助我們在不同的機器上,快速檢測依賴庫的版本號是否一致。
我建議要把 Podfile
和 Podfile.lock
文件一同commit
并 push
到 Git
代碼管理服務器里面。特別是在團隊開發的環境下,這樣能幫助我們保證各個依賴庫版本號的一致性。
在實踐操作中,無論我們在哪臺機器上執行pod install
, PODFILE CHECKSUM
都不應該發生任何改變。因為我們在 Git
保存了 Podfile.lock
,一旦我們發現老版本 App
的 Bug
,就可以根據該文件為各個依賴庫重新安裝同一版本號,來重現和定位問題,從而幫助我們快速修改這些 Bug
。
Workspace 文檔
Workspace
文檔是 Xcode 管理子項目
的方式。通過 Workspace
,我們可以把相關聯的多個 Xcode
子項目組合起來方便開發。
前面說過,當我們執行pod install
的時候,CocoaPods
會自動創建或者更新一個叫作 Pods
的項目文檔(Pods.xcodeproj
)以及一個 Workspace
文檔(在我們項目中叫作 Moments.xcworkspace
)。
其中,Pods
項目文檔負責統一管理各個依賴庫,當我們在 Podfile
里面指定構建成動態庫的時候,該項目會自動生成一個名叫Pods_<項目名稱>.framework
的動態庫供我們項目使用。
而 Workspace
文檔則統一管理了我們原有的主項目 (Moments.xcodeproj
)以及那個Pods
項目。
與此同時,CocoaPods
還會修改Xcode
項目中的 Build Phases
以此來檢測 Podfile.lock
和 Manifest.lock
文件的一致性,并把Pods_<項目名稱>.framework
動態庫嵌入我們的主項目中去。
以上所有操作都是由 CocoaPods
自動幫我們完成。以后的開發,我們都可以打開 Workspace
文檔而不是原有的Xcode
項目文檔來進行。
Pod 版本更新
使用 CocoaPods
管理第三方依賴庫的操作非常簡單,可是一旦使用不當,特別是在 Pod
更新的時候,很容易引起依賴庫版本不一致,從而出現各種問題。
比如,在編譯程序的時候,有些開發者可以順利進行,而另外一些開發者編譯時候就會出錯;或者程序在本地編譯時運行良好,一旦在 CI 上構建
,就會出現 App 崩潰
,等等。
那么,怎么保證更新 Pod
的時候都能保證版本一致呢?
下面結合我的實踐經驗,以第三方網絡庫 Alamofire
為例子和你介紹下。
第一步
,CocoaPods
已經為我們提供了pod outdated
命令,我們可以用它一次查看所有 Pod
的最新版本,而無須到 GitHub
上逐一尋找。下面是執行pod outdated
命令的其中一條結果:
復制代碼
The following pod updates are available:
- Alamofire 5.2.0 -> 5.2.0 (latest version 5.4.0)
這表示當前我們使用了版本為 5.2.0
的 Alamofire
,其最新版本為5.4.0
。如果我們決定更新到版本5.4.0
,那么可以繼續下一步。
第二步
,在更新依賴庫版本之前,為了避免在新版本中不小心引入 Bug
,我們需要了解新的版本到底提供了哪些新功能,修改了哪些 Bug
,與老版本是否兼容等事項。具體我們可以到CocoaPods
官網上查找需要更新的第三方依賴庫,然后在 GitHub
等平臺上找到,并仔細閱讀該庫的版本說明(release note
)。
請注意,我們要閱讀當前使用版本到要更新的版本之間的所有版本說明。 在這個例子中,我們要閱讀 5.2.1
,5.2.2
,5.3.0
和 5.4.0
的所有版本說明。這些版本說明會列出新增功能,更新的API
,修改的 Bug
,有沒有不可兼容的更新 。
第三步
,在Podfile 文件
里把要更新的Pod
的版本號進行修改。例如把pod 'Alamofire', '= 5.2.0'
改成pod 'Alamofire', '= 5.4.0'
。 然后執行pod install來
重新生成 Podfile.lock
文件。
此時特別注意的是,我們要使用pod install
而不是pod update
。因為執行pod update
會自動更新所有Pod
的版本,這可能會更新了一些我們目前還不想更新的 Pod
,從而會引入一些難以覺察的問題。
第四步
,如果所更新的版本包含了不可兼容的更新,我們需要修改代碼來保證代碼能順利完成編譯。
第五步
,很多第三方依賴庫都是一些通用的基礎組件,一旦發生問題會影響到整個 App 的功能,因此我們需要根據所更新的庫進行回歸測試。例如當更新了 Alamofire
庫的時候,我們需要把每個網絡請求都執行一遍,避免所更新的版本引入新的 Bug
。
第六步
,為了把更新的版本共享給所有開發者和CI 服務器
,我們需要把Podfile
和 Podfile.lock
文件一同 commit
并 push
到 Git
代碼管理服務器,并通過 Pull Request
流程并入主分支。
第七步
,一旦更新的代碼并入主分支后,要通過 Slack
等內部通信軟件告訴所有開發者pull
或者 rebase
主分支的代碼,并執行pod install
來更新他們開發環境的所有依賴庫。
特別注意,千萬不要使用pod update
,因為pod update
會自動把開發者機器上所有Pod
的版本自動更新了。這種更新往往不是我們想要的結果,我們希望統一更新各個 Pod
的版本,并通過 Git
進行集中管理。
如果開發者在編譯新代碼前沒有執行pod install
命令,會出現以下的錯誤。
The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.
這錯誤可以有效提醒所有開發者,需要再次執行pod instal
l來更新他們本地的依賴庫,從而保證所有開發者使用的依賴庫的版本都是一致的。
另外,如果更新了基礎組件的依賴庫(如網絡庫),在測試階段,我們還需要進行全面的回歸測試。因為這些基礎組件庫的新版本如果有Bug
很可能導致我們的App
會發生大比例的崩潰,嚴重影響用戶的體驗。
有了上面的一流程,我們就可以有效地保證每個開發者使用的依賴庫版本都是一致的,同時也能保證CI
在自動構建 App
的時候所使用的依賴庫版本也是統一的。
總結
這一講我介紹了如何使用CocoaPods
來統一管理依賴庫的版本。特別是根據我自己的經驗總結了一套更新 Pod
版本的流程,希望你靈活使用這些步驟,從而少走彎路。
這里我再特別強調一下,為了保證依賴庫版本都能保持一致,盡量不要執行pod update
,而是使用通過修改Podfile
文件里的版本號并執行pod install
來更新 Pod
的版本,然后把Podfile
和 Podfile.lock
文件一同并入 Git
主分支中進行統一管理。
源碼地址:
Podfile 文件地址:
https://github.com/lagoueduCol/iOS-linyongjian/blob/main/Podfile