最近想為公司搭建cocoapods私有庫框架,老早之前做過,踩過不少坑,想不到又一次掉坑里。果真是好記性不如爛筆頭,這次得記下來。
1. 前言
cocoapods早已不是新鮮技術(shù),作為iOS平臺上的依賴管理工具,方便我們?nèi)粘i_發(fā)過程中管理和更新第三方庫?;臼褂梅绞讲辉儋樖?,這里說一下需求。項(xiàng)目規(guī)模大了之后,往往需要將各個業(yè)務(wù)功能拆分封裝,一方面解耦,另一方面可作為第三方庫供其他項(xiàng)目組使用。
所以產(chǎn)生一個需求:公司各開發(fā)團(tuán)隊(duì),將自己的業(yè)務(wù),封裝為私有庫,上傳到公司私有g(shù)it上,利用cocoapods統(tǒng)一管理。
分解需求,我們需要:
- 創(chuàng)建私有spec repo(相當(dāng)于cocoapods私有庫資源中心https://github.com/CocoaPods/Specs.git),所有的私有庫上傳記錄在私有spec中
- 工程封裝為私有庫,上傳到私有spec repo中
- 項(xiàng)目工程集成私有庫
2. 搭建
1. 創(chuàng)建私有spec repo
什么是spec repo
在Podfile中,我們通過
pod 'repoName'
來配置要加載的第三方庫。repoName對應(yīng)的repo地址并沒有寫出來,因?yàn)槲覀儠?a target="_blank" rel="nofollow">https://github.com/CocoaPods/Specs.git中去查找。它相當(dāng)于一個容器,將所有私有庫的podspec文件放到這個spec repo中,建立索引,提供私有庫配置信息。
通過pod repo list命令,查看mac 上的repo列表:
HotacooldeMacBook-Pro:0-Venus hotacool$ pod repo list
master
- Type: git (master)
- URL: https://github.com/CocoaPods/Specs.git
- Path: /Users/hotacool/.cocoapods/repos/master
本地spec repo的大致結(jié)構(gòu):
.
├── Specs
└── [SPEC_NAME]
└── [VERSION]
└── [SPEC_NAME].podspec
進(jìn)入/Users/hotacool/.cocoapods/repos/master,可以看到目錄結(jié)構(gòu):

