之前整理過一種打包靜態(tài)庫的方法: 打包靜態(tài)庫(源碼中包含其他靜態(tài)庫以及開源庫)。不過這種方式的缺點(diǎn)很明顯:1. 需要自己使用Xcode創(chuàng)建靜態(tài)庫工程;2. 需要手動(dòng)處理代碼中依賴的第三方開源庫;3. 如果靜態(tài)庫工程依賴私有庫,當(dāng)這些私有庫有更新時(shí),就得把他們重新拷貝一份到靜態(tài)庫工程,不夠靈活。
因?yàn)楣拘枰掷m(xù)性向別人提供一套即時(shí)通訊SDK,按照之前的方式打包靜態(tài)庫真是太痛苦,SDK依賴的一些私有庫會(huì)有頻繁的更新,依賴的第三方庫也是錯(cuò)綜復(fù)雜。我迫切需要找到一種更方便的打包靜態(tài)庫的方式,既能隨時(shí)更新私有庫,也能解決開源庫的沖突問題(比如你的SDK包含了AFNetworking,別人項(xiàng)目中本身也含有AFNetworking,就會(huì)產(chǎn)生沖),那就是使用cocoapods。
假設(shè)你已經(jīng)有一堆寫好的源碼,并且它們依賴一堆私有庫和第三方庫,也許,這些依賴并非都是源碼,可能也包含了靜態(tài)庫(.a或者.framework)。沒關(guān)系,先放在那吧。
1. 創(chuàng)建你的 project
打開終端,cd 到你喜歡的某個(gè)文件路徑,輸入 pod lib create YOUR_POD_LIBARY_NAME創(chuàng)建并初始化一個(gè)工程。

然后回答幾個(gè)問題,就自動(dòng)創(chuàng)建出一個(gè)project:

我們新建的工程以及目錄如下:

