CocoaPods 歷險記這個專題是 Edmond 和 冬瓜 是對於 iOS / macOS 工程中版本管理工具 CocoaPods 的實現細節、原理、源碼、實踐與經驗的分享記錄,旨在幫助你們可以更加了解這個依賴管理工具,而不只侷限於pod install
和pod update
。
CocoaPods
做爲業界標準,各位 iOS 開發同窗應該都不陌生。不過不少同窗對 CocoaPods
的使用基本停留在 pod install
和 pod update
上。一旦項目組件化,各業務線邏輯拆分到獨立的 Pod
中後,光了解幾個簡單 Pod
命令是沒法知足需求的,同時還面臨開發環境的一致性,Pod
命令執行中的各類異常錯誤,都要求咱們對其有更深層的認知和 🤔。html
關於 CocoaPods
深刻的文章有不少,推薦 ObjC China 的這篇,深刻理解 CocoaPods,而本文但願從依賴管理工具的角度來談談 CocoaPods
的管理理念。node
Version control systems are a category of software tools that help a software team manage changes to source code over time. Version control software keeps track of every modification to the code in a special kind of database.
軟件工程中,版本控制系統是敏捷開發的重要一環,爲後續的持續集成提供了保障。Source Code Manager
(SCM) 源碼管理就屬於 VCS 的範圍之中,熟知的工具備如 Git
。而 CocoaPods
這種針對各類語言所提供的 Package Manger (PM)
也能夠看做是 SCM 的一種。git
而像 Git
或 SVN
是針對項目的單個文件的進行版本控制,而 PM 則是以每一個獨立的 Package 做爲最小的管理單元。包管理工具都是結合 SCM
來完成管理工做,對於被 PM 接管的依賴庫的文件,一般會在 Git
的 .ignore
文件中選擇忽略它們。github
例如:在 Node
項目中通常會把 node_modules
目錄下的文件 ignore 掉,在 iOS / macOS 項目則是 Pods
。web
Git submodules allow you to keep a git repository as a subdirectory of another git repository. Git submodules are simply a reference to another repository at a particular snapshot in time. Git submodules enable a Git repository to incorporate and track version history of external code.
Git Submodules
能夠算是 PM 的「青春版」,它將單獨的 git 倉庫以子目錄的形式嵌入在工做目錄中。它不具有 PM 工具所特有的語義化版本管理、沒法處理依賴共享與衝突等。只能保存每一個依賴倉庫的文件狀態。shell
Git
在提交更新時,會對全部文件製做一個快照並將其存在數據庫中。Git 管理的文件存在 3 種狀態:數據庫
index area
),存在 .git/index
目錄下,保存的是執行 git add
相關命令後從工做目錄添加的文件。.git/
目錄下,到這個狀態的文件改動算是入庫成功,基本不會丟失了。
Git submodule 是依賴 .gitmodules
文件來記錄子模塊的。npm
[submodule "ReactNative"] path = ReactNative url = https://github.com/facebook/ReactNative.git
.gitmodules
僅記錄了 path 和 url 以及模塊名稱的基本信息, 可是咱們還須要記錄每一個 Submodule Repo 的 commit 信息,而這 commit 信息是記錄在 .git/modules
目錄下。同時被添加到 .gitmodules
中的 path 也會被 git 直接 ignore 掉。json
做爲 Git Submodule 的強化版,PM 基本都具有了語義化的版本檢查能力,依賴遞歸查找,依賴衝突解決,以及針對具體依賴的構建能力和二進制包等。簡單對好比下:swift
Key File | Git submodule | CocoaPods | SPM | npm |
---|---|---|---|---|
描述文件 | .gitmodules | Podfile | Package.swift | Package.json |
鎖存文件 | .git/modules | Podfile.lock | Package.resolved | package-lock.json |
從 👆 可見,PM 工具基本圍繞這個兩個文件來現實包管理:
除了這兩個文件以外,中心化的 PM 通常會提供依賴包的託管服務,好比 npm 提供的 npmjs.com 能夠集中查找和下載 npm 包。若是是去中心化的 PM 好比 iOS
的 Carthage
和 SPM
就只能經過 Git 倉庫的地址了。
CocoaPods
是開發 iOS/macOS 應用程序的一個第三方庫的依賴管理工具。 利用 CocoaPods
,能夠定義本身的依賴關係(簡稱 Pods
),以及在整個開發環境中對第三方庫的版本管理很是方便。
下面咱們以 CocoaPods
爲例。
Podfile
Podfile
是一個文件,以 DSL(其實直接用了 Ruby 的語法)來描述依賴關係,用於定義項目所須要使用的第三方庫。該文件支持高度定製,你能夠根據我的喜愛對其作出定製。更多相關信息,請查閱 Podfile 指南。
Podfile.lock
這是 CocoaPods
建立的最重要的文件之一。它記錄了須要被安裝的 Pod 的每一個已安裝的版本。若是你想知道已安裝的 Pod
是哪一個版本,能夠查看這個文件。推薦將 Podfile.lock
文件加入到版本控制中,這有助於整個團隊的一致性。
Manifest.lock
這是每次運行 pod install
命令時建立的 Podfile.lock
文件的副本。若是你碰見過這樣的錯誤 沙盒文件與 Podfile.lock
文件不一樣步 (The sandbox is not in sync with the Podfile.lock
),這是由於 Manifest.lock
文件和 Podfile.lock
文件不一致所引發。因爲 Pods
所在的目錄並不總在版本控制之下,這樣能夠保證開發者運行 App 以前都能更新他們的 Pods
,不然 App 可能會 crash,或者在一些不太明顯的地方編譯失敗。
Ultimately, the goal is to improve discoverability of, and engagement in, third party open-source libraries, by creating a more centralized ecosystem.
做爲包管理工具,CocoaPods
的目標是爲咱們提供一個更加集中的生態系統,來提升依賴庫的可發現性和參與度。本質上是爲了提供更好的檢索和查詢功能,惋惜成爲了它的問題之一。由於 CocoaPods
經過官方的 Spec 倉庫來管理這些註冊的依賴庫。隨着不斷新增的依賴庫致使 Spec 的更新和維護成爲了使用者的包袱。
好在這個問題在 1.7.2 版本中已經解決了,CocoaPods
提供了 Mater Repo CDN ,能夠直接 CDN 到對應的 Pod 地址而無需在經過本地的 Spec 倉庫了。同時在 1.8 版本中,官方默認的 Spec 倉庫已替換爲 CDN,其地址爲 https://cdn.cocoapods.org。
對於一部分僅接觸過 CocoaPods
的同窗,其 PM 可能並不熟悉。其實 CocoaPods
的思想借鑑了其餘語言的 PM 工具,例:RubyGems
, Bundler
, npm
和 Gradle
。
咱們知道 CocoaPods
是經過 Ruby 語言實現的。它自己就是一個 Gem
包。理解了 Ruby 的依賴管理有助於咱們更好的管理不一樣版本的 CocoaPods
和其餘 Gem
。同時可以保證團隊中的全部同事的工具是在同一個版本,這也算是敏捷開發的保證吧。
RVM
& rbenv
RVM
和 rbenv
都是管理多個 Ruby 環境的工具,它們都能提供不一樣版本的 Ruby 環境管理和切換。
具體哪一個更好要看我的習慣。 固然rbenv
官方是這麼說的 Why rbenv 。本文後續的實驗也都是是使用rbenv
進行演示。
The RubyGems software allows you to easily download, install, and use ruby software packages on your system. The software package is called a 「gem」 which contains a packaged Ruby application or library.
RubyGems 是 Ruby 的一個包管理工具,這裏面管理着用 Ruby 編寫的工具或依賴咱們稱之爲 Gem。
而且 RubyGems 還提供了 Ruby 組件的託管服務,能夠集中式的查找和安裝 library 和 apps。當咱們使用 gem install xxx
時,會經過 rubygems.org
來查詢對應的 Gem Package。而 iOS 平常中的不少工具都是 Gem 提供的,例:Bundler
,fastlane
,jazzy
,CocoaPods
等。
在默認狀況下 Gems 老是下載 library 的最新版本,這沒法確保所安裝的 library 版本符合咱們預期。所以咱們還缺一個工具。
Bundler 是管理 Gem 依賴的工具,能夠隔離不一樣項目中 Gem 的版本和依賴環境的差別,也是一個 Gem。
Bundler 經過讀取項目中的依賴描述文件 Gemfile
,來肯定各個 Gems 的版本號或者範圍,來提供了穩定的應用環境。當咱們使用 bundle install
它會生成 Gemfile.lock
將當前 librarys 使用的具體版本號寫入其中。以後,他人再經過 bundle install
來安裝 libaray 時則會讀取 Gemfile.lock
中的 librarys、版本信息等。
Gemfile
能夠說 CocoaPods
實際上是 iOS 版的 RubyGems + Bundler 組合。Bundler 依據項目中的 Gemfile
文件來管理 Gem,而 CocoaPods
經過 Podfile 來管理 Pod。
Gemfile 配置以下:
source 'https://gems.example.com' do gem 'cocoapods', '1.8.4'是管理 Gem 依賴的工具 gem 'another_gem', :git => 'https://looseyi.github.io.git', :branch => 'master' end
可見,Podfile 的 DSL 寫法和 Gemfile 一模一樣。那什麼狀況會用到 Gemfile 呢 ?
CocoaPods
每一年都會有一些重大版本的升級,前面聊到過 CocoaPods
在 install
過程當中會對項目的 .xcodeproj
文件進行修改,不一樣版本其有所不一樣,這些在變動均可能致使大量 conflicts
,處理很差,項目就不能正常運行了。我想你必定不肯意去修改 .xcodeproj
的衝突。
若是項目是基於 fastlane
來進行持續集成的相關工做以及 App 的打包工做等,也須要其版本管理等功能。
講完了這些工具的分工,而後來講說實際的運用。咱們可使用 homebrew
+ rbenv
+ RubyGems
+ Bundler
這一整套工具鏈來控制一個工程中 Ruby 工具的版本依賴。
如下是我認爲比較可控的 Ruby 工具鏈分層管理圖。下面咱們逐一講述每一層的管理方式,以及實際的操做方法。
homebrew
安裝 rbenv
$ brew install rbenv
安裝成功後輸入 rbenv
就能夠看到相關提示:
$ rbenv rbenv 1.1.2 Usage: rbenv <command> [<args>] Some useful rbenv commands are: commands List all available rbenv commands local Set or show the local application-specific Ruby version global Set or show the global Ruby version shell Set or show the shell-specific Ruby version install Install a Ruby version using ruby-build uninstall Uninstall a specific Ruby version rehash Rehash rbenv shims (run this after installing executables) version Show the current Ruby version and its origin versions List installed Ruby versions which Display the full path to an executable whence List all Ruby versions that contain the given executable See `rbenv help <command>' for information on a specific command. For full documentation, see: https://github.com/rbenv/rbenv#readme
rbenv
管理 Ruby 版本使用 rbenv
來安裝一個 Ruby 版本,這裏我使用剛剛 release Ruby 2.7:
$ rbenv install 2.7.0
這個安裝過程有些長,由於要下載 openssl
和 Ruby 的解釋器,大概要 20 分鐘左右。
安裝成功後,咱們讓其在本地環境中生效:
$ rbenv shell 2.7.0
輸入上述命令後,可能會有報錯。rbenv
提示咱們在.zshrc
中增長一行eval "$(rbenv init -)"
語句來對rbenv
環境進行初始化。若是報錯,咱們增長並重啓終端便可。
$ ruby --version ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin19] $ which ruby /Users/gua/.rbenv/shims/ruby
切換以後咱們發現 Ruby 已經切換到 rbenv
的管理版本,而且其啓動 PATH
也已經變成 rbenv
管理下的 Ruby。而且咱們能夠看一下 Ruby 捆綁的 Gem
的 PATH
:
$ which gem /Users/bytedance/.rbenv/shims/gem
對應的 Gem
也已經變成 rbenv
中的 PATH
。
Gem
依賴如此,咱們使用 rbenv
已經對 Ruby 及其 Gem
環境在版本上進行了環境隔離。咱們能夠經過 gem list
命令來查詢當前系統環境下全部的 Gem
依賴:
$ gem list *** LOCAL GEMS *** activesupport (4.2.11.3) ... claide (1.0.3) cocoapods (1.9.3) cocoapods-core (1.9.3) cocoapods-deintegrate (1.0.4) cocoapods-downloader (1.3.0) cocoapods-plugins (1.0.0) cocoapods-search (1.0.0) cocoapods-stats (1.1.0) cocoapods-trunk (1.5.0) cocoapods-try (1.2.0)
記住這裏的 CocoaPods
版本,咱們後面項目中還會查詢。
如此咱們已經完成了所有的 Ruby、Gem 環境的配置,咱們經過一張漏斗圖再來梳理一下:
下面咱們來實踐一下,如何使用 Bundler
來鎖定項目中的 Gem
環境,從而讓整個團隊統一 Gem
環境中的全部 Ruby 工具版本。從而避免文件衝突和沒必要要的錯誤。
下面是在工程中對於 Gem
環境的層級圖,咱們能夠在項目中增長一個 Gemfile
描述,從而鎖定當前項目中的 Gem
依賴環境。
如下也會逐一講述每一層的管理方式,以及實際的操做方法。
Bundler
環境首先咱們有一個 iOS Demo 工程 - GuaDemo
:
$ ls -al total 0 drwxr-xr-x 4 gua staff 128 Jun 10 14:47 . drwxr-xr-x@ 52 gua staff 1664 Jun 10 14:47 .. drwxr-xr-x 8 gua staff 256 Jun 10 14:47 GuaDemo drwxr-xr-x@ 5 gua staff 160 Jun 10 14:47 GuaDemo.xcodeproj
首先先來初始化一個 Bundler
環境(其實就是自動建立一個 Gemfile
文件):
$ bundle init Writing new Gemfile to /Users/Gua/GuaDemo/Gemfile
Gemfile
中聲明使用的 CocoaPods
版本並安裝以後咱們編輯一下這個 Gemfile
文件,加入咱們當前環境中須要使用 CocoaPods 1.5.3
這個版本,則使用 Gemfile
的 DSL 編寫如下內容:
# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } # gem "rails" gem "cocoapods", "1.5.3"
編寫以後執行一下 bundle install
:
$ bundle install Fetching gem metadata from https://gems.ruby-china.com/............ Resolving dependencies... ... Fetching cocoapods 1.5.3 Installing cocoapods 1.5.3 Bundle complete! 1 Gemfile dependency, 30 gems now installed.
發現 CocoaPods 1.5.3
這個指定版本已經安裝成功,而且還保存了一份 Gemfile.lock
文件用來鎖存此次的依賴結果。
CocoaPods
版本操做 iOS 工程此時咱們能夠檢查一下當前 Bundler
環境下的 Gem
列表:
$ bundle exec gem list *** LOCAL GEMS *** activesupport (4.2.11.3) atomos (0.1.3) bundler (2.1.4) CFPropertyList (3.0.2) claide (1.0.3) cocoapods (1.5.3) ...
發現相比於全局 Gem
列表,這個列表精簡了許多,而且也只是基礎 Gem
依賴和 CocoaPods
的 Gem
依賴。此時咱們使用 bundle exec pod install
來執行 Install 這個操做,就可使用 CocoaPods 1.5.3
版原本執行 Pod
操做了(固然,前提是你還須要寫一個 Podfile
,你們都是 iOSer 這裏就略過了)。
$ bundle exec pod install Analyzing dependencies Downloading dependencies Installing SnapKit (5.0.1) Integrating client project [!] Please close any current Xcode sessions and use `GuaDemo.xcworkspace` for this project from now on. Sending stats Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed.
能夠再來看一下 Podfile.lock
文件:
cat Podfile.lock PODS: - SnapKit (5.0.1) DEPENDENCIES: - SnapKit (~> 5.0.0) SPEC REPOS: https://github.com/cocoapods/specs.git: - SnapKit SPEC CHECKSUMS: SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb PODFILE CHECKSUM: 1a4b05aaf43554bc31c90f8dac5c2dc0490203e8 COCOAPODS: 1.5.3
發現使用的 CocoaPods
的版本確實是 1.5.3
。而當咱們不使用 bundle exec
執行前綴,則會使用系統環境中的 CocoaPods
版本。如此咱們也就驗證了工程中的 Gem
環境和系統中的環境能夠經過 Bundler
進行隔離。
SVN
、Git
,再細分出 Git Submodule
,再到各個語言的 Package Manager
也是一直在發展的。CocoaPods
做爲包管理工具控制着 iOS 項目的各類依賴庫,但其自身一樣遵循着嚴格的版本控制並不斷迭代。但願你們能夠從本文中認識到版本管理的重要性。Bundler
管理工程的全流程,學習了 Bundler
基礎,並學習瞭如何控制一個項目中的 Gem
版本信息。後續咱們將會圍繞 CocoaPods
,從工具鏈逐漸深刻到細節,根據咱們的使用經驗,逐一講解。
這裏羅列了四個問題用來考察你是否已經掌握了這篇文章,若是沒有建議你加入收藏再次閱讀:
PM
是如何進行依賴庫的版本管理?Ruby
和 RVM/rbenv
之間的關係是什麼?Gem
、Bundler
和 CocaPods
之間的關係是什麼?Bundler
來管理工程中的 Gem
環境?如何鎖死工程內部的 CocoaPods
版本?