創(chuàng)建remote&本地clone
理解了上面這個知識點(diǎn),我們知道了需要創(chuàng)建一個自己的spec repo。
- 在代碼托管平臺(github,Bitbucket,碼云等),創(chuàng)建spec repo的托管空間
例如在碼云上創(chuàng)建一個spec repo:https://gitee.com/hotacool/HACSpec.git - 利用cocoapods提供command,在terminal上運(yùn)行:
// Clones `URL` in the local spec-repos directory at `~/.cocoapods/repos/`. The
remote can later be referred to by `NAME`.
pod repo add HACSpec https://gitee.com/hotacool/HACSpec.git
會看到從remote地址git clone spec repo到本地,再次查看本地repo列表,可以看到我們創(chuàng)建的spec repo:
HotacooldeMacBook-Pro:0-Venus hotacool$ pod repo list
HACSpec
- Type: git (master)
- URL: https://gitee.com/hotacool/HACSpec.git
- Path: /Users/hotacool/.cocoapods/repos/HACSpec
master
- Type: git (master)
- URL: https://github.com/CocoaPods/Specs.git
- Path: /Users/hotacool/.cocoapods/repos/master
這一步是在本地添加私有庫source,其他開發(fā)人員Podfile需要集成私有spec上的私有庫,都需要首先通過pod repo add命令,本地clone spec repo。
另外一些命令也可能會用到:
// 查看本地spec repo列表
pod repo list
// 更新本地specName的spec repo。私有庫podspec文件更改并push到spec repo后,往往需要update
pod repo update specName
// 本地刪除spec repo
pod repo remove specName
2. 私有庫制作
創(chuàng)建一個基本(不包含代碼)的私有庫
創(chuàng)建私有庫之前,首先需要解耦獨(dú)立出單獨(dú)的組件項(xiàng)目。如果已有獨(dú)立的組件項(xiàng)目,可以通過pod spec create直接創(chuàng)建podspec文件,然后配置,生成私有庫。這里首先講一下,如何完整的創(chuàng)建一個新的私有庫,并push到我么自己的spec repo中。
官網(wǎng)上有詳細(xì)的命令解釋文檔。下面以創(chuàng)建一個名為HStockCharts的私有庫為例:
- 創(chuàng)建新的lib庫
pod lib create HStockCharts
cocoapods會clone pod-template模板來生成HStockCharts,創(chuàng)建過程中會問幾個問題:
What language do you want to use?? [ Swift / ObjC ]
> ObjC
Would you like to include a demo application with your library? [ Yes / No ]
> Yes
Which testing frameworks will you use? [ Specta / Kiwi / None ]
> None
Would you like to do view based testing? [ Yes / No ]
> No
What is your class prefix?
> H
Running pod install on your new library.
一般我會按上面代碼來生成一個帶example的例子工程,這樣生成的example相對干凈。也可以選擇自動集成testing framework,或者view based testing,這樣cocoapods會自動幫你集成一些額外的三方庫。
這樣生成的目錄結(jié)構(gòu):
HotacooldeMacBook-Pro:HStockCharts hotacool$ tree -L 2
.
├── Example
│ ├── HStockCharts
│ ├── HStockCharts.xcodeproj
│ ├── HStockCharts.xcworkspace
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Pods
│ └── Tests
├── HStockCharts
│ ├── Assets
│ └── Classes
├── HStockCharts.podspec
├── LICENSE
├── README.md
└── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj
10 directories, 5 files
HStockCharts.podspec是私有庫的配置文件;HStockCharts文件夾下是私有庫代碼文件,Assets是放資源,Classes下是編譯的源文件;Example是自動生成的測試工程。
- 編輯.podspec文件
.podspec文件是私有庫的配置文件。新創(chuàng)建的lib中,自動生成了HStockCharts.podspec,填充了一些默認(rèn)配置,我們需要手動去編輯。cocoapods是基于Ruby語音寫的,所以可以設(shè)置為Ruby語法高亮。
Pod::Spec.new do |s|
s.name = 'HStockCharts'
s.version = '0.1.0'
s.summary = 'A short description of HStockCharts.'
# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
s.homepage = 'https://github.com/shisosen@163.com/HStockCharts'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'shisosen@163.com' => 'shisosen@163.com' }
s.source = { :git => 'https://github.com/shisosen@163.com/HStockCharts.git', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.ios.deployment_target = '8.0'
s.source_files = 'HStockCharts/Classes/**/*'
# s.resource_bundles = {
# 'HStockCharts' => ['HStockCharts/Assets/*.png']
# }
# s.public_header_files = 'Pod/Classes/**/*.h'
# s.frameworks = 'UIKit', 'MapKit'
# s.dependency 'AFNetworking', '~> 2.3'
end
各標(biāo)簽(s.name等)的具體意義可以參照官方文檔。這里先列舉幾個重要的地方:
s.name //名稱,pod search 查找
s.version //版本號,私有庫git tag。如果s.source里設(shè)置了s.version.to_s,需要注意與私有庫git的tag相匹配。
s.source //重要,私有庫的remote 地址和tag。也可以:branch => 'master'設(shè)置分支。
s.source_files //#代碼源文件地址,**/*表示Classes目錄及其子目錄下所有文件;如果有多個目錄下則用逗號分開如['','','']形式
s.resource_bundles //資源文件地址
s.public_header_files //公開的頭文件地址
s.frameworks //所需的framework,多個用逗號隔開
s.dependency //依賴關(guān)系,該項(xiàng)目所依賴的其他庫,如果有多個需要填寫多個s.dependency。cocoapods會自動將依賴的其他庫集成到工程中。
現(xiàn)在只簡單的對source等配置(s.source,s.description)做一下修改:
Pod::Spec.new do |s|
s.name = 'HStockCharts'
s.version = '0.1.0'
s.summary = 'HStockCharts.'
s.description = <<-DESC
HStockCharts is a library for stock charts.
DESC
s.homepage = 'https://gitee.com/hotacool'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'hotacool' => 'shisosen@163.com' }
s.source = { :git => 'https://gitee.com/hotacool/HStockCharts.git', :tag => s.version.to_s }
s.ios.deployment_target = '8.0'
s.source_files = 'HStockCharts/Classes/**/*'
# s.resource_bundles = {
# 'HStockCharts' => ['HStockCharts/Assets/*.png']
# }
# s.public_header_files = 'Pod/Classes/**/*.h'
# s.frameworks = 'UIKit', 'MapKit'
# s.dependency 'AFNetworking', '~> 2.3'
end
- push 私有庫到remote,validation
現(xiàn)在在本地已完成私有庫的創(chuàng)建,需要將私有庫push到遠(yuǎn)程倉庫,這樣其他人才能到遠(yuǎn)程倉庫clone。這里將本地HStockCharts push到https://gitee.com/hotacool/HStockCharts.git,即podspec設(shè)置的source地址。
因?yàn)槲覀僷odspec s.source設(shè)置了:tag => s.version.to_s,version是0.1.0,所以需要打tag,注意tag應(yīng)與version保持一致:
git tag -m "release v.0.1.0 "0.1.0"
git push --tags //推送tag到遠(yuǎn)端倉庫
然后開始通過pod lib lint 對私有庫進(jìn)行validation:
HotacooldeMacBook-Pro:HStockCharts hotacool$ pod lib lint
-> HStockCharts (0.1.0)
HStockCharts passed validation.
驗(yàn)證通過。默認(rèn)情況下,如果驗(yàn)證出現(xiàn)warning,是不能通過驗(yàn)證的。可以通過添加--allow-warnings來允許warnings。
- push私有庫到spec repo
我們已經(jīng)在本地搭建好了一個私有庫,在Example中,Podfile通過相對地址,引入HStockCharts。
pod 'HStockCharts', :path => '../'
現(xiàn)在需要將私有庫推送到我們的spec repo中,從而可以從遠(yuǎn)程clone。
之前已經(jīng)在本地通過pod repo add,新加入了我們自己的spec repo: HACSpec。通過命令:
pod repo push HACSpec HStockCharts.podspec //前面是本地Repo名字 后面是podspec名字
將HStockCharts推送到spec repo。執(zhí)行pod repo update HACSpec,再cd 到本地repo文件夾/Users/hotacool/.cocoapods/repos/HACSpec,我們可以看到:
.
├── HStockCharts
│ └── 0.1.0
├── README.md
HStockCharts已從remote加入本地spec,版本號0.1.0。
- 集成私有庫
現(xiàn)在我們可以在其他項(xiàng)目中,集成我們自己的私有庫。Podfile加入:
pod 'HStockCharts'
運(yùn)行pod install,報錯[!] Unable to find a specification for HStockCharts
因?yàn)镠StockCharts在我們自己的spec repo中,默認(rèn)的source地址是cocoapods repo。Podfile頭部加入:
source 'https://gitee.com/hotacool/HACSpec.git' #private spec repo
再次pod install,成功。
同樣的,因?yàn)槲覀兊膕pec是自建的,沒有fork cocoapods的,所以不包含其他公共的私有庫,如果你用到pod 'AFNetworking',Podfile也需要添加:
source 'https://github.com/CocoaPods/Specs.git'
至此,我們已經(jīng)完整的建立了私有庫,并加入私有的spec repo。但這個庫什么都干不了,如果加入我們自己的代碼,需要做更多的配置和處理。
私有庫設(shè)置
上面的介紹,沒有涉及功能代碼,實(shí)際的私有庫配置,往往會遇到各種問題。下面對遇到的具體問題,做一些對應(yīng)。
- 私有庫依賴其他framework、library設(shè)置
解耦出的私有庫工程,往往有對某些系統(tǒng)或者第三方framework、.a靜態(tài)庫的依賴。所以需要對私有庫的podspec進(jìn)行設(shè)置,使其他工程Podfile集成私有庫時,能夠自動引入相關(guān)依賴。
我們會對下面幾個標(biāo)簽進(jìn)行設(shè)置:
s.framework = 'SystemConfiguration','CoreData' //對系統(tǒng)framework的依賴
s.libraries = 'c++','stdc++.6.0.9' //對系統(tǒng).a的依賴,注意去掉lib前綴
s.ios.vendored_libraries = 'libXXX.a' //對第三方.a的依賴,注意路徑,libXXX.a在.podspec目錄下
s.ios.vendored_frameworks = 'XXX.framework' //對第三方framework的依賴
在項(xiàng)目的Pods工程的私有庫target的build setting中,查看Other linker Flags,可以看到依賴的framework和.a:

