1. 程式人生 > >深入理解 CocoaPods

深入理解 CocoaPods

CocoaPods 是開發 OS X 和 iOS 應用程式的一個第三方庫的依賴管理工具。利用 CocoaPods,可以定義自己的依賴關係 (稱作 pods),並且隨著時間的變化,以及在整個開發環境中對第三方庫的版本管理非常方便。

CocoaPods 背後的理念主要體現在兩個方面。首先,在工程中引入第三方程式碼會涉及到許多內容。針對 Objective-C 初級開發者來說,工程檔案的配置會讓人很沮喪。在配置 build phases 和 linker flags 過程中,會引起許多人為因素的錯誤。CocoaPods 簡化了這一切,它能夠自動配置編譯選項。

其次,通過 CocoaPods,可以很方便的查詢到新的第三方庫。當然,這並不是說你可以簡單的將別人提供的庫拿來拼湊成一個應用程式。它的真正作用是讓你能夠找到真正好用的庫,以此來縮短我們的開發週期和提升軟體的質量。

本文中,我們將通過分析 pod 安裝 (pod install) 的過程,一步一步揭示 CocoaPods 背後的技術。

核心元件

CocoaPods是用 Ruby 寫的,並由若干個 Ruby 包 (gems) 構成的。在解析整合過程中,最重要的幾個 gems 分別是: CocoaPods/CocoaPods, CocoaPods/Core, 和 CocoaPods/Xcodeproj (是的,CocoaPods 是一個依賴管理工具 -- 利用依賴管理進行構建的!)。

編者注 CocoaPods 是一個 objc 的依賴管理工具,而其本身是利用 ruby 的依賴管理 gem 進行構建的

CocoaPods/CocoaPod

這是是一個面向使用者的元件,每當執行一個 pod 命令時,這個元件都將被啟用。該元件包括了所有使用 CocoaPods 涉及到的功能,並且還能通過呼叫所有其它的 gems 來執行任務。

CocoaPods/Core

Core 元件提供支援與 CocoaPods 相關檔案的處理,檔案主要是 Podfile 和 podspecs。

Podfile

Podfile 是一個檔案,用於定義專案所需要使用的第三方庫。該檔案支援高度定製,你可以根據個人喜好對其做出定製。更多相關資訊,請查閱 Podfile 指南

Podspec

.podspec 也是一個檔案,該檔案描述了一個庫是怎樣被新增到工程中的。它支援的功能有:列出原始檔、framework、編譯選項和某個庫所需要的依賴等。

CocoaPods/Xcodeproj

這個 gem 元件負責所有工程檔案的整合。它能夠對建立並修改 .xcodeproj.xcworkspace 檔案。它也可以作為單獨的一個 gem 包使用。如果你想要寫一個指令碼來方便的修改工程檔案,那麼可以使用這個 gem。

執行 pod install 命令

當執行 pod install 命令時會引發許多操作。要想深入瞭解這個命令執行的詳細內容,可以在這個命令後面加上 --verbose。現在執行這個命令 pod install --verbose,可以看到類似如下的內容:

$ pod install --verbose

Analyzing dependencies

Updating spec repositories
Updating spec repo `master`
  $ /usr/bin/git pull
  Already up-to-date.


Finding Podfile changes
  - AFNetworking
  - HockeySDK