其中重要文件夾我都已經(jīng)展開,可以看到里面包含的內(nèi)容,其他沒有展開的文件夾不用管它。
從上向下看,最重要的一個(gè)就是YJDemoSDK.podspec文件,*.podspec是關(guān)于pod庫的描述文件,它詳細(xì)說明了在這個(gè)pod library中源碼應(yīng)該從哪里取出、應(yīng)用怎樣的構(gòu)建設(shè)置以及其他基本的信息,比如名稱、版本、描述等。下面插播一段關(guān)于.podspec文件的植入性廣告:
podspec文件的內(nèi)容如下所示,每一項(xiàng)是什么意思都做了簡(jiǎn)要解釋,不明白可以去看官方文檔:
Pod::Spec.new do |s|
s.name = 'YJDemoSDK' #項(xiàng)目名
s.version = '0.1.0' #相應(yīng)的版本號(hào)
s.summary = 'A short description of YJDemoSDK.' #簡(jiǎn)述
s.description = <<‐ DESC #詳細(xì)描述
TODO: Add long description of the pod here.
DESC
s.homepage = 'https://github.com/yangjie2/YJDemoSDK' #項(xiàng)目主頁
s.license = { :type => 'MIT', :file => 'LICENSE' } #開源協(xié)議
s.author = { 'yangjie2' => 'yangjie2@guahao.com' } #作者
s.platform = :ios, '8.0' #支持的平臺(tái)
s.requires_arc = true #arc和mrc選項(xiàng)
s.libraries = 'z', 'sqlite3' #表示依賴的系統(tǒng)類庫,比如libz.dylib等
s.frameworks = 'UIKit','AVFoundation' #表示依賴系統(tǒng)的框架
s.ios.vendored_frameworks = 'YJKit/YJKit.framework' # 依賴的第三方/自己的framework
s.vendored_libraries = 'Library/Classes/libWeChatSDK.a' #表示依賴第三方/自己的靜態(tài)庫(比如libWeChatSDK.a)
#依賴的第三方的或者自己的靜態(tài)庫文件必須以lib為前綴進(jìn)行命名,否則會(huì)出現(xiàn)找不到的情況,這一點(diǎn)非常重要
#平臺(tái)信息
s.platform = :ios, '7.0'
s.ios.deployment_target = '7.0'
#文件配置項(xiàng)
s.source = { :git => 'https://github.com/yangjie2/YJDemoSDK.git', :tag => s.version.to_s }
#配置項(xiàng)目的目標(biāo)路徑,如果不是本地開發(fā),pod init/update會(huì)從這個(gè)路去拉去代碼
s.source_files = 'YJDemoSDK/Classes/**/*.{h,m}' #你的源碼位置
s.resources = ['YJDemoSDK/Assets/*.png'] #資源,比如圖片,音頻文件等
s.public_header_files = 'YJDemoSDK/Classes/YJDemoSDK.h' #需要對(duì)外開放的頭文件
#依賴的項(xiàng)目?jī)?nèi)容 可以多個(gè)
s.dependency 'YYModel'
s.dependency 'AFNetworking' '2.3'
明白了.podspec文件是什么之后,繼續(xù)往下看我們的工程目錄,有個(gè)文件夾 Development Pods ,這里就是放置我們的源碼和圖片等資源文件的地方,要與YJDemoSDK.podspec文件中描述的一致。當(dāng)你向YOUR_POD_LIBARY_NAME/Classes、YOUR_POD_LIBARY_NAME/Assets添加新的/已經(jīng)存在的文件,或者更新你的.podspec時(shí),需要運(yùn)行pod install或者pod update。
Development Pods
Development Pods are different from normal CocoaPods in that they are symlinked files, so making edits to them will change the original files, so you can work on your library from inside Xcode. Your demo & tests will need to include references to headers using the#import <MyLib/XYZ.h> format.Note: Due to a Development Pods implementation detail, when you add new/existing files to Pod/Classes or Pod/Assets or update your podspec, you should run pod install or pod update.
2. 配置podspec文件,添加自己的代碼
上面創(chuàng)建了一個(gè)pod library項(xiàng)目YJDemoSDK,可以看到項(xiàng)目的文件目錄結(jié)構(gòu),并對(duì)幾個(gè)重要的地方進(jìn)行了單獨(dú)說明。下面就開始設(shè)置它。
向Classes中添加源碼替換掉ReplaceMe.m文件(為了簡(jiǎn)單,這里添加YJDemoSDK.h和 YJDemoSDK.m )。YJDemoSDK.podspec文件配置結(jié)果如下圖:

配置完之后,切到終端,使用 pod lib lint ***.podspec驗(yàn)證是否有效,必須沒有錯(cuò)誤,沒有警告才可以通過驗(yàn)證。
由于YJDemoSDK依賴私有庫YJHelloDog,并且這個(gè)私有庫中包含靜態(tài)庫(.a)文件,所以在驗(yàn)證時(shí),需要鍵入如下命令:
pod lib lint --sources=git@git.guahao-inc.com:yangjie2/snowRepo.git,https://github.com/CocoaPods/Specs.git --use-libraries --allow-warnings
其中,--sources=git@git.guahao-inc.com:yangjie2/snowRepo.git,https://github.com/CocoaPods/Specs.git指定了我們的私有Repo地址和cocoapods官方的Repo,并且是master分支。--use-libraries表示依賴了靜態(tài)庫,--allow-warnings忽略警告。這樣就可以驗(yàn)證通過了。

然后cd到Y(jié)JDemoSDK的Example目錄,執(zhí)行
pod install。
幽怨的發(fā)現(xiàn),出錯(cuò)了,找不到Y(jié)JDemoSDK依賴的YJHelloDog這個(gè)倉(cāng)庫。

不過問題也很顯然, YJHelloDog 是私有庫。在podfile文件中,添加上我自己的Repo 地址就好。

再次執(zhí)行 pod install,就沒問題了。

這時(shí)候再看下pod項(xiàng)目的目錄,發(fā)現(xiàn)已經(jīng)添加好了源碼和依賴庫:

