1. 程式人生 > >Xcode 創建.a和framework靜態庫(轉)

Xcode 創建.a和framework靜態庫(轉)

通過 不用 方式 源碼 右鍵 eos 一個 iphone5s 最終

最近因為項目中的聊天SDK,需要封裝成靜態庫,所以實踐了一下創建靜態庫的步驟,做下記錄。

庫介紹

庫從本質上來說是一種可執行代碼的二進制格式,可以被載入內存中執行。庫分靜態庫和動態庫兩種。
iOS中的靜態庫有 .a 和 .framework兩種形式;動態庫有.dylib 和 .framework 形式,後來.dylib動態庫又被蘋果替換成.tbd的形式。

靜態庫與動態庫的區別

靜態庫和動態庫是相對編譯期和運行期的:靜態庫在程序編譯時會被鏈接到目標代碼中,程序運行時將不再需要改靜態庫;而動態庫在程序編譯時並不會被鏈接到目標代碼中,只是在程序運行時才被載入,因為在程序運行期間還需要動態庫的存在。
總結:同一個靜態庫在不同程序中使用時,每一個程序中都得導入一次,打包時也被打包進去,形成一個程序。而動態庫在不同程序中,打包時並沒有被打包進去,只在程序運行使用時,才鏈接載入(如系統的框架如UIKit、Foundation等),所以程序體積會小很多,但是蘋果不讓使用自己的動態庫,否則審核就無法通過。

創建.a靜態庫

第一步,新建工程。一般使用工程名就使用庫的名稱,比如我這裏用FMDB來創建靜態庫,我的工程名就取名為FMDB,創建的.a靜態庫就是libFMDB.a。

技術分享
使用靜態庫模板新建工程.png 技術分享
創建的工程.png


第二步,刪除系統默認創建的【FMDB.h】和【FMDB.m】文件,導入需要打包的源文件。

技術分享
導入源文件後.png


第三步(方式一),修改項目配置

技術分享
修改配置.png


點擊上圖中的【3】,彈出的列表中選擇【New Headers Phase】,打開【Headers (0 items)】,點擊左下角的【+】,選擇所有的.h文件。

技術分享
配置需要暴漏的文件的.h頭.png

第三步(方式二),修改項目配置

技術分享
修改項目配置.png

第四步,修改導出product配置

技術分享
修改編譯配置.png

第五步,修改編譯指令集

技術分享
設置Release為NO.png

模擬器:iPhone4s~5 : i386 iPhone5s~6plus : x86_64
真機:iPhone3gs~4s : armv7 iPhone5~5c : armv7s iPhone5s~6plus : arm64
如果第五步這裏,設置為YES,那麽編譯出來的.a靜態庫就只包含當前設備的指令集。
舉個例子:如果我們選擇iPhone 5模擬器【Command+B】編譯,則編譯出來的.a靜態庫只能用iPhone4s~5模擬器跑程序,用iPhone5s~6plus,則會報找不到x86_64的libFMDB庫。
設置為NO,則會把所有指令集的都打包合並。

第六步,編譯(快捷鍵【Command+B】
編譯時,需要用模擬器和真機各編譯一次,這樣Products目錄下的libFMDB.a靜態庫才會變為黑色,右鍵show in Finder,可以進入Products目錄下。

技術分享
編譯結果.png


為什麽需要用模擬器和真機各編譯一次呢?
可以看到Products目錄下有【Release-iphoneos】和【Release-iphonesimulator】兩個文件件。前者裏面是真機使用的.a靜態庫,後者是模擬器使用的.a靜態庫。

註意:如果步驟四中,不將Build Configuration改為Release,則打包出來的靜態庫會存於【Debug-iphoneos】和【Debug-iphonesimulator】兩個文件夾下。
我們一般都使用Release模式,因為程序最終發布之後是Release版的,所以靜態庫也是在Release模式下使用。

如果想要通用需要將模擬器使用的靜態庫與真機使用的靜態庫合並成一個靜態庫,可以使用終端命令來實現。命令格式:
lipo -create 第一個.a文件的絕對路徑 第二個.a文件的絕對路徑 -output 最終的.a文件路徑。
本文中使用的命令如下:

lipo -create /Users/harvey/Library/Developer/Xcode/DerivedData/FMDB-ctegiztcjikewoeprxxtmryzetfa/Build/Products/Release-iphoneos/libFMDB.a /Users/harvey/Library/Developer/Xcode/DerivedData/FMDB-ctegiztcjikewoeprxxtmryzetfa/Build/Products/Release-iphonesimulator/libFMDB.a -output /Users/harvey/Desktop/libFMDB.a

補充:經過多次實踐,第三步的操作省略,依然可以導出可正常使用的包。
如果靜態庫中有category類,則在使用靜態庫的項目配置中【Other Linker Flags】需要添加參數【-ObjC]或者【-all_load】。

創建framework靜態庫

第一步,新建項目

技術分享
新建項目.png


