1、認識靜態(tài)庫和動態(tài)庫
首先 庫 是對程序代碼、函數(shù)及資源文件的封裝,是一種共享程序代碼,實現(xiàn)代碼資源復用的方式。分為 靜態(tài)庫 和 動態(tài)庫 。
靜態(tài)庫和動態(tài)庫最明顯的區(qū)別在于載入鏈接時期的不同,是相對于編譯期和運行期而言的。
靜態(tài)庫 在程序編譯時被鏈接到目標代碼塊,模塊化提高代碼的復用性,多次使用將多次拷貝,容易造成冗余,使得包變大。
動態(tài)庫 在程序編譯時不會被鏈接,而是在程序運行時才被動態(tài)載入加到內(nèi)存中,系統(tǒng)只需要加載一次,供多個程序共享使用,共享代碼節(jié)省內(nèi)存空間。由于運行時動態(tài)載入的特性,我們可隨時對庫進行更新,而不需要重新編譯代碼,實現(xiàn) 動態(tài)更新 。但是可能會導致運行時錯誤且不易于定位和修復。
iOS8之前蘋果不允許第三方框架使用動態(tài)方式加載,iOS8之后新增APP Extension,主APP需要和Extension共享代碼;緊接著Swift語言機制不支持靜態(tài)庫(因為Swift運行庫沒被包含在iOS系統(tǒng)中而是打包進APP中,靜態(tài)庫會導致最終的目標程序中包含重復的運行庫),于是推出 framework ,framework 既可以打包靜態(tài)庫又可以打包動態(tài)庫。本文著重介紹如何使用framework打包靜態(tài)庫。有關(guān)靜態(tài)庫和動態(tài)庫的知識可以自行深入學習,這里只做簡單介紹。
2、封裝framework靜態(tài)庫
- 創(chuàng)建framework

- 配置 Build Setting
修改 Mach-O Type 為 Static Library,將鏈接時生成的執(zhí)行文件類型設(shè)置成靜態(tài)庫類型
設(shè)置 Dead Code Stripping 為 YES 死碼(代碼被定義卻從未被調(diào)用)剝離,去掉不會執(zhí)行到的冗余代碼,為編譯包瘦身

設(shè)置 Build Active Architecture Only 為 NO 包含支持多種架構(gòu),否則為僅支持當前活躍架構(gòu)的模擬器或真機。 Valid Architectures 可控制支持的架構(gòu),如舍棄 armv7 ,則不支持 4s 及以往機型,為打包瘦身。
真機(armv7=3gs-4s,armv7s=5-5c, arm64=5s-)
模擬器(i386=3gs-5,x86_64=5s-)

設(shè)置 framework 支持的最低版本

- 將 xib 、圖像資源包含到 framework 中,將需要暴露的 .h 文件放在
Public中,需要編譯的 .m 文件放在Compile Sources,并在建立 framework 時自動生成的頭文件中加入要暴露的頭文件.h,否則會報 Warning


- 開始編譯
選中真機/模擬器,Command+B 編譯,分別生成只支持真機/模擬器的靜態(tài)庫,對.framework 右鍵 Show In Finder 可找到對應(yīng)的靜態(tài)庫


3、制作通用靜態(tài)庫
有兩種方法可合并上述生成的只支持真機、模擬器版本的靜態(tài)庫為通用庫
- 使用終端輸入命令行語句
lipo -create (此處填寫真機 FrameworkDemo 文件路徑) (此處填寫模擬器 FrameworkDemo 文件路徑) -output 自定義合成文件存儲路徑(合成文件的名字 FrameworkDemo ),生成合并后的 FrameworkDemo 文件,將此文件拷貝到相應(yīng)的 FrameworkDemo.framework 中替換即可。

- 使用 Aggregate
- 創(chuàng)建 Aggregate


- 編寫自定義 shell 腳本,選擇當前 target,Command+B 編譯

# define output folder environment variable
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
# copy the header file to universal dir,make sure your "${CONFIGURATION}-iphoneos/usr" dir exists
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"
# Create universal binary file using lipo
lipo -create "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}"
open "${UNIVERSAL_OUTPUTFOLDER}"
打開終端進入到對應(yīng)目錄查看靜態(tài)庫信息,使用 lipo -info 指令

我們便可直接使用 Debug-universal 里面合并后的通用版本 framework 了。以上打包的是 debug 模式,若想發(fā)布可打包成 release 模式,只需在 Edit Scheme 里設(shè)置即可。支持 `framework 靜態(tài)庫打包完畢。。
4、問題
-
當靜態(tài)庫中包含 Category 的時候,會報錯,原因是跟 OC 的 runtime 機制有關(guān)。Objective-C 沒有為每個函數(shù)、方法定義鏈接符號,它只為每個類創(chuàng)建鏈接符號。所以當我們在靜態(tài)庫中使用類別來擴展已有類的時候,鏈接器不知道如何把類原有的方法和類別中的方法整合起來,就會導致你調(diào)用類別中的方法時,出現(xiàn)”selector not recognized”,也就是找不到方法定義的錯誤。為了解決這個問題,引入了可以給鏈接器添加
-ObjC標志,它的作用就是將靜態(tài)庫中所有的和對象相關(guān)的文件都加載進來。 靜態(tài)庫中如果包含 xib 、圖片等資源,使用 NSBundle 讀取需注意資源路徑,例如
self = [[[NSBundle mainBundle] loadNibNamed:@"xxxx.framewrok/TestView" owner:nil options:nil] lastObject] ;
img.image = [UIImage imageNamed:@"xxxx.framewrok/test.png"];
完結(jié)