Resolving dependencies of `Podfile`
Resolving dependencies for target `Pods' (iOS 6.0)
  - AFNetworking (= 1.2.1)
  - SDWebImage (= 3.2)
    - SDWebImage/Core

Comparing resolved specification to the sandbox manifest
  - AFNetworking
  - HockeySDK

Downloading dependencies

-> Using AFNetworking (1.2.1)

-> Using HockeySDK (3.0.0)
  - Running pre install hooks
    - HockeySDK

Generating Pods project
  - Creating Pods project
  - Adding source files to Pods project
  - Adding frameworks to Pods project
  - Adding libraries to Pods project
  - Adding resources to Pods project
  - Linking headers
  - Installing libraries
    - Installing target `Pods-AFNetworking` iOS 6.0
      - Adding Build files
      - Adding resource bundles to Pods project
      - Generating public xcconfig file at `Pods/Pods-AFNetworking.xcconfig`
      - Generating private xcconfig file at `Pods/Pods-AFNetworking-Private.xcconfig`
      - Generating prefix header at `Pods/Pods-AFNetworking-prefix.pch`
      - Generating dummy source file at `Pods/Pods-AFNetworking-dummy.m`
    - Installing target `Pods-HockeySDK` iOS 6.0
      - Adding Build files
      - Adding resource bundles to Pods project
      - Generating public xcconfig file at `Pods/Pods-HockeySDK.xcconfig`
      - Generating private xcconfig file at `Pods/Pods-HockeySDK-Private.xcconfig`
      - Generating prefix header at `Pods/Pods-HockeySDK-prefix.pch`
      - Generating dummy source file at `Pods/Pods-HockeySDK-dummy.m`
    - Installing target `Pods` iOS 6.0
      - Generating xcconfig file at `Pods/Pods.xcconfig`
      - Generating target environment header at `Pods/Pods-environment.h`
      - Generating copy resources script at `Pods/Pods-resources.sh`
      - Generating acknowledgements at `Pods/Pods-acknowledgements.plist`
      - Generating acknowledgements at `Pods/Pods-acknowledgements.markdown`
      - Generating dummy source file at `Pods/Pods-dummy.m`
  - Running post install hooks
  - Writing Xcode project file to `Pods/Pods.xcodeproj`
  - Writing Lockfile in `Podfile.lock`
  - Writing Manifest in `Pods/Manifest.lock`

Integrating client project

可以上到,整個過程執行了很多操作,不過把它們分解之後,再看看,會發現它們都很簡單。讓我們逐步來分析一下。

讀取 Podfile 檔案

你是否對 Podfile 的語法格式感到奇怪過,那是因為這是用 Ruby 語言寫的。相較而言,這要比現有的其他格式更加簡單好用一些。

在安裝期間,第一步是要弄清楚顯示或隱式的聲明瞭哪些第三方庫。在載入 podspecs 過程中,CocoaPods 就建立了包括版本資訊在內的所有的第三方庫的列表。Podspecs 被儲存在本地路徑 ~/.cocoapods 中。

版本控制和衝突

CocoaPods 使用語義版本控制 - Semantic Versioning 命名約定來解決對版本的依賴。由於衝突解決系統建立在非重大變更的補丁版本之間,這使得解決依賴關係變得容易很多。例如,兩個不同的 pods 依賴於 CocoaLumberjack 的兩個版本,假設一個依賴於 2.3.1,另一個依賴於 2.3.3,此時衝突解決系統可以使用最新的版本 2.3.3,因為這個可以向後與 2.3.1 相容。

但這並不總是有效。有許多第三方庫並不使用這樣的約定,這讓解決方案變得非常複雜。

當然,總會有一些衝突需要手動解決。如果一個庫依賴於 CocoaLumberjack 的 1.2.5,另外一個庫則依賴於 2.3.1,那麼只有終端使用者通過明確指定使用某個版本來解決衝突。

載入原始檔

CocoaPods 執行的下一步是載入原始碼。每個 .podspec 檔案都包含一個原始碼的索引,這些索引一般包裹一個 git 地址和 git tag。它們以 commit SHAs 的方式儲存在 ~/Library/Caches/CocoaPods 中。這個路徑中檔案的建立是由 Core gem 負責的。

CocoaPods 將依照 Podfile.podspec 和快取檔案的資訊將原始檔下載到 Pods 目錄中。

生成 Pods.xcodeproj

每次 pod install 執行,如果檢測到改動時,CocoaPods 會利用 Xcodeproj gem 元件對 Pods.xcodeproj 進行更新。如果該檔案不存在,則用預設配置生成。否則,會將已有的配置項載入至記憶體中。

安裝第三方庫

當 CocoaPods 往工程中新增一個第三方庫時,不僅僅是新增程式碼這麼簡單,還會新增很多內容。由於每個第三方庫有不同的 target,因此對於每個庫,都會有幾個檔案需要新增,每個 target 都需要:

  • 一個包含編譯選項的 .xcconfig 檔案
  • 一個同時包含編譯設定和 CocoaPods 預設配置的私有 .xcconfig 檔案
  • 一個編譯所必須的 prefix.pch 檔案
  • 另一個編譯必須的檔案 dummy.m

一旦每個 pod 的 target 完成了上面的內容,整個 Pods target 就會被建立。這增加了相同檔案的同時,還增加了另外幾個檔案。如果原始碼中包含有資源 bundle,將這個 bundle 新增至程式 target 的指令將被新增到 Pods-Resources.sh 檔案中。還有一個名為 Pods-environment.h 的檔案,檔案中包含了一些巨集,這些巨集可以用來檢查某個元件是否來自 pod。最後,將生成兩個認可檔案,一個是 plist,另一個是 markdown,這兩個檔案用於給終端使用者查閱相關許可資訊。

寫入至磁碟

直到現在,許多工作都是在記憶體中進行的。為了讓這些成果能被重複利用,我們需要將所有的結果儲存到一個檔案中。所以 Pods.xcodeproj 檔案被寫入磁碟,另外兩個非常重要的檔案:Podfile.lockManifest.lock 都將被寫入磁碟。

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,或者在一些不太明顯的地方編譯失敗。

xcproj

如果你已經依照我們的建議在系統上安裝了 xcproj,它會對 Pods.xcodeproj 檔案執行一下 touch 以將其轉換成為舊的 ASCII plist 格式的檔案。為什麼要這麼做呢?雖然在很久以前就不被其它軟體支援了,但是 Xcode 仍然依賴於這種格式。如果沒有 xcproj,你的 Pods.xcodeproj 檔案將會以 XML 格式的 plist 檔案儲存,當你用 Xcode 開啟它時,它會被改寫,並造成大量的檔案改動。

結果

執行 pod install 命令的最終結果是許多檔案被新增到你的工程和系統中。這個過程通常只需要幾秒鐘。當然沒有 Cocoapods 這些事也都可以完成。只不過所花的時間就不僅僅是幾秒而已了。

補充:持續整合

CocoaPods 和持續整合在一起非常融洽。雖然持續整合很大程度上取決於你的專案配置,但 Cocoapods 依然能很容易地對專案進行編譯。

Pods 資料夾的版本控制

如果 Pods 資料夾和裡面的所有內容都在版本控制之中,那麼你不需要做什麼特別的工作,就能夠持續整合。我們只需要給 .xcworkspace 選擇一個正確的 scheme 即可。

不受版本控制的 Pods 資料夾

如果你的 Pods 資料夾不受版本控制,那麼你需要做一些額外的步驟來保證持續整合的順利進行。最起碼,Podfile 檔案要放入版本控制之中。另外強烈建議將生成的 .xcworkspacePodfile.lock 檔案納入版本控制,這樣不僅簡單方便,也能保證所使用 Pod 的版本是正確的。

一旦配置完畢,在持續整合中執行 CocoaPods 的關鍵就是確保每次編譯之前都執行了 pod install 命令。在大多數系統中,例如 Jenkins 或 Travis,只需要定義一個編譯步驟即可 (實際上,Travis 會自動執行 pod install 命令)。對於 Xcode Bots,在書寫這篇文章時我們還沒能找到非常流暢的方式,不過我們正朝著解決方案努力,一旦成功,我們將會立即分享。

結束語

CocoaPods 簡化了 Objective-C 的開發流程,我們的目標是讓第三方庫更容易被發現和新增。瞭解 CocoaPods 的原理能讓你做出更好的應用程式。我們沿著 CocoaPods 的整個執行過程,從載入 specs 檔案和原始碼、建立 .xcodeproj 檔案和所有元件,到將所有檔案寫入磁碟。所以接下來,我們執行 pod install --verbose,靜靜觀察 CocoaPods 的魔力如何顯現。