使用CocoaPods也有很長一段時間了,最近幾個月的時間里也主導了公司私有Pods的創建和使用。在此期間踩過了不少坑,在踩坑的過程中也收獲了不少經驗,更加熟練地掌握了CocoaPods的一些指令的使用。本篇作為這段時間收獲的備忘。
一、CocoaPods簡介
CocoaPods是專門為iOS工程提供第三方依賴庫的管理工具,通過CocoaPods,我們可以更方便地管理每個第三方庫的版本,而且不需要我們做太多的配置,就可以直觀、集中和自動化地管理我們項目的第三方庫。
CocoaPods將所有依賴的庫都放在一個名為Pods的項目下,然后讓主項目依賴Pods項目。然后,我們編碼工作都從主項目轉移到Pods項目。Pods項目最終會編譯為一個libPod-項目名.a靜態庫,主項目依賴于這個靜態庫。
對于資源文件,CocoaPods 提供了一個名為 Pods-resources.sh 的 bash 腳本,該腳本在每次項目編譯的時候都會執行,將第三方庫的各種資源文件復制到目標目錄中。
CocoaPods 通過一個名為 Pods.xcconfig 的文件來在編譯時設置所有的依賴和參數。
CocoaPods是用 Ruby 寫的,并由若干個 Ruby 包 (gems) 構成的。在解析整合過程中,最重要的幾個 gems 分別是: CocoaPods/CocoaPods, CocoaPods/Core, 和 CocoaPods/Xcodeproj。
CocoaPod的核心組件
CocoaPods/CocoaPod
這是一個面向用戶的組件,每當執行一個 pod 命令時,這個組件都將被激活。該組件包括了所有使用 CocoaPods 涉及到的功能,并且還能通過調用所有其它的 gems 來執行任務。CocoaPods/Core
Core 組件提供支持與 CocoaPods 相關文件的處理,文件主要是 Podfile 和 podspecs。Podfile
Podfile 是一個文件,用于定義項目所需要使用的第三方庫。該文件支持高度定制,你可以根據個人喜好對其做出定制。更多相關信息,請查閱 Podfile 指南。Podspec
.podspec 也是一個文件,該文件描述了一個庫是怎樣被添加到工程中的。它支持的功能有:列出源文件、framework、編譯選項和某個庫所需要的依賴等。CocoaPods/Xcodeproj
這個 gem 組件負責所有工程文件的整合。它能夠創建并修改 .xcodeproj 和 .xcworkspace 文件。它也可以作為單獨的一個 gem 包使用。如果你想要寫一個腳本來方便地修改工程文件,那么可以使用這個 gem。
CocoaPods的安裝和配置,以及Podfile中第三方庫引用的語法規則(特別是版本號的語法格式)這里就不贅述了,下面挑重點講一講。
二、多target時Podfile該如何寫?
我的建議是使用Ruby語法,定義不同的分組,然后不同的target可以自由選擇依賴哪些分組,這種方式看起來更簡潔,對于多target的項目來說也更友好:
platform :ios, '8.0'
def commonPods #通用pods集
pod 'AFNetworking', '~> 2.0'
pod 'Masonry'
end
def appOnlyPods #app專用pods集
pod 'MBProgressHUD'
end
def extensionPods #擴展專用pods集
pod 'GTSDKExtension'
end
target :TestCocoaPods do
commonPods
appOnlyPods
target :TestCocoaPodsTests do
inherit! :search_paths
# Pods for testing
end
target :TestCocoaPodsUITests do
inherit! :search_paths
# Pods for testing
end
end
target :SecondTarget do
commonPods
end
三、如何忽略Pods警告?
有些第三方Pod集成進來會有一大堆警告信息,如果你看著比較難受想把它忽略的話,在Podfile中對應的target或分組下加上關鍵字inhibit_all_warnings
即可。
四、如何直接引用第三方庫中的頭文件?
在用CocoaPods集成第三方庫之后,默認情況下,我們需要使用類似#import <XXX/YYY.h>
的方式引入第三方庫的頭文件。我們可以在Build Settings -> User Header Search Paths中添加${SRCROOT}
并設置成recursive,這樣我們就可以直接使用#impot "YYY.h"
這種方式了。
五、pod install
or pod update
?
如官方文檔所說,pod install
和 pod update
確實是大家最容易搞混的兩條指令,很多人還沒搞清楚這兩條指令的區別,反正不管三七二十一上來就是一個pod update
,大家一定要搞清楚這兩條指令的區別。
按照官方文檔所說,pod install
在第一次檢索集成第三方以及每一次在Podfile中新增、更改或刪除pod的時候使用。每一次執行pod install
命令,它都會下載安裝新的pod,并且會把每一個安裝的pod的版本信息寫入Podfile.lock文件。Podfile.lock文件跟蹤每一個安裝的pod的版本并且上鎖。每一次執行pod install
命令,只解決還沒有在Podfile.lock中列出的依賴:對于已在Podfile.lock中列出的pod,會下載指定的版本,不會檢查是否有新版本。對于沒有在Podfile.lock中列出的pod,它會搜索并安裝Podfile中指定的版本。
直接執行pod update
命令會檢查安裝Podfile中列出的所有pod的新版本(往往比較慢)。
執行pod update PODNAME
命令會檢查PODNAME的新版本(不考慮Podfile.lock中記錄的版本信息),它會把PODNAME更新為最新版本,只要跟Podfile中指定的版本匹配。也就是說,pod update PODNAME
將PODNAME更新到Podfile中指定的版本,可以是更新到老版本也可以是更新到新版本,取決于Podfile。(比如:如果此時Podfile中指定了pod 'AFNetworking', '~> 2.0'
,此時執行pod update AFNetworking
并不會把AFNetworking更新到最新版本(因為此時的版本滿足大于等于2.0版),必須先修改Podfile中的版本信息才會更新到指定版本)。
兩者的區別:
用
pod install
命令來安裝新的pod,每次在Podfile中新增和刪除pod都使用pod install
命令。在Podfile中添加新的pod后應該用
pod install
命令,而不是pod update
命令。通過pod install
命令安裝新的pod而不用擔心在同一進程中修改已有的pod。pod update
命令僅用在更新指定pod到指定版本或者更新所有pod。
我的建議是:該用pod install
的時候不要用pod update PODNAME
。另外,盡量不要用pod update
,因為它是全部檢查一遍,不僅慢有時候還會出現坑。比如有一個依賴的第三方庫本來是2.0版本的用的好好的,因為它是國外的資源,下載起來非常慢,我們在沒有bug的情況下是不希望輕易去更新它的,那么如果你上來就是一個pod update
指令,OK, 如果你Podfile中指定了每次使用最新版本(不指定版本號),那么CocoaPods就會去下載最新的這個第三方庫,那在下載完成之前你還要不要做其他事情了?這還是情況好的,如果這個最新的版本一直下載失敗,所以一直集成失敗怎么辦?
六、如何創建私有Pod?
要創建私有Pod,首先我們需要兩個私有倉庫,一個放私有Pod源碼,一個放私有Pod的說明書(類似公有Pod的CocoaPods/Specs)。
1、添加私有Spec倉庫到本地
pod repo add privateSpecs your_privateSpecs.git
如果執行成功,之后便可以通過pod repo list
命令查看本地Spec倉庫列表,正常情況下會有一個公有的CocoaPods官方的master repo 和你的 privateSpecs repo,并可以看到它們在本地的存放路徑(其實在~/.cocoapods/repos
目錄下)。
2、創建私有Pod
在私有Pod代碼所在文件夾下執行pod spec create your_podName
在該目錄下創建一個your_podName.podspec說明書文件。之后的工作就是編輯這個說明書文件了,這里簡單注明一下規則:
Pod::Spec.new do |s|
s.name = "ATCategory"
s.version = "0.0.1"
s.summary = "共用擴展類集合"
s.description = <<-DESC
大家如果需要用到擴展,都使用這里已有的擴展啦。
DESC
s.homepage = "your_privatePodGit_address/ATCategory"
s.author = { "ApesTalk" => "https://github.com/apestalk" }
s.platform = :ios
s.platform = :ios, "8.0"
s.source = { :git => "your_privatePodGit_address", :tag => "#{s.version}"
# 如果你有多個私有Pod放在一個倉庫里,你可以修改tag像下面這樣,對應打tag的時候的規則就對應需要變成PodName-v0.0.1這樣子了
# s.source = { :git => "your_privatePodGit_address", :tag => s.name + "-v"+"#{s.version}"
}
s.source_files = 'ATCategory/**/*'
s.public_header_files = 'ATCategory/Category/*.h'
s.requires_arc = true
s.frameworks = 'UIKit','Foundation'
# 依賴的系統library,這里是指系統的類似libz.tbd、libxml2.tbd這類的系統庫
# s.library = 'z' // 單個
# s.libraries = 'z','xml2' // 多個
# 第三方.a
# s.vendored_libraries =
# 第三方frameworks文件
# s.vendored_frameworks =
# 依賴關系,該項目所依賴的其他庫,如果有多個需要填寫多個s.dependency
# s.dependency 'AFNetworking', '~> 2.3'
# 資源文件地址
# s.resource_bundles = {
# 'ATCategory' => ['ATCategory/Images/*.png']
# }
end
3、提交源代碼并打tag
注意這里tag必須跟podspec文件中的tag保持一致,因為CocoaPods是通過podspec文件中的tag去找源文件的,如果tag對應不起來就會驗證失敗。打好tag提交到遠端。
4、驗證podspec文件合法性和可選參數
有兩種驗證方式,一種是本地驗證pod lib lint your_podName.podspec
和聯網驗證pod spec lint your_podName.podspec
。建議大家都用聯網驗證。
這里可選參數有:
-
--allow-warnings
:允許警告 -
--sources=‘master,privateSpecs
:指定源,比如你的私有pod同時依賴了公有庫和私有庫,你必須指定源才行,因為默認只會去在公有源中查找對應的依賴 -
--use-libraries
:如果使用了靜態庫,記得加上它
5、提交說明書文件到私有說明書庫
pod repo push privateSpecs your_podName.podspec
,同樣的加上上面驗證時使用到的可選參數。
6、如何使用素材?
官方建議pod中的素材用bundle的形式避免和主項目中的文件名發生沖突,那么集成后我們如何使用bundle中的素材呢?
NSBundle *bundle = [NSBundle mainBundle];
//NSBundle *bundle = [NSBundle bundleForClass:[ClassFromPodspec class]];//對于靜態庫,拿到的是mainBundle,如果是動態庫,拿到的是類所在的bundle。使用動態庫需要在Podfile中開啟use_frameworks!
NSURL *wttpodBundleURL = [bundle URLForResource:@"WTTPod" withExtension:@"bundle"];
NSBundle *wttpodBundle = [NSBundle bundleWithURL: wttpodBundleURL];
UIImage *img = [UIImage imageNamed:@"Chat_checkin_empty_stu" inBundle:wttpodBundle compatibleWithTraitCollection:nil];
7、關于subspec
如果我們的pod中文件比較多,而我們又希望能像AFNetworking那樣集成后分幾個物理文件夾(默認會把所有文件都放在一個物理文件夾下,文件太多會顯得很亂),那么就要用到subspec來把我們的pod分成幾個獨立的子模塊。
s.subspec '子模塊名稱' do |別名,不能和子模塊名稱相同,比如ss|
ss.source_files = ''
end
具體怎么用,大家可以參考AFNetworking.podspec文件中的寫法。
七、如何使用私有庫
如果我們同時使用了公有庫和私有庫,我們只需要在Podfile的頭部同時把公有庫和私有庫的source加上即可。
八、pod search
搜不到私有庫?或者搜得到pod install
失敗?
在提交私有庫說明書之后,先執行一下pod repo update privateSpecs
,然后再集成。
九、集成某一個pod速度過慢,比如MobileVLCKit總是下載失敗
把對應版本的MobileVLCKit下載下來放在訪問速度更快的地方(比如內網服務器或者本機用Python開啟一個FTP服務)在本機master repo源中搜索找到對應版本的MobileVLCKit.podspec.json文件,把其中的source改成我們存放MobileVLCKit.tar.xz文件地址,之后再執行相關指令集成。
如何利用Python開啟一個本地FTP服務:
cd到要共享的目錄下,執行python -m SimpleHTTPServer 8000
,之后同一個局域網內就可以通過本機ip:8000
訪問到該共享文件夾了。
"http":"http://192.168.210.111:8000/MobileVLCKit-3.1.2-bf58e19-37855b857a.tar.xz"
獲取本機IP地址的方法:按住Option的同時點下Mac菜單欄的無線網Icon,在下拉列表中即可看到IP地址。也可以在終端中輸入ifconfig en0
命令查看。
十、驗證podspec時,報錯 symbol(s) not found for architecture i386
檢查一下是否私有Pod中使用到的什么文件不支持i386架構,比如什么.a文件。
解決辦法:在podspec文件中指定支持的架構
valid_archs = ['armv7s','arm64',]
s.xcconfig = {
'VALID_ARCHS' => valid_archs.join(' '),
}
s.pod_target_xcconfig = {
'ARCHS[sdk=iphonesimulator*]' => '$(ARCHS_STANDARD_64_BIT)'
}
同樣的,如果在驗證過程中遇到xcodebuild: Returned an unsuccessful exit code.卻不報具體的錯,去看看NOTE類型的信息,如果看到missing required architecture i386 in file此類消息,說明某個.a或者frawork不支持i386架構,需要在podspec文件中寫明該pod支持哪些架構。
十一、pod init
失敗?
用Xcode9.4.1新建一個項目,然后執行pod init
(Cocoapods1.4.0版本)時提示失敗,錯誤提示如下:
――― MARKDOWN TEMPLATE ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
### Command
/usr/local/Cellar/cocoapods/1.4.0/libexec/bin/pod init
### Report
* What did you do?
* What did you expect to happen?
* What happened instead?
### Stack
CocoaPods : 1.4.0
Ruby : ruby 2.3.3p222 (2016-11-21 revision 56859) [universal.x86_64-darwin17]
RubyGems : 2.5.2
Host : Mac OS X 10.13.3 (17D47)
Xcode : 9.4.1 (9F2000)
Git : git version 2.15.2 (Apple Git-101.1)
Ruby lib dir : /System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib
### Plugins
cocoapods-deintegrate : 1.0.2
cocoapods-plugins : 1.0.0
cocoapods-search : 1.0.0
cocoapods-stats : 1.0.0
cocoapods-trunk : 1.3.0
cocoapods-try : 1.1.0
### Error
RuntimeError - [Xcodeproj] Unknown object version.
/usr/local/Cellar/cocoapods/1.4.0/libexec/gems/xcodeproj-1.5.4/lib/xcodeproj/project.rb:217:in `initialize_from_file'
/usr/local/Cellar/cocoapods/1.4.0/libexec/gems/xcodeproj-1.5.4/lib/xcodeproj/project.rb:102:in `open'
/usr/local/Cellar/cocoapods/1.4.0/libexec/gems/cocoapods-1.4.0/lib/cocoapods/command/init.rb:41:in `validate!'
/Library/Ruby/Gems/2.3.0/gems/claide-1.0.2/lib/claide/command.rb:333:in `run'
/usr/local/Cellar/cocoapods/1.4.0/libexec/gems/cocoapods-1.4.0/lib/cocoapods/command.rb:52:in `run'
/usr/local/Cellar/cocoapods/1.4.0/libexec/gems/cocoapods-1.4.0/bin/pod:55:in `<top (required)>'
/usr/local/Cellar/cocoapods/1.4.0/libexec/bin/pod:22:in `load'
/usr/local/Cellar/cocoapods/1.4.0/libexec/bin/pod:22:in `<main>'
――― TEMPLATE END ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
[!] Oh no, an error occurred.
Search for existing GitHub issues similar to yours:
https://github.com/CocoaPods/CocoaPods/search?q=%5BXcodeproj%5D+Unknown+object+version.&type=Issues
If none exists, create a ticket, with the template displayed above, on:
https://github.com/CocoaPods/CocoaPods/issues/new
Be sure to first read the contributing guide for details on how to properly submit a ticket:
https://github.com/CocoaPods/CocoaPods/blob/master/CONTRIBUTING.md
Don't forget to anonymize any private data!
Looking for related issues on cocoapods/cocoapods...
- Pod Update: RuntimeError - [Xcodeproj] Unknown object version. Xcode Beta 5
https://github.com/CocoaPods/CocoaPods/issues/8003 [closed] [17 comments]
a day ago
- RuntimeError - [Xcodeproj] Unknown object version.
https://github.com/CocoaPods/CocoaPods/issues/7697 [closed] [28 comments]
3 weeks ago
- Pod init. Unknown object version
https://github.com/CocoaPods/CocoaPods/issues/7907 [closed] [2 comments]
03 Jul 2018
and 42 more at:
https://github.com/cocoapods/cocoapods/search?q=[Xcodeproj]%20Unknown%20object%20version.&type=Issues&utf8=?
這種失敗原因是Cocoapods和xcodeproj版本兼容問題。
嘗試了網上的解決辦法Run gem install xcodeproj:1.4.1,依然失敗。
解決辦法:打開項目,在Project Document下將Project Format從Xcode 9.3-compatible修改為Xcode 8.0-compatible即可。
十二、在pod中引入項目文件報錯(file not found)
在開發中有時候需要在pod中import項目中的文件進行調試或測試(當然這種情況比較少見),但是當你輸入import的時候會發現系統根本沒法聯想到你想用的項目中的文件,即使你手動寫入,也會報file not found的錯誤。
此時,可以在我們的Pods項目中的Build settings下找到 User Header Search Paths,添加一行,并設置$(SRCROOT)/..
為recursive。注意有/..
,意思是在上級目錄下遞歸查找文件。
十三、清除本地緩存(version重用)
如果在某個tag下(假設WTTestPodCache-v0.0.1)驗證私有pod失敗,修改代碼后,我們還希望使用這個版本號而不是去修改podspec文件中的version字段。我們就需要清除Pod的本地緩存了,否則會因為該tag對應的緩存問題,還是一樣驗證不過。這時候我們需要先把代碼修改正確后提交,然后把本地和遠程的原tag刪除,最后在最新代碼節點上打一個相同的tag提交再次提交驗證。---參考:CocoaPods清理本地緩存
我們在驗證的時候加上--verbose參數,會看到CocoaPods有如下拷貝操作:
copying XXX from 后面跟著的就是本地緩存的地址,進入該路徑下會看到WTTestPodCache,我們刪除本地和遠程tag后,本地該緩存依然存在,當執行pod cache clean WTTestPodCache
后,本地該緩存就不存在了,在驗證私有Pod時CocoaPods就會嘗試再次從遠程拉取。
查看本地緩存列表
pod cache list
清除本地緩存
pod cache clean XXX
十四、集成MobileVLCKit時報錯Lzma library error
如果在驗證pod,安裝某個庫時報Lzma庫的錯誤:
ERROR | [iOS] unknown: Encountered an unknown error ([!] /usr/bin/tar xf /var/folders/tf/jlbz0pq91m16571fgc7ywbk80000gn/T/d20191016-38557-1l50cyi/file.txz -C /var/folders/tf/jlbz0pq91m16571fgc7ywbk80000gn/T/d20191016-38557-1l50cyi
MobileVLCKit-binary/MobileVLCKit.framework/MobileVLCKit: Lzma library error: No progress is possible
tar: Error exit delayed from previous errors.
這屬于pod依賴的這個庫的壓縮包解壓失敗了,Lzma library是用來解壓用的,如果壓縮包損壞了,會導致解壓失敗,從而報這個錯誤,此時需要檢查壓縮包是否正常。
十五、本地多個Ruby環境導致pod執行失敗
如果執行任意pod指令都失敗,失敗信息像下面這樣:
Ignoring ffi-1.13.1 because its extensions are not built. try: gem pristine ffi —version 1.13.1
Ignoring gem-wrappers-1.4.0 because its extensions are not builts. Try: gem pristine gem-wrappers —version 1.4.0
Traceback (most recent call last):
23: from /USR/LOCAL/BIN/pod:23:in `<main>`
22: from /USR/LOCAL/BIN/pod:23:in `load`
21: from /Users/username/.rvm/rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/cocoapods-1.9.3/bin/pod:36:in `<top (required)>`
20: from /System/Library/Frameworks/Ruby.frameworks/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require`
如果是這樣子的話,根據錯誤提示說明是本地有多個Ruby環境導致的,應該是中途什么時候看了一些教程安裝了rvm然后又裝了個Ruby,刪除rvm目錄下的Ruby即可。
十六、慎用--skip-import-validation與--skip-tests
如果pod怎么都驗證不過,始終只有這么一條提示([iOS] xcodebuild: Returned an unsuccessful exit code),沒有任何其他ERROR信息,可以加上這兩個選項了讓pod驗證通過。
--skip-tests: 在驗證期間跳過構建和運行測試
--skip-import-validation: 跳過驗證pod是否可以導入
十七、pod install報錯
/.rvm/rubies/ruby-3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-7.0.8/lib/active_support/logger_thread_safe_level.rb:12:in `<module:LoggerThreadSafeLevel>': uninitialized constant ActiveSupport::LoggerThreadSafeLevel::Logger (NameError)
rvm use dev 切換到ruby3.0.0
sudo gem install activesupport 安裝新版本的activesupport