- 包含.a的私有庫
上面說了對第三方.a的依賴,可以通過s.ios.vendored_libraries來配置。公開的header files,按照官方說明,可以再s.public_header_files中設(shè)置。但貌似沒達(dá)不到效果。目前我是通過s.source_files,將.h頭文件統(tǒng)一放在s.source_files目錄下輸出。
另一個問題,直接對包含.a的私有庫使用pod lib lint來validation是可以通過的,但pod repo push驗(yàn)證會出錯會報錯。報錯內(nèi)容類似:
- ERROR | [iOS] unknown: Encountered an unknown error (The 'Pods-App' target has transitive dependencies that include static binaries: (/private/var/folders/vx/466sl6v913z1xvbssfgpnjb00000gn/T/CocoaPods-Lint-20171220-2701-11qtxh8-MarketModule/Pods/XXXLib/libXXX.a)) during validation.
雖然這并不影響Pod的使用,但是驗(yàn)證是無法通過的??梢酝ㄟ^ --use-libraries 來讓驗(yàn)證通過:
pod repo push HACSpec MarketModule.podspec --allow-warnings --use-libraries
還有一個類似問題,如果在工程Podfile中使用use_frameworks!,會報錯:
[!] The 'Pods-Venus' target has transitive dependencies that include static binaries: (/Users/hotacool/work/0-Venus/0-Venus/Pods/SZYDataSource/libMApi.a)
這里一個可行的辦法,只能注釋掉'use_frameworks!'。參考鏈接。
- 通配符地址匹配報錯
可能會出現(xiàn)類似報錯:
- ERROR | [iOS] file patterns: The `source_files` pattern did not match any file.
一般是文件路徑無法匹配造成。例如:
s.source_files = ['HStockCharts/Classes/**/*.{c,h,hh,m,mm,cpp}' //可以匹配HStockCharts/Classes/A/A.m,但無法匹配HStockCharts/Classes/A.m。
s.source_files = ['HStockCharts/Classes/**/*.{c,h,hh,m,mm,cpp}', 'HStockCharts/Classes/*.{c,h,hh,m,mm,cpp}'] //可以實(shí)現(xiàn)上述匹配
- 私有庫對私有庫的依賴
私有庫B需要依賴私有庫A,可以在B.podspec中設(shè)置:
s.dependency 'A'
如果執(zhí)行pod lib lint 或者pod repo push 會報錯找不到A。這是肯定的,默認(rèn)的spec repo是cocoapods的公共庫,所以需要對source單獨(dú)設(shè)置:
pod lib lint --sources='https://gitee.com/hotacool/HACSpec.git'
pod repo push HACSpec B.podspec --sources='https://gitee.com/hotacool/HACSpec.git'
- 添加資源文件
有以下幾種添加資源方法:
// 1.默認(rèn)方式,cocoapods會將匹配路徑'HStockCharts/Assets/*.png’下資源打包為名稱HStockCharts的bundle,代碼中通過bundle名來查找資源。
s.resource_bundles = {
'HStockCharts' => ['HStockCharts/Assets/*.png']
}
// 調(diào)用方式:
NSBundle *bundle = [NSBundle bundleForClass:[MYSomeClass class]];
NSURL *bundleURL = [bundle URLForResource:@"HStockCharts" withExtension:@"bundle"];
NSBundle *resourceBundle = [NSBundle bundleWithURL: bundleURL];
UIImage *img = [UIImage imageNamed:icon inBundle:bundle compatibleWithTraitCollection:nil];
// 或者
UIImage *image = [UIImage imageNamed:@"HStockCharts.bundle/imagename"];
// 2.資源會在打包的時候直接拷貝的main bundle中,可能會和其它資源產(chǎn)生命名沖突
spec.resources = ["Images/*.png", "Sounds/*"]
// 3.把資源都放在bundle中,然后打包時候這個bundle會直接拷貝進(jìn)app的mainBundle中。使用的時候在mainBundle中查找這個bundle然后再搜索具體資源
spec.resource = "Resources/MYLibrary.bundle"
//調(diào)用方式:
NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"JZShare" withExtension:@"bundle"];
NSBundle *bundle = [NSBundle bundleWithURL:bundleURL];
UIImage *img = [UIImage imageNamed:icon inBundle:bundle compatibleWithTraitCollection:nil];
- pod lib lint 和 pod spec lint
兩者都是對私有庫進(jìn)行validation。不過區(qū)別是:
pod lib lint是只從本地驗(yàn)證你的pod能否通過驗(yàn)證
pod spec lint是從本地和遠(yuǎn)程驗(yàn)證你的pod能否通過驗(yàn)證
所以如果你使用pod spec lint,需要保證remote及時更新,否則會發(fā)現(xiàn)本地已經(jīng)做了修改,但一直無法validation成功...
更新維護(hù)podspec
在開發(fā)初始階段,可能會頻繁對podspec進(jìn)行更改。在pod repo push后,記得及時pod repo update specName,否則可能拉取不到最新的私有庫配置。mrc文件設(shè)置
某些文件需要設(shè)置mrc,podspec可以如下設(shè)置:
non_arc_files = 'MyLib/Classes/xxx.m'
s.exclude_files = non_arc_files
s.subspec 'no-arc' do |sp|
sp.source_files = non_arc_files
sp.requires_arc = false
end
but,不知是否是cocoapods的版本原因(1.3.1),采用這種方式我仍舊無法通過驗(yàn)證。通過--no-clean,進(jìn)入驗(yàn)證的APP工程,發(fā)現(xiàn)私有庫下的編譯文件只有non_arc_files文件。
懷疑是否默認(rèn)使用no-arc 的subspec,之后參照鏈接,設(shè)置default_subspec和subspec。但仍舊無法驗(yàn)證通過。
最后,只能通過將requires_arc設(shè)為mrc,單獨(dú)添加arc配置,來實(shí)現(xiàn)對mrc文件的編譯。如下:
#source_files需要包含mrc和arc全部文件路徑
s.source_files = ["xxx/MRC/*.{c,h,hh,m,mm,cpp}", 'xxx/Classes/**/*.{c,h,hh,m,mm,cpp}', 'xxx/Classes/*.{c,h,hh,m,mm,cpp}']
s.requires_arc = false
s.requires_arc = ['xxx/Classes/**/*.{c,h,hh,m,mm,cpp}', 'xxx/Classes/*.{c,h,hh,m,mm,cpp}']
- pod 本地repo緩存清理
私有庫開發(fā)階段,會經(jīng)常修改文件,這時候極易出現(xiàn)remote上已更新,但本地pod install/update 的私有庫還是老版本的情況。這往往是因?yàn)閜od本地緩存導(dǎo)致,需要更新本地緩存:
//1. 更新本地spec
pod repo update xxxSpec
//2. 去本地pods緩存文件夾(~/Library/Caches/CocoaPods/Pods/Release),刪除需要更新的庫
open ~/Library/Caches/CocoaPods/Pods/
3. 項(xiàng)目集成私有庫
私有庫已搭建完成,私有庫的集成方式與cocoapods公共庫集成方式無異,只需按照上述,在Podfile的source加入私有spec repo地址。無需贅述。
這里提一下本地相對路徑的集成方式。我們可以通過pod xxx,直接從remote clone私有庫到項(xiàng)目中。也可以通過pod 'HStockCharts', :path => '../'的方式,將本地庫鏈到工程中。對于需要開發(fā)的各個私有庫,可以將其clone到本地,然后本地相對路徑的方式鏈接到工程中,對私有庫進(jìn)行修改,然后push到對應(yīng)私有庫?;蛟S可以以此來解耦復(fù)雜工程,結(jié)合腳本,搭建環(huán)境,完成業(yè)務(wù)的獨(dú)立開發(fā),這也是目前我做的事情。
4. 總結(jié)
這里總結(jié)了一下基于cocoapods的私有庫搭建,歸納了一些基本的方法,記錄了一些遇到的問題。因?yàn)樾枨蟛淮?,涉及深度有限。如在后續(xù)改造優(yōu)化過程中有更多的問題出現(xiàn),也在此同步更新。