3. 提交,打tag
YJDemoSDK 項(xiàng)目配置完成,添加了自己的源碼,也添加了依賴的私有庫、第三方開源庫。提交所有的更新,打tag,push到托管服務(wù)器(比如gitHub等)。
打開終端,重新 cd 到 YJDemoSDK 路徑,執(zhí)行以下命令進(jìn)行提交、打tag,推送tag到遠(yuǎn)端托管服務(wù)器。
git add .
git tag -a 0.1.0 -m 'version 0.1.0'
git push origin 0.1.0
4. 打包靜態(tài)庫
前面的準(zhǔn)備工作完成,最后一步就是打包靜態(tài)庫了。這里需要安裝一個(gè) CocoaPods 打包插件 cocoapods-packager。終端執(zhí)行安裝命令:sudo gem install cocoapods-packager,等待安裝完成。
該插件通過對(duì)引用的三方庫進(jìn)行重命名很好的解決了類庫命名沖突的問題。
終端cd到項(xiàng)目所在目錄下,執(zhí)行以下命令即開始打包靜態(tài)庫:
pod package YJDemoSDK.podspec --library --force --no-mangle --spec-sources=http://git.guahao-inc.com/yangjie2/snowRepo.git,https://github.com/CocoaPods/Specs.git
# --library 表示打包成.a文件。--force 表示強(qiáng)制覆蓋之前存在的文件
pod package YJDemoSDK.podspec --force --no-mangle --spec-sources=http://git.guahao-inc.com/yangjie2/snowRepo.git,https://github.com/CocoaPods/Specs.git
# 沒有--library,則打包成.framework文件
上面的命令中,有一個(gè)是--no-mangle,表示Do not mangle symbols of depedendant Pods,當(dāng)你的項(xiàng)目依賴包含靜態(tài)庫時(shí),不加上這句,就會(huì)打包失?。?/p>


到此為止,.a 形式的靜態(tài)庫打包成功!剛才打包好的靜態(tài)庫就在我的項(xiàng)目路徑下YJDemoSDK-0.1.0文件夾中,里面的文件如下圖所示:

驀然發(fā)現(xiàn),沒有頭文件。。。我希望公開被
別人調(diào)用的頭文件跑哪里去了?這樣打出來靜態(tài)庫也沒辦法用。google了下,找到一種不是答案的答案,這是cocoapods package 的一個(gè)bug,不知道為啥沒被修復(fù)。或者哪位老鐵有了解決方法還請(qǐng)不吝分享下。
這樣只能打包成framework了,它是有頭文件的。目錄如下圖:

使用時(shí),直接拖到項(xiàng)目中,然后 #import <YJDemoSDK/YJDemoSDK.h>,使用#import "YJDemoSDK/YJDemoSDK.h"形式是找不到文件的,要使用尖括號(hào)。拖到你的項(xiàng)目中,編譯時(shí)會(huì)出錯(cuò):

