之前整理過一種打包靜態(tài)庫的方法: 打包靜態(tài)庫(源碼中包含其他靜態(tài)庫以及開源庫)辈末。不過這種方式的缺點很明顯:1. 需要自己使用Xcode創(chuàng)建靜態(tài)庫工程居夹;2. 需要手動處理代碼中依賴的第三方開源庫;3. 如果靜態(tài)庫工程依賴私有庫本冲,當這些私有庫有更新時准脂,就得把他們重新拷貝一份到靜態(tài)庫工程,不夠靈活檬洞。
因為公司需要持續(xù)性向別人提供一套即時通訊SDK狸膏,按照之前的方式打包靜態(tài)庫真是太痛苦,SDK依賴的一些私有庫會有頻繁的更新添怔,依賴的第三方庫也是錯綜復雜湾戳。我迫切需要找到一種更方便的打包靜態(tài)庫的方式,既能隨時更新私有庫广料,也能解決開源庫的沖突問題(比如你的SDK包含了AFNetworking
砾脑,別人項目中本身也含有AFNetworking
,就會產生沖)艾杏,那就是使用cocoapods韧衣。
假設你已經有一堆寫好的源碼,并且它們依賴一堆私有庫和第三方庫,也許畅铭,這些依賴并非都是源碼氏淑,可能也包含了靜態(tài)庫(.a
或者.framework
)。沒關系硕噩,先放在那吧假残。
1. 創(chuàng)建你的 project
打開終端,cd 到你喜歡的某個文件路徑炉擅,輸入 pod lib create YOUR_POD_LIBARY_NAME
創(chuàng)建并初始化一個工程辉懒。
然后回答幾個問題,就自動創(chuàng)建出一個project:
我們新建的工程以及目錄如下:
其中重要文件夾我都已經展開谍失,可以看到里面包含的內容眶俩,其他沒有展開的文件夾不用管它。
從上向下看袱贮,最重要的一個就是YJDemoSDK.podspec
文件,*.podspec
是關于pod庫的描述文件体啰,它詳細說明了在這個pod library
中源碼應該從哪里取出攒巍、應用怎樣的構建設置以及其他基本的信息,比如名稱荒勇、版本柒莉、描述等。下面插播一段關于.podspec
文件的植入性廣告:
podspec文件的內容如下所示沽翔,每一項是什么意思都做了簡要解釋兢孝,不明白可以去看官方文檔:
Pod::Spec.new do |s|
s.name = 'YJDemoSDK' #項目名
s.version = '0.1.0' #相應的版本號
s.summary = 'A short description of YJDemoSDK.' #簡述
s.description = <<‐ DESC #詳細描述
TODO: Add long description of the pod here.
DESC
s.homepage = 'https://github.com/yangjie2/YJDemoSDK' #項目主頁
s.license = { :type => 'MIT', :file => 'LICENSE' } #開源協(xié)議
s.author = { 'yangjie2' => 'yangjie2@guahao.com' } #作者
s.platform = :ios, '8.0' #支持的平臺
s.requires_arc = true #arc和mrc選項
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為前綴進行命名仅偎,否則會出現找不到的情況跨蟹,這一點非常重要
#平臺信息
s.platform = :ios, '7.0'
s.ios.deployment_target = '7.0'
#文件配置項
s.source = { :git => 'https://github.com/yangjie2/YJDemoSDK.git', :tag => s.version.to_s }
#配置項目的目標路徑,如果不是本地開發(fā)橘沥,pod init/update會從這個路去拉去代碼
s.source_files = 'YJDemoSDK/Classes/**/*.{h,m}' #你的源碼位置
s.resources = ['YJDemoSDK/Assets/*.png'] #資源窗轩,比如圖片,音頻文件等
s.public_header_files = 'YJDemoSDK/Classes/YJDemoSDK.h' #需要對外開放的頭文件
#依賴的項目內容 可以多個
s.dependency 'YYModel'
s.dependency 'AFNetworking' '2.3'
明白了.podspec
文件是什么之后座咆,繼續(xù)往下看我們的工程目錄痢艺,有個文件夾 Development Pods ,這里就是放置我們的源碼和圖片等資源文件的地方介陶,要與YJDemoSDK.podspec
文件中描述的一致堤舒。當你向YOUR_POD_LIBARY_NAME/Classes
、YOUR_POD_LIBARY_NAME/Assets
添加新的/已經存在的文件哺呜,或者更新你的.podspec
時舌缤,需要運行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)建了一個pod library項目YJDemoSDK
友驮,可以看到項目的文件目錄結構漂羊,并對幾個重要的地方進行了單獨說明。下面就開始設置它卸留。
向Classes中添加源碼替換掉ReplaceMe.m
文件(為了簡單走越,這里添加YJDemoSDK.h
和 YJDemoSDK.m
)。YJDemoSDK.podspec
文件配置結果如下圖:
配置完之后耻瑟,切到終端旨指,使用 pod lib lint ***.podspec
驗證是否有效,必須沒有錯誤喳整,沒有警告才可以通過驗證谆构。
由于YJDemoSDK依賴私有庫YJHelloDog
,并且這個私有庫中包含靜態(tài)庫(.a)文件框都,所以在驗證時搬素,需要鍵入如下命令:
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
忽略警告谓罗。這樣就可以驗證通過了粱哼。
然后cd到YJDemoSDK的Example目錄,執(zhí)行
pod install檩咱。
幽怨的發(fā)現揭措,出錯了,找不到YJDemoSDK依賴的YJHelloDog這個倉庫刻蚯。
不過問題也很顯然绊含, YJHelloDog 是私有庫。在podfile文件中炊汹,添加上我自己的Repo 地址就好艺挪。
再次執(zhí)行 pod install,就沒問題了兵扬。
這時候再看下pod項目的目錄麻裳,發(fā)現已經添加好了源碼和依賴庫:
3. 提交,打tag
YJDemoSDK 項目配置完成器钟,添加了自己的源碼津坑,也添加了依賴的私有庫、第三方開源庫傲霸。提交所有的更新疆瑰,打tag眉反,push到托管服務器(比如gitHub等)。
打開終端穆役,重新 cd 到 YJDemoSDK 路徑寸五,執(zhí)行以下命令進行提交、打tag耿币,推送tag到遠端托管服務器梳杏。
git add .
git tag -a 0.1.0 -m 'version 0.1.0'
git push origin 0.1.0
4. 打包靜態(tài)庫
前面的準備工作完成,最后一步就是打包靜態(tài)庫了淹接。這里需要安裝一個 CocoaPods 打包插件 cocoapods-packager
十性。終端執(zhí)行安裝命令:sudo gem install cocoapods-packager
,等待安裝完成塑悼。
該插件通過對引用的三方庫進行重命名很好的解決了類庫命名沖突的問題劲适。
終端cd到項目所在目錄下,執(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 表示強制覆蓋之前存在的文件
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文件
上面的命令中,有一個是--no-mangle
斑鸦,表示Do not mangle symbols of depedendant Pods
愕贡,當你的項目依賴包含靜態(tài)庫時,不加上這句鄙才,就會打包失斔毯琛:
到此為止促绵,.a 形式的靜態(tài)庫打包成功攒庵!剛才打包好的靜態(tài)庫就在我的項目路徑下YJDemoSDK-0.1.0文件夾中,里面的文件如下圖所示:
驀然發(fā)現败晴,沒有頭文件浓冒。。尖坤。我希望公開被
別人調用的頭文件跑哪里去了稳懒?這樣打出來靜態(tài)庫也沒辦法用。google了下慢味,找到一種不是答案的答案场梆,這是cocoapods package 的一個bug,不知道為啥沒被修復纯路』蛴停或者哪位老鐵有了解決方法還請不吝分享下。
這樣只能打包成framework了驰唬,它是有頭文件的顶岸。目錄如下圖:
使用時腔彰,直接拖到項目中,然后 #import <YJDemoSDK/YJDemoSDK.h>
辖佣,使用#import "YJDemoSDK/YJDemoSDK.h"
形式是找不到文件的霹抛,要使用尖括號。拖到你的項目中卷谈,編譯時會出錯:
原因是找不到
YJHelloDog.a
文件杯拐,這個文件是我們的靜態(tài)庫依賴的另一個靜態(tài)庫,它是不會被打包進framework的雏搂,需要你手動添加進項目藕施,所以再把依賴的這個靜態(tài)庫 YJHelloDog.a
拖進項目就好了,運行成功凸郑,顯示toast提示:hello dog. 說明方法調用成功裳食。至此才算結束了。
使用含有category的靜態(tài)庫時, selector not recognized
的解決方案
在 iOS/Mac 平臺下芙沥,包含 Category 的靜態(tài)庫無法被正常加載诲祸,原因在于 Category 是 Objective-C 語言的特性,編譯器并不會為它生成鏈接符號而昨,在鏈接過程中便無法找到該對象文件的引用關系救氯,鏈接器將會直接忽略掉 Category 對應的對象文件,從而在運行時無法找到相應的 selector歌憨。解決該問題的目標就是讓鏈接器加載 Category 對應的對象文件着憨,一種方法是添加編譯參數讓編譯器加載所有的對象文件或是加載指定的對象文件;另一種方法是在 Category 的對象文件中添加 Fake symbol 务嫡,當 Fake symbol 被加載時 Category 的對象文件便一同被加載甲抖。
解決方法:
- 在編譯選項 Other Linker Flags 中添加 -all_load,用于會告訴編譯器 對于所有靜態(tài)庫中的所有對象文件心铃,不管里面的符號有沒有被用到准谚,全部都載入,這種方法可以解決問題去扣,但是會產生比較大的二進制文件柱衔。
- 在編譯選項 Other Linker Flags 中添加 -force_load 并指定路徑:
-force_load $(BUILT_PRODUCTS_DIR)/<library_name.a>`
這種方法和 -all_load 類似,不同的是它只載入指定的靜態(tài)庫愉棱。
- 在編譯選項 Other Linker Flags 中添加 -ObjC唆铐,這個標識告訴編譯器 如果在靜態(tài)庫的對象文件中發(fā)現了 Objective-C 代碼,就把它載入奔滑,Category 中肯定會存在 Objective-C 代碼艾岂。該方法與前兩張類似,只是將加載的范圍減少了档押。
- 另一種解決方法是新版本 Xcode 里 build setting 中的 Perform Single-Object PreLink澳盐,如果啟用這個選項祈纯,所有的對象文件都會被合并成一個單文件(這不是真正的鏈接,所以叫做預鏈接)叼耙,這個對象文件(有時被稱做主對象文件 master object file)被添加到靜態(tài)庫中⊥罂現在如果主對象文件中的任何符號被認為是在使用,整個主對象文件都會被認為在使用筛婉,這樣它里面的 Objective-C 部分就會被載入了簇爆,當然也包括 Category 對應的對象文件。
- 最后一種解決方法是在 Category 的源文件里添加 Fake symbol爽撒,并確保以某種方法在編譯時引用了該 Fake symbol入蛆,這會使得 Fake symbol 對象文件被加載時它里面 Category 代碼也會被載入。該方法可以控制哪些 Category 可以被正常加載硕勿,同時也不需要添加編譯參數做特殊處理哨毁。
建議使用第五種方法解決問題,因為前 4 種都會增加二進制文件的體積源武,在第三方集成你的 SDK 時需要手動設置編譯參數扼褪,會給第三方帶來不好的使用體驗。為了使用方便可定義一下宏:
#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() 宏來聲明一個 Fake symbol 粱栖,在 Category 的實現文件中使用 FIX_CATEGORY_BUG_M() 宏來實現該 Fake symbol话浇。最后在找一處運行 ENABLE_CATEGORY() 宏,可以是初始化方法中闹究,也可以是其他任何地方幔崖,只要確保它能被正常調用,目的在于該 Fake symbol 確保編譯器能正常加載它渣淤。
在 64 位的 Mac 系統(tǒng)或者 iOS 系統(tǒng)下赏寇,鏈接器有一個 bug,會導致只包含有 Category 的靜態(tài)庫無法使用 -ObjC 標志來加載 Objective-C 對象文件砂代。