第二步,刪除系統默認創建的【FMDB.h】和【FMDB.m】文件,導入需要打包的源文件。

技術分享
導入源碼後的工程.png

第三步,修改項目配置
首先,設置需要暴漏的頭文件

技術分享
header文件設置.png

這裏需要註意的是暴露出來的頭文件中import的其他類也得添加到public中暴露出來。
如果不想將import的類暴露出來,那麽在頭文件中用@class 然後在對應的.m文件中再import。

然後設置編譯模式,在Xcode菜單【Product】--->【Scheme】--->【Edit Scheme...】中

技術分享
設置編譯模式.png

設置編譯出的靜態庫包含的指令集

技術分享
設置編譯出的靜態庫包含的指令集.png

最後修改生成的Mach-O格式

技術分享
修改Mach-O 格式.png

第四步,編譯生成靜態庫
編譯時,需要用模擬器和真機各編譯一次,這樣Products目錄下的libFMDB.a靜態庫才會變為黑色,右鍵show in Finder,可以進入Products目錄下。

技術分享
編譯生成的framework靜態庫.png

第五步,合並模擬器版framework和真機版framework
合並的命令同上面相似,不同之處是:framework靜態庫合並的不是framework,而是framework下的一個二進制文件,即上一步圖中標記的文件。
lipo -create 第一個framework下二進制文件的絕對路徑 第二個framework下二進制文件的絕對路徑 -output 最終的二進制文件路徑。
本文中使用的命令如下:

lipo -create /Users/harvey/Library/Developer/Xcode/DerivedData/FMDB-clvayfrjgytqrbdkyqrtcjkxfeuz/Build/Products/Release-iphonesimulator/FMDB.framework/FMDB /Users/harvey/Library/Developer/Xcode/DerivedData/FMDB-clvayfrjgytqrbdkyqrtcjkxfeuz/Build/Products/Release-iphoneos/Release-iphoneos.framework/FMDB -output /Users/harvey/Desktop/FMDB

最後將任何一個framework中的二進制文件替換成合並後的二進制文件即可。
把framework添加到要使用的項目中即可使用。

註意:如果創建的framework中使用了category類,則在使用framework的項目配置中【Other Linker Flags】需要添加參數【-ObjC]或者【-all_load】。
如果使用framework的使用出現【Umbrella header for module ‘XXXX‘ does not include header ‘XXXXX.h‘】,是因為錯把xxxxx.h拖到了public中。
如果出現【dyld: Library not loaded:XXXXXX】,是因為打包的framework版本太高。比如打包framework時,選擇的是iOS 9.0,而實際的工程環境是iOS 8開始的。

如果創建的framework類中使用了.dylib或者.tbd,首先需要在實際項目中導入.dylib或者.tbd動態庫,然後需要設置【Allow Non-modular Includes ....】為YES,否則會報錯"Include of non-modular header inside framework module"。

技術分享
設置【Allow Non-modular Includes ....】.png

補充:打包成的靜態庫肯定是比源碼類要大很多的,因為是由不同指令集不同設備的版本合並成的。所以如果你很在意你的app大小,並且也不是很需要打包成靜態庫的話,還是用原始類吧。
framework靜態庫中是可以包含圖片資源的;而.a靜態庫中不能包含圖片資源,只能另外創建一個目錄存放。

填坑記錄

上面的註意裏提到了一些坑,以及解決辦法。這裏再記錄一些:
1.framework中用到了NSClassFromString,但是轉換出來的class 一直為nil。
先來看一下這個API的官方描述

技術分享
官方描述.png


什麽意思呢?如果轉換出來的class為nil,有兩種情況:一種情況是這個類不存在;第二種情況是這個類還沒有被load。所以一般出現問題,都是第二種情況。
怎麽解決這個問題呢?在主工程的【Other Linker Flags】需要添加參數【-ObjC]即可。

2.framework中把圖片、音頻打包進bundle中,但是一直加載不到。
打包的framework中有一個bundle,bundle裏有一些圖片、音頻等資源。但是用如下方式:

NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"XXXX" ofType:@"bundle"];
NSString *mp3Path = [[NSBundle bundleWithPath:bundlePath] pathForResource:@"Message_system" ofType:@"mp3"];

獲取的mp3Path一直為nil。framework,只暴露了一些.h頭文件,bundle沒有暴露出來,無法獲取。那麽我們只需要將bundle與framework一起放入目標工程中即可。其實bundle根本不用打包進framework中。
例如:
我們創建了一個叫ABC.framework的靜態庫。庫中使用了Message_system.mp3,那麽我們創建一個bundle,命名為ABC.bundle,然後將Message_system.mp3放入bundle中。打包的時候,framework並不包含ABC.bundle。最後在要使用ABC.framework的工程中,新建一個文件夾or group,然後把ABC.framework和ABC.bundle一起拖進去,就可以啦。

Have Fun!

Xcode 創建.a和framework靜態庫(轉)