玩轉CocoaPods

紅楓國度發表於2018-01-22

作者:阿里-移動雲-大前端

CocoaPods作為iOS的依賴管理工具,已然成為iOS開發的標準工具(官方給出的資料,超過42W個庫和300W個App使用了CocoaPods)。
本篇文章,非講述CocoaPods的教學文章,而是圍繞使用CocoaPods的兩個主題:依賴管理Pod庫釋出,講述些易忽略、混淆的關鍵點和不為熟知的用法。

執行pod env,可檢視本地環境:

CocoaPods : 1.2.0
     Ruby : ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin15]
 RubyGems : 2.5.1
     Host : Mac OS X 10.13.2 (17C88)
    Xcode : 9.2 (9C40b)
      Git : git version 2.14.3 (Apple Git-98)

1. 依賴管理

1.1 Podfile

Podfile是一個說明檔案,描述一個或多個Xcode工程Target的依賴庫,類似於Maven管理依賴的pom.xml檔案。

Podfile可以很簡單,也可以很複雜。

依賴版本號

  • 依賴最新版本,不指定版本號:
pod `TestSDK`
  • 依賴指定版本,明確寫明版本號:
pod `TestSDK`, `1.0`
  • 使用邏輯運算子:
`> 1.0`,版本號大於1.0。
`>= 1.0`,版本號大於等於1.0。
`< 1.0`,版本號小於1.0。
`<= 1.0`,版本號小於等於1.0。
  • 使用optimistic operator (~>):
`~> 1.0.1`,版本號範圍:1.0.1 <= version < 1.1
`~> 1.0`,版本號範圍:1.0 <= version < 2.0
`~> 0`,版本號範圍:0 <= version,無意義

實際使用時,可根據專案需求,靈活配置依賴版本號。

Hook

Podfile中可配置鉤子函式,在依賴庫安裝過程中會被呼叫。主要有兩個Hook函式:pre_installpost_install,接收的引數為:Pod::Installer,分別對應Pods工程安裝前和安裝後。

  • pre_install配置示例如下:
pre_install do |installer|
  puts `[Test] - pre_install here`
end
  • post_install配置示例如下,Pods工程安裝後,讀取列印iOS deploy target預設配置,並將其修改為8.0。
post_install do |installer|
  puts `[Test] - post_install here`
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      puts "[Test] - config:" + config.name +  ", deploy target: " + config.build_settings[`IPHONEOS_DEPLOYMENT_TARGET`]
      config.build_settings[`IPHONEOS_DEPLOYMENT_TARGET`] = `8.0`
    end
  end
end

1.2 pod install vs. pod update

  • Podfile.lock

    • 該檔案用來記錄和追蹤生成的Pod版本。
  • pod install

    • 每次執行pod install,都會重新下載並安裝pods。
    • pods的版本號從Podfile.lock檔案中獲取:

      • 有記錄的pods,直接下載安裝該記錄版本號的pods,不檢查對應pods是否有更新;(已經安裝的pods不會更新其版本)
      • 無記錄的pods,查詢下載並安裝滿足Podfile中指定版本號條件的pods。
  • pod update

    • 完整命令為:pod update [PODNAME],執行命令後,CocoaPods會無視Podfile.lock鎖定的版本號,查詢並更新到,滿足Podfile中指定版本號條件的最新版本pods;若沒有指定PODNAME,預設更新Podfile中全部pods。
  • pod outdated

    • 滿足Podfile中指定版本號條件下,列出比Podfile.lock中記錄鎖定的版本號新的pods。
    • 實際執行pod update命令時,更新的pods即為執行pod outdated列出的pods。
  • 建議用法

    • 工程首次執行pod installpod update,執行效果一致。
    • 需要安裝新新增pod,建議執行pod installpod update [NEW_POD],已安裝的其他pods版本不變,否則可能由於版本更新的不確定性引起適配問題。
    • 明確更新某pod版本時,執行pod update [PODNAME],明確更新全部pods版本時,執行pod update

1.3 pod cache

  • pod cache list [NAME]

    • 可列出本地pods快取記錄,執行pod installpod update時,若命中快取記錄,則直接從本地拉取。
  • pod cache clean [NAME]

    • 刪除本地pods快取記錄。
    • 執行pod cache clean --all,刪除全部快取記錄。
    • 例:從私有CocoaPods倉庫拉取TestSDK v1.0.1,該記錄新增到本地快取;由於某些原因TestSDK使用同樣的版本號v1.0.1做了覆蓋釋出,可以先執行pod cache clean TestSDK,然後再執行pod update,保證拉取到最新版本的v1.0.1 SDK檔案。

2. Pod庫釋出

CocoaPods除了做依賴管理外,也會將自實現的pod庫上傳到公共倉庫/私有倉庫。

2.1 podspec

podspec檔案,即Pod Specification(Pod描述檔案),描述指定版本的pod庫資訊,包括:pod庫原始碼地址、檔案列表、配置資訊、描述資訊等。

執行pod spec create,可建立生成.podspec檔案,其為Ruby語法格式,修改後如下,例:

Pod::Spec.new do |spec|
  spec.name         = `Reachability`
  spec.version      = `3.1.0`
  spec.license      = { :type => `BSD` }
  spec.homepage     = `https://github.com/tonymillion/Reachability`
  spec.authors      = { `Tony Million` => `tonymillion@gmail.com` }
  spec.summary      = `ARC and GCD Compatible Reachability Class for iOS and OS X.`
  spec.source       = { :git => `https://github.com/tonymillion/Reachability.git`, :tag => `v3.1.0` }
  spec.source_files = `Reachability.{h,m}`
  spec.framework    = `SystemConfiguration`
end
  • 執行pod ipc spec xx.podspec,可將.podspec檔案內容從Ruby轉換為json格式:
{
  "name": "Reachability",
  "version": "3.1.0",
  "license": {
    "type": "BSD"
  },
  "homepage": "https://github.com/tonymillion/Reachability",
  "authors": {
    "Tony Million": "tonymillion@gmail.com"
  },
  "summary": "ARC and GCD Compatible Reachability Class for iOS and OS X.",
  "source": {
    "git": "https://github.com/tonymillion/Reachability.git",
    "tag": "v3.1.0"
  },
  "source_files": "Reachability.{h,m}",
  "frameworks": "SystemConfiguration"
}

2.2 私有倉庫Push

CocoaPods倉庫本質上是Git倉庫,倉庫裡儲存的是各pod庫所有版本的.podspec.podspec.json描述檔案。
pod庫上傳,即對應Git倉庫的commit提交。

Pod庫上傳到公共倉庫,即向 公共Git倉庫 提交commit。

因此,CocoaPods私有倉庫的搭建,只需再準備一個Github/Gitlab倉庫;具體搭建流程不再描述,可參考官網教程:CocoaPods – Private Pods

  • 執行pod repo push REPO [NAME.podspec]上傳Pod庫到私有倉庫,REPO為私有倉庫在本地的倉庫名。

準備上傳的Pod庫,如果對其他pod庫有依賴,需要在.podspec檔案中宣告dependency;同時執行pod repo push命令時,新增--source引數,宣告依賴要查詢的倉庫地址;支援配置多個倉庫地址,以,分隔。

例:

# 準備上傳TestSDK到私有倉庫PrivateSpec,倉庫Git座標:git@github.com/xx/xx-specs.git
# TestSDK依賴ASDK和BSDK,其中ASDK位於公共master倉庫,BSDK位於私有倉庫
# 上傳TestSDK時執行命令如下:
pod repo push PrivateSpec TestSDK.podspec --verbose --allow-warnings --source=git@github.com/xx/xx-specs.git,git@github.com:CocoaPods/Specs.git

2.3 依賴衝突

如果只從CocoaPods master倉庫拉取Pods,則不會有依賴衝突問題。依賴問題是由於引入三方私有CocoaPods倉庫導致的。
首先來看pods依賴傳遞問題。

  • Pods依賴傳遞

假設TestSDK依賴ASDK和BSDK,工程引入TestSDK後,執行pod installpod update,會將TestSDK、ASDK和BSDK一併拉取下來,這種可認為是依賴傳遞

  • Pods依賴傳遞版本號管理

假設有依賴關係如下,TestSDK使用時,ASDK必須整合1.0.1版本,CSDK和ASDK(1.0.2)不能相容。

  • TestSDK(1.0.2)

    • ASDK(1.0.2)
    • BSDK(1.0.2)
  • CSDK(1.0.1)

    • ASDK(1.0.1)

按下面的依賴配置,拉取下來的SDK版本如下,存在CSDK和ASDK不相容問題。

  • TestSDK(1.0.2)
  • ASDK(1.0.2)
  • BSDK(1.0.2)
  • CSDK(1.0.1)
pod `TestSDK`, `1.0.2`
pod `CSDK`, `1.0.1`

此時,需要顯式指定ASDK版本號,拉取下來SDK版本如下:

  • TestSDK(1.0.2)
  • ASDK(1.0.1)
  • BSDK(1.0.2)
  • CSDK(1.0.1)
pod `TestSDK`, `1.0.2`
pod `CSDK`, `1.0.1`
pod `ASDK`, `1.0.1`
  • 依賴衝突問題

存在於同時整合master公共倉庫和私有倉庫時,或整合多個私有倉庫時。

假設有兩個私有倉庫PrivateSpec1和PrivateSpec2,有SDK依賴關係如下,其中ASDK1和ASDK2是同一SDK的不同Pod封裝。

PrivateSpec1:

  • TestSDK1(1.0.0)

    • ASDK1(1.0.0)

PrivateSpec2:

  • TestSDK2(1.0.1)

    • ASDK2(1.0.1)

若同時依賴PrivateSpec1中的TestSDK1和PrivateSpec2中的TestSDK2,則ASDK1(1.0.0)和ASDK2(1.0.1)會衝突。

若Pods依賴支援類似Maven依賴的exclude,將ASDK1或ASDK2其中之一exclude,可解決該問題,但CocoaPods並不支援類似操作。

  • 方法1,可手動整合TestSDK1或TestSDK2,將ASDK1或ASDK2刪除。
  • 方法2,仍通過Pods整合,但ASDK1和ASDK2必須修改為同一Pod標識,整合時顯式指定ASDK版本號。

3. 參考