原因是找不到
YJHelloDog.a 文件,這個(gè)文件是我們的靜態(tài)庫依賴的另一個(gè)靜態(tài)庫,它是不會(huì)被打包進(jìn)framework的,需要你手動(dòng)添加進(jìn)項(xiàng)目,所以再把依賴的這個(gè)靜態(tài)庫 YJHelloDog.a 拖進(jìn)項(xiàng)目就好了,運(yùn)行成功,顯示toast提示:hello dog. 說明方法調(diào)用成功。至此才算結(jié)束了。
使用含有category的靜態(tài)庫時(shí), selector not recognized的解決方案
在 iOS/Mac 平臺(tái)下,包含 Category 的靜態(tài)庫無法被正常加載,原因在于 Category 是 Objective-C 語言的特性,編譯器并不會(huì)為它生成鏈接符號(hào),在鏈接過程中便無法找到該對(duì)象文件的引用關(guān)系,鏈接器將會(huì)直接忽略掉 Category 對(duì)應(yīng)的對(duì)象文件,從而在運(yùn)行時(shí)無法找到相應(yīng)的 selector。解決該問題的目標(biāo)就是讓鏈接器加載 Category 對(duì)應(yīng)的對(duì)象文件,一種方法是添加編譯參數(shù)讓編譯器加載所有的對(duì)象文件或是加載指定的對(duì)象文件;另一種方法是在 Category 的對(duì)象文件中添加 Fake symbol ,當(dāng) Fake symbol 被加載時(shí) Category 的對(duì)象文件便一同被加載。
解決方法:
- 在編譯選項(xiàng) Other Linker Flags 中添加 -all_load,用于會(huì)告訴編譯器 對(duì)于所有靜態(tài)庫中的所有對(duì)象文件,不管里面的符號(hào)有沒有被用到,全部都載入,這種方法可以解決問題,但是會(huì)產(chǎn)生比較大的二進(jìn)制文件。
- 在編譯選項(xiàng) Other Linker Flags 中添加 -force_load 并指定路徑:
-force_load $(BUILT_PRODUCTS_DIR)/<library_name.a>`
這種方法和 -all_load 類似,不同的是它只載入指定的靜態(tài)庫。
- 在編譯選項(xiàng) Other Linker Flags 中添加 -ObjC,這個(gè)標(biāo)識(shí)告訴編譯器 如果在靜態(tài)庫的對(duì)象文件中發(fā)現(xiàn)了 Objective-C 代碼,就把它載入,Category 中肯定會(huì)存在 Objective-C 代碼。該方法與前兩張類似,只是將加載的范圍減少了。
- 另一種解決方法是新版本 Xcode 里 build setting 中的 Perform Single-Object PreLink,如果啟用這個(gè)選項(xiàng),所有的對(duì)象文件都會(huì)被合并成一個(gè)單文件(這不是真正的鏈接,所以叫做預(yù)鏈接),這個(gè)對(duì)象文件(有時(shí)被稱做主對(duì)象文件 master object file)被添加到靜態(tài)庫中?,F(xiàn)在如果主對(duì)象文件中的任何符號(hào)被認(rèn)為是在使用,整個(gè)主對(duì)象文件都會(huì)被認(rèn)為在使用,這樣它里面的 Objective-C 部分就會(huì)被載入了,當(dāng)然也包括 Category 對(duì)應(yīng)的對(duì)象文件。
- 最后一種解決方法是在 Category 的源文件里添加 Fake symbol,并確保以某種方法在編譯時(shí)引用了該 Fake symbol,這會(huì)使得 Fake symbol 對(duì)象文件被加載時(shí)它里面 Category 代碼也會(huì)被載入。該方法可以控制哪些 Category 可以被正常加載,同時(shí)也不需要添加編譯參數(shù)做特殊處理。
建議使用第五種方法解決問題,因?yàn)榍?4 種都會(huì)增加二進(jìn)制文件的體積,在第三方集成你的 SDK 時(shí)需要手動(dòng)設(shè)置編譯參數(shù),會(huì)給第三方帶來不好的使用體驗(yàn)。為了使用方便可定義一下宏:
#define FIX_CATEGORY_BUG_H(name) \
@interface FIX_CATEGORY_BUG_##name : NSObject \
+(void)print; \
@end
#define FIX_CATEGORY_BUG_M(name) \
@implementation FIX_CATEGORY_BUG_##name \
+ (void)print {} \
@end
#define ENABLE_CATEGORY(name) [FIX_CATEGORY_BUG_##name print]
在 Category 的頭文件中使用 FIX_CATEGORY_BUG_H() 宏來聲明一個(gè) Fake symbol ,在 Category 的實(shí)現(xiàn)文件中使用 FIX_CATEGORY_BUG_M() 宏來實(shí)現(xiàn)該 Fake symbol。最后在找一處運(yùn)行 ENABLE_CATEGORY() 宏,可以是初始化方法中,也可以是其他任何地方,只要確保它能被正常調(diào)用,目的在于該 Fake symbol 確保編譯器能正常加載它。
在 64 位的 Mac 系統(tǒng)或者 iOS 系統(tǒng)下,鏈接器有一個(gè) bug,會(huì)導(dǎo)致只包含有 Category 的靜態(tài)庫無法使用 -ObjC 標(biāo)志來加載 Objective-C 對(duì)象文件。