Cocoapods是非常好用的一個iOS依賴管理工具,使用它可以方便的管理和更新項目中所使用到的第三方庫,以及將自己的項目中的公共組件交由它去管理。Cocoapods的介紹及優(yōu)點本文就不在贅述,我開始使用Cocoapods還是在6年前,那個時候它剛剛出現(xiàn),網(wǎng)上的資料還非常的少,就連他們自己的HomePage都十分的簡單,我就著手嘗試著使用了一下,用它管理起第三方庫確實是十分的方便順手。后來它有了更強大的功能就是自己創(chuàng)建podspec,更可以設(shè)置私有的庫。
這里學(xué)習(xí)一下創(chuàng)建私有的podspec并在項目中部署使用,以及pods的subspec的創(chuàng)建及使用。
整體先說明一下創(chuàng)建一個私有的podspec包括如下那么幾個步驟:
- 創(chuàng)建并設(shè)置一個私有的Spec Repo。
- 創(chuàng)建Pod的所需要的項目工程文件,并且有可訪問的項目版本控制地址。
- 創(chuàng)建Pod所對應(yīng)的podspec文件。
- 本地測試配置好的podspec文件是否可用。
- 向私有的Spec Repo中提交podspec。
- 在個人項目中的Podfile中增加剛剛制作的好的Pod并使用。
- 更新維護podspec。
在這一系列的步驟中需要創(chuàng)建兩個Git倉庫,分別是第一步和第二步(第二步不一定非要是Git倉庫,只要是可以獲取到相關(guān)代碼文件就可以,也可以是SVN的,也可以說zip包,區(qū)別就是在podspec中的source項填寫的內(nèi)容不同),并且第一步只是在初次創(chuàng)建私有podspec時才需要,之后在創(chuàng)建其他的只需要從第二步開始就可以。本文只介紹在Git環(huán)境下的操作,其他環(huán)境其他方式暫不說明。
創(chuàng)建私有Spec Repo
先來說第一步,什么是Spec Repo?它是所有的Pods的一個索引,就是一個容器,所有公開的Pods都在這個里面,它實際是一個Git倉庫remote端在GitHub上,但是當(dāng)你使用了Cocoapods后它會被clone到本地的~/.cocoapods/repos目錄下,可以進入到這個目錄看到master文件夾就是這個官方的Spec Repo了。這個master目錄的結(jié)構(gòu)是這個樣子的
.
├── Specs
└── [SPEC_NAME]
└── [VERSION]
└── [SPEC_NAME].podspec
因此我們需要創(chuàng)建一個類似于master的私有Spec Repo,這里我們可以fork官方的Repo,也可以自己創(chuàng)建,個人建議不fork,因為你只是想添加自己的Pods,沒有必要把現(xiàn)有的公開Pods都copy一份。所以創(chuàng)建一個 Git倉庫,這個倉庫你可以創(chuàng)建私有的也可以創(chuàng)建公開的,不過既然私有的Spec Repo,還是創(chuàng)建私有的倉庫吧,需要注意的就是如果項目中有其他同事共同開發(fā)的話,你還要給他這個Git倉庫的權(quán)限。因為GitHub的私有倉庫是收費的,我還不是GitHub的付費用戶,所以我使用了其他Git服務(wù),我使用的是CODING,當(dāng)然還有其他的可供選擇開源中國、Bitbucket以及CSDN Code.
創(chuàng)建完成之后在Terminal中執(zhí)行如下命令
# pod repo add [Private Repo Name] [GitHub HTTPS clone URL]
$ pod repo add WTSpecs https://coding.net/wtlucky/WTSpecs.git
此時如果成功的話進入到~/.cocoapods/repos目錄下就可以看到WTSpecs這個目錄了。至此第一步創(chuàng)建私有Spec Repo完成。
PS:如果有其他合作人員共同使用這個私有Spec Repo的話在他有對應(yīng)Git倉庫的權(quán)限的前提下執(zhí)行相同的命令添加這個Spec Repo即可。
創(chuàng)建Pod項目工程文件
這個第二步?jīng)]有什么好介紹的,如果是有現(xiàn)有的組件項目,并且在Git的版本管理下,那么這一步就算完成了,可以直接進行下一步了。
如果你的組件還在你冗余龐大的項目中,需要拆分出來或者需要自己從零開始創(chuàng)建一個組件庫,那么我建議你使用Cocoapods提供的一個工具將第二步與第三步結(jié)合起來做。
現(xiàn)在來說一下這個工具,相關(guān)的文檔介紹是Using Pod Lib Create 就拿我創(chuàng)建的podTestLibrary為例子具體講一下這里是如何操作的,先cd到要創(chuàng)建項目的目錄然后執(zhí)行
$ pod lib create podTestLibrary
之后他會問你四個問題,1.是否需要一個例子工程;2.選擇一個測試框架;3.是否基于View測試;4.類的前綴;4個問題的具體介紹可以去看官方文檔,我這里選擇的是1.yes;2.Specta/Expecta;3.yes;4.PTL。 問完這4個問題他會自動執(zhí)行pod install命令創(chuàng)建項目并生成依賴。
$ tree PodTestLibrary -L 2
PodTestLibrary
├── Example #demo APP
│ ├── PodTestLibrary
│ ├── PodTestLibrary.xcodeproj
│ ├── PodTestLibrary.xcworkspace
│ ├── Podfile #demo APP 的依賴描述文件
│ ├── Podfile.lock
│ ├── Pods #demo APP 的依賴文件
│ └── Tests
├── LICENSE #開源協(xié)議 默認MIT
├── Pod #組件的目錄
│ ├── Assets #資源文件
│ └── Classes #類文件
├── PodTestLibrary.podspec #第三步要創(chuàng)建的podspec文件
└── README.md #markdown格式的README
9 directories, 5 files
以上是項目生成的目錄結(jié)構(gòu)及相關(guān)介紹。
接下來就是向Pod文件夾中添加庫文件和資源,并配置podspec文件,我把一個網(wǎng)絡(luò)模塊的共有組件放入Pod/Classes中,然后進入Example文件夾執(zhí)行pod update命令,再打開項目工程可以看到,剛剛添加的組件已經(jīng)在Pods子工程下Development Pods/PodTestLibrary中了,然后編輯demo工程,測試組件,我并沒有使用提供的測試框架進行測試,這里就先不介紹了。
注:這里需要注意的是每當(dāng)你向Pod中添加了新的文件或者以后更新了podspec的版本都需要重新執(zhí)行一遍pod update命令。
測試無誤后需要將該項目添加并推送到遠端倉庫,并編輯podspec文件。
通過Cocoapods創(chuàng)建出來的目錄本身就在本地的Git管理下,我們需要做的就是給它添加遠端倉庫,同樣去GitHub或其他的Git服務(wù)提供商那里創(chuàng)建一個私有的倉庫,拿到SSH地址,然后cd到PodTestLibrary目錄
$ git add .
$ git commit -s -m "Initial Commit of Library"
$ git remote add origin git@coding.net:wtlucky/podTestLibrary.git #添加遠端倉庫
$ git push origin master #提交到遠端倉庫
注意:如果git push origin master失敗,那么我們可以先排查下當(dāng)前庫的分支有哪些:
$ git branch -limage.png由上圖可見,本地只有main分支。到github倉庫查看,遠程主機也只有main分支。
我們這里將git push origin master命令改為 git push origin main即可$ git push origin main #提交到遠端倉庫
做完這些就可以開始編輯podspec文件了,它是一個Ruby的文件,把編輯器的格式改成Ruby就能看到語法高亮,下面我貼上我的podspec文件,并在后面以注釋的形式說明每個字段的含義,沒有涉及到的字段可以去官方文檔查閱
# # 井號可以在 podspec 文件中添加注釋說明
# s 代表一級目錄文件相關(guān)屬性
# ss 代表二級(子級)文件目錄相關(guān)屬性
Pod::Spec.new do |s|
# 開源庫文件名稱
s.name = 'LXKBaseKit'
# 庫文件當(dāng)前版本號,必須保證此處版本號和 GitHub(也可能不是碼云之類的代碼托管平臺)中的 tag 版本號保持一致,否則無法提交成功
s.version = '1.0.0'
# 遵循的開源協(xié)議
s.license = { :type => 'MIT', :file => 'LICENSE' }
# 開源庫的簡要介紹,會在通過 pod search 指令搜索時顯示出來的
s.summary = 'A Library for iOS to get result fasterly with some methods.'
# 開源庫的主頁地址,如果是私有庫就填私有庫的主頁地址
s.homepage = 'https://github.com/lxkboy/LXKBaseKit'
# 開源庫作者信息
s.authors = { 'luoxiankang' => '773638256@qq.com' }
# 項目地址(此處建議使用 :tag => s.version),如果是私有庫就填私有庫的地址
s.source = { :git => 'https://github.com/lxkboy/LXKBaseKit', :tag => s.version }
# 支持的版本號
s.platform = :ios, '10.0'
# pod 支持的開源庫語言最低版本號
s.ios.deployment_target = '10.0'
# 是否支持 ARC
s.requires_arc = false
# 開放共用頭文件地址
s.public_header_files = 'LXKBaseKitDemo/LXKBaseKit/LXKBaseKitHeader.h'
# 頭文件地址
s.source_files = 'LXKBaseKitDemo/LXKBaseKit/LXKBaseKitHeader.h'
# 資源包地址(建議使用 bundle 資源包形式)
s.resource = 'LXKBaseKitDemo/LXKBaseKit/LXKBaseKitResource/LXKBaseKit.bundle'
# 遵循的公共頭文件
s.prefix_header_contents = '#import <UIKit/UIKit.h>', '#import <Foundation/Foundation.h>'
# 一級子目錄結(jié)構(gòu)
s.subspec 'LXKBaseKitOthers' do |ss|
ss.source_files = 'LXKBaseKitDemo/LXKBaseKit/LXKBaseKit Others/**/*.{h,m}'
end
s.subspec 'LXKBaseKitDefine' do |ss|
ss.source_files = 'LXKBaseKitDemo/LXKBaseKit/LXKBaseKitDefine/**/*.{h,m}'
ss.public_header_files = 'LXKBaseKitDemo/LXKBaseKit/LXKBaseKitDefine/**/*.{h}'
end
s.subspec 'LXKBaseKitObject' do |ss|
ss.source_files = 'LXKBaseKitDemo/LXKBaseKit/LXKBaseKitObject/**/*.{h,m}'
ss.public_header_files = 'LXKBaseKitDemo/LXKBaseKit/LXKBaseKitObject/**/*.{h}'
# pod成功的庫,每個子文件夾都是對應(yīng)一個子庫,子庫的目的是為了防止一個 kit 太大,把功能模塊都分出來減少包體積;所以子庫原則上是不進行相互依賴的;如果庫中有必須依賴的話,可以通過該方式進行依賴;其中依賴的對應(yīng)為 pod 成功后顯示的庫路徑,非真實路徑
ss.dependency 'LXKBaseKit/LXKBaseKitDefine'
end
s.subspec 'LXKBaseKitCategory' do |ss|
ss.source_files = 'LXKBaseKitDemo/LXKBaseKit/LXKBaseKitCategory/**/*.{h,m}'
ss.public_header_files = 'LXKBaseKitDemo/LXKBaseKit/LXKBaseKitCategory/**/*.{h}'
ss.dependency 'LXKBaseKit/LXKBaseKitDefine'
ss.dependency 'LXKBaseKit/LXKBaseKitObject'
end
# 二級(子級)目錄結(jié)構(gòu)(注:ss 可以換成任意非 s 的名稱,即子級目錄代稱)
s.subspec 'LXKBaseKitView' do |ss|
# 子級目錄下所有文件(* 代表通配符)
ss.source_files = 'LXKBaseKitDemo/LXKBaseKit/LXKBaseKitView/**/*.{h,m}'
ss.public_header_files = 'LXKBaseKitDemo/LXKBaseKit/LXKBaseKitView/**/*.{h}'
# 子級目錄下文件需要的依賴文件(如果需要依賴多個,官方寫法是分別寫出,且子級目錄依賴只能單方向依賴,不能相互依賴)
ss.dependency 'LXKBaseKit/LXKBaseKitDefine'
ss.dependency 'LXKBaseKit/LXKBaseKitObject'
ss.dependency 'LXKBaseKit/LXKBaseKitCategory'
end
# 依賴第三方庫文件
s.dependency 'MBProgressHUD', '1.1.0'
# 依賴系統(tǒng)靜態(tài)庫文件
s.framework = 'UIKit', 'Foundation', 'QuartzCore', 'CoreText', 'CoreGraphics'
end
1、當(dāng)項目工程代碼完成后,需要先將代碼提交到遠程端,并打上 tag 標(biāo)簽
$ git commit -m "提交文件日志" # 提交所有更新過的文件
$ git tag # 查詢當(dāng)前已存在的 tag 標(biāo)簽
$ git tag 1.0.0 # 添加 tag 本地標(biāo)簽(必須)
$ git push --tags # 提交所有 tag 標(biāo)簽到遠程端(必須)
#$ git tag -d 1.0.0 # 刪除 tag 本地標(biāo)簽
#$ git push origin :refs/tags/1.0.0 # 刪除遠程端指定 tag 標(biāo)簽
2、驗證 podpsec 文件的合法性
提交tag后,需要驗證一下這個文件是否可用,如果有任何WARNING或者ERROR都是不可以的,它就不能被添加到Spec Repo中,不過xcode的WARNING是可以存在的,驗證需要執(zhí)行一下命令
出現(xiàn) passed validation 代表驗證成功
一下是一些本地和遠程單的驗證方法,本地驗證通過后再驗證遠程單即可:
## pod lib lint 從本地驗證 pod 能否通過驗證
## pod spec lint 從本地和遠程端驗證 pod 能否通過驗證
## pod spec lint --verbose 詳細編譯驗證 podspec 合法性
## --allow-warnings 帶有警告需要的語法
## --use-libraries 依賴三方庫需要的語法(比如:s.dependency 'MBProgressHUD', '1.1.0')
## 從本地驗證 pod 能否通過驗證
pod lib lint LXKBaseKit.podspec
## 從本地驗證依賴三方庫的 pod 能否通過驗證
pod lib lint LXKBaseKit.podspec --use-libraries
## 從本地驗證帶有警告的 pod 能否通過驗證
pod lib lint LXKBaseKit.podspec --allow-warnings
## 從本地驗證依賴三方庫且有警告的 pod 能否通過驗證
pod lib lint LXKBaseKit.podspec --use-libraries --allow-warnings
## 從本地和遠程端驗證 pod 能否通過驗證
pod spec lint LXKBaseKit.podspec
## 從本地和遠程端驗證依賴三方庫的 pod 能否通過驗證
pod spec lint LXKBaseKit.podspec --use-libraries
## 從本地驗和遠程端證帶有警告的 pod 能否通過驗證
pod spec lint LXKBaseKit.podspec --allow-warnings
## 從本地和遠程端驗證依賴三方庫且有警告的 pod 能否通過驗證
pod spec lint LXKBaseKit.podspec --use-libraries --allow-warnings
## 詳細編譯驗證 podspec 合法性
pod spec lint --verbose
a、開始先驗證本地pod的合法性,如果有waring,可以帶命令 --allow-warnings
$ pod lib lint
-> LXKBaseKit (0.1.0)
LXKBaseKit passed validation.
如果報錯:- ERROR | [iOS] xcodebuild: Returned an unsuccessful exit code.
那么我們需要在.podspec文件中添加如下代碼:s.pod_target_xcconfig = { 'VALID_ARCHS' => 'x86_64 armv7 arm64' }
b、驗證遠程端的合法性
pod spec lint LXKBaseKit.podspec --allow-warnings
LXKBaseKit.podspec passed validation.
證明遠程驗證通過
c、驗證成功后,將庫文件推送到服務(wù)器:
向Spec Repo提交podspec
向Spec Repo提交podspec需要完成兩點一個是podspec必須通過驗證無誤,在一個就是刪掉無用的注釋(這個不是必須的,為了規(guī)范還是刪掉吧)。 向我們的私有Spec Repo提交podspec只需要一個命令
$ pod repo push WTSpecs PodTestLibrary.podspec --allow-warnings #前面是本地Repo名字 后面是podspec名字
完成之后這個組件庫就添加到我們的私有Spec Repo中了,可以進入到~/.cocoapods/repos/WTSpecs目錄下查看
.
├── LICENSE
├── PodTestLibrary
│ └── 0.1.0
│ └── PodTestLibrary.podspec
└── README.md
再去看我們的Spec Repo遠端倉庫,也有了一次提交,這個podspec也已經(jīng)被Push上去了。
至此,我們的這個組件庫就已經(jīng)制作添加完成了,使用pod search命令就可以查到我們自己的庫了
$ pod search PodTestLibrary
-> PodTestLibrary (0.1.0)
Just Testing.
pod 'PodTestLibrary', '~> 0.1.0'
- Homepage: https://coding.net/u/wtlucky/p/podTestLibrary
- Source: https://coding.net/wtlucky/podTestLibrary.git
- Versions: 0.1.0 [WTSpecs repo]
這里說的是添加到私有的Repo,如果要添加到Cocoapods的官方庫了,可以使用trunk工具,具體可以查看官方文檔。
對于私有庫的特別處理
對于私有庫,在驗證和推送到spec時可能會出現(xiàn)失敗,這里需要特殊處理
在索引庫驗證 pod lib lint 時
正常的做法是執(zhí)行pod spec lint --verbose --allow-warnings ,但是如果引用的依賴庫既有g(shù)ithub官網(wǎng)庫,又有自己的服務(wù)器git庫時,需要指定兩個url地址
pod lib lint --sources='http://192.168.***.***/r/***frame/xx_repo.git,https://github.com/CocoaPods/Specs.git' --private --allow-warnings
在推送索引庫到Spec Repo時
和上面的驗證原則一樣
pod repo push <本地索引庫> <索引文件名> --sources='http://192.168.***.***/r/***frame/xx_repo.git,https://github.com/CocoaPods/Specs.git' --verbose --allow-warnings
使用制作好的Pod
在完成這一系列步驟之后,我們就可以在正式項目中使用這個私有的Pod了只需要在項目的Podfile里增加以下一行代碼即可
$ pod 'PodTestLibrary', '~> 0.1.0'
然后執(zhí)行pod update,更新庫依賴,然后打卡項目可以看到,我們自己的庫文件已經(jīng)出現(xiàn)在Pods子項目中的Pods子目錄下了,而不再是Development Pods。
更新維護podspec
最后再來說一下制作好的podspec文件后續(xù)的更新維護工作,比如如何添加新的版本,如何刪除Pod。
我已經(jīng)制作好了PodTestLibrary的0.1.0版本,現(xiàn)在我對他進行升級工作,這次我添加了更多的模塊到PodTestLibrary之中,包括工具類,底層Model及UIKit擴展等,這里又嘗試了一下subspec功能,給PodTestLibrary創(chuàng)建了多個子分支。
具體做法是先將源文件添加到Pod/Classes中,然后按照不同的模塊對文件目錄進行整理,因為我有四個模塊,所以在Pod/Classes下有創(chuàng)建了四個子目錄,完成之后繼續(xù)編輯之前的PodTestLibrary.podspec,這次增加了subspec特性
Pod::Spec.new do |s|
s.name = "PodTestLibrary"
s.version = "1.0.0"
s.summary = "Just Testing."
s.description = <<-DESC
Testing Private Podspec.
* Markdown format.
* Don't worry about the indent, we strip it!
DESC
s.homepage = "https://coding.net/u/wtlucky/p/podTestLibrary"
# s.screenshots = "www.example.com/screenshots_1", "www.example.com/screenshots_2"
s.license = 'MIT'
s.author = { "wtlucky" => "wtlucky@foxmail.com" }
s.source = { :git => "https://coding.net/wtlucky/podTestLibrary.git", :tag => "1.0.0" }
# s.social_media_url = 'https://twitter.com/'
s.platform = :ios, '7.0'
s.requires_arc = true
#s.source_files = 'Pod/Classes/**/*'
#s.resource_bundles = {
# 'PodTestLibrary' => ['Pod/Assets/*.png']
#}
#s.public_header_files = 'Pod/Classes/**/*.h'
s.subspec 'NetWorkEngine' do |networkEngine|
networkEngine.source_files = 'Pod/Classes/NetworkEngine/**/*'
networkEngine.public_header_files = 'Pod/Classes/NetworkEngine/**/*.h'
networkEngine.dependency 'AFNetworking', '~> 2.3'
end
s.subspec 'DataModel' do |dataModel|
dataModel.source_files = 'Pod/Classes/DataModel/**/*'
dataModel.public_header_files = 'Pod/Classes/DataModel/**/*.h'
end
s.subspec 'CommonTools' do |commonTools|
commonTools.source_files = 'Pod/Classes/CommonTools/**/*'
commonTools.public_header_files = 'Pod/Classes/CommonTools/**/*.h'
commonTools.dependency 'OpenUDID', '~> 1.0.0'
end
s.subspec 'UIKitAddition' do |ui|
ui.source_files = 'Pod/Classes/UIKitAddition/**/*'
ui.public_header_files = 'Pod/Classes/UIKitAddition/**/*.h'
ui.resource = "Pod/Assets/MLSUIKitResource.bundle"
ui.dependency 'PodTestLibrary/CommonTools'
end
s.frameworks = 'UIKit'
#s.dependency 'AFNetworking', '~> 2.3'
#s.dependency 'OpenUDID', '~> 1.0.0'
end
因為我們創(chuàng)建了subspec所以項目整體的依賴dependency,源文件source_files,頭文件public_header_files,資源文件resource等都移動到了各自的subspec中,每個subspec之間也可以有相互的依賴關(guān)系,比如UIKitAddition就依賴于CommonTools。
編輯完成之后,在測試項目里pod update一下,幾個子項目都被加進項目工程了,寫代碼驗證無誤之后,就可以將這個工程push到遠端倉庫,并打上新的tag->1.0.0。
最后再次使用pod lib lint驗證編輯好的podsepc文件,沒有自身的WARNING或者ERROR之后,就可以再次提交到Spec Repo中了,命令跟之前是一樣的
$ pod repo push WTSpecs PodTestLibrary.podspec
之后再次到~/.cocoapods/repos/WTSpecs目錄下查看
.
├── LICENSE
├── PodTestLibrary
│ ├── 0.1.0
│ │ └── PodTestLibrary.podspec
│ └── 1.0.0
│ └── PodTestLibrary.podspec
└── README.md
3 directories, 4 files
已經(jīng)有兩個版本了,使用pod search查找得到的結(jié)果為
$ pod search PodTestLibrary
-> PodTestLibrary (1.0.0)
Just Testing.
pod 'PodTestLibrary', '~> 1.0.0'
- Homepage: https://coding.net/u/wtlucky/p/podTestLibrary
- Source: https://coding.net/wtlucky/podTestLibrary.git
- Versions: 1.0.0, 0.1.0 [WTSpecs repo]
- Sub specs:
- PodTestLibrary/NetWorkEngine (1.0.0)
- PodTestLibrary/DataModel (1.0.0)
- PodTestLibrary/CommonTools (1.0.0)
- PodTestLibrary/UIKitAddition (1.0.0)
完成這些之后,在實際項目中我們就可以選擇使用整個組件庫或者是組件庫的某一個部分了,對應(yīng)的Podfile中添加的內(nèi)容為
platform :ios, '7.0'
pod 'PodTestLibrary/NetWorkEngine', '1.0.0' #使用某一個部分
pod 'PodTestLibrary/UIKitAddition', '1.0.0'
pod 'PodTestLibrary', '1.0.0' #使用整個庫
最后介紹一下如何刪除一個私有Spec Repo,只需要執(zhí)行一條命令即可
$ pod repo remove WTSpecs
這樣這個Spec Repo就在本地刪除了,我們還可以通過
$ pod repo add WTSpecs git@coding.net:wtlucky/WTSpecs.git
再把它給加回來。
如果我們要刪除私有Spec Repo下的某一個podspec怎么操作呢,此時無需借助Cocoapods,只需要cd到~/.cocoapods/repos/WTSpecs目錄下,刪掉庫目錄
wtlucky@wtluckydeMacBook-Pro:~/.cocoapods/repos/WTSpecs$ rm -Rf PodTestLibrary
然后在將Git的變動push到遠端倉庫即可
wtlucky@wtluckydeMacBook-Pro:~/.cocoapods/repos/WTSpecs$ git add --all .
wtlucky@wtluckydeMacBook-Pro:~/.cocoapods/repos/WTSpecs$ git ci -m "remove unuseful pods"
wtlucky@wtluckydeMacBook-Pro:~/.cocoapods/repos/WTSpecs$ git push origin master
