在iOS開發(fā)中,將特定功能代碼封裝在一個(gè)庫中,對(duì)外提供接口調(diào)用,這樣方便維護(hù)和集成,如網(wǎng)絡(luò)庫。庫有靜態(tài)庫和動(dòng)態(tài)庫,我們?cè)诩蓵r(shí)該選擇哪種?制作自己的庫時(shí),該如何指定?
一、問題引出
在使用CocoaPods管理三方庫時(shí),Podfile文件中關(guān)于use_frameworks!的使用有以下特點(diǎn):
說明:
s.static_framework = true\false用于在庫的podspec文件中聲明是否要生成靜態(tài)庫。
| Podfile選項(xiàng) \ Podspec | s.static_framework = true | s.static_framework = false |
|---|---|---|
| use_frameworks! | 靜態(tài)庫 | 動(dòng)態(tài)庫 |
| 不使用use_frameworks! | 靜態(tài)庫,swift項(xiàng)目導(dǎo)入庫時(shí)報(bào)錯(cuò),OC不會(huì) | 靜態(tài)庫,swift項(xiàng)目導(dǎo)入庫報(bào)錯(cuò),OC不會(huì) |
| use_frameworks! :linkage => :static | 靜態(tài)庫 | 靜態(tài)庫 |
| use_frameworks! :linkage => :dynamic | 靜態(tài)庫 | 動(dòng)態(tài)庫 |
結(jié)論:
1.swift項(xiàng)目需要使用
use_frameworks!選項(xiàng),后面可以接:linkage => :static等。
2.庫通過s.static_framework = true指定了生成靜態(tài)庫,那么集成時(shí)就會(huì)是靜態(tài)庫。
3.庫沒有通過s.static_framework = true指定靜態(tài)庫,那么集成時(shí)可以通過上述表格中方式控制。
現(xiàn)在我們知道了如何通過pod控制集成動(dòng)、靜態(tài)庫,那么動(dòng)態(tài)庫、靜態(tài)庫究竟有啥區(qū)別?
二、動(dòng)、靜態(tài)庫簡(jiǎn)單對(duì)比
| 對(duì)比項(xiàng) | 靜態(tài)庫 | 動(dòng)態(tài)庫 |
|---|---|---|
| 文件格式 |
.a、framework、xcframework
|
.tbd、.dylib、framework、xcframework
|
| 編譯鏈接 | 鏈接時(shí)合并到可執(zhí)行文件中;推送擴(kuò)展等插件依賴了該庫也會(huì)拷貝一份 | 編譯鏈接時(shí)不合并,會(huì)獨(dú)立生成一個(gè)動(dòng)態(tài)庫類型的Mach-O文件,放在xxx.app/Frameworks/xxx.framework中;其它擴(kuò)展、插件依賴了不會(huì)再生成一份 |
| 對(duì)啟動(dòng)影響 | 內(nèi)容跟隨主二進(jìn)制加載到內(nèi)存,對(duì)啟動(dòng)影響較小 | 啟動(dòng)時(shí)dyld會(huì)加載其Mach-O并進(jìn)行符號(hào)解析,相比靜態(tài)庫更耗時(shí) |
| 包體積 | 如果主工程依賴該庫而擴(kuò)展插件未依賴,那么包體積會(huì)相比做成動(dòng)態(tài)庫小一點(diǎn) | 相比靜態(tài)庫要大一丁點(diǎn) |
三、了解靜態(tài)庫
Static libraries are collections or archives of object files.即靜態(tài)庫是.o文件的集合或歸檔。
1. iOS工程中的靜態(tài)庫
靜態(tài)庫主要文件形式是.a文件,以及靜態(tài)庫類型的framwork。使用CocoaPos集成的庫,如果在Podfile中寫了:
install! 'cocoapods',
# 禁用輸入輸出路徑,不在生成的 Xcode 項(xiàng)目中包含特定的輸入輸出路徑,從而避免一些可能的兼容性問題。
disable_input_output_paths: true,
generate_multiple_pod_projects: true # 讓每個(gè)pod依賴庫成為一個(gè)單獨(dú)的項(xiàng)目引入,這樣大大提升了編譯速度
那么每個(gè)pod install后每個(gè)庫會(huì)對(duì)應(yīng)一個(gè)工程,可以通過在對(duì)應(yīng)工程的build setting中查看Mach-O type確定靜態(tài)庫還是動(dòng)態(tài)庫。
關(guān)于Mach-O基本了解可以查看:iOS中Mach-O概覽
2. framework靜態(tài)庫和.a的區(qū)別
》.a是一個(gè)純二進(jìn)制文件,.framework中有二進(jìn)制文件、頭文件、資源文件、模塊文件Modules。
》常規(guī)開發(fā)時(shí),.a文件不能直接使用,至少要有.h文件配合,.framework文件可以直接使用。
》.a + .h + sourceFile + Modules = .framework。
純swift生成靜態(tài)庫時(shí),如果生成.a靜態(tài)庫無法直接使用,建議生成framework靜態(tài)庫。(swift語言生成.a文件后,我們拖入項(xiàng)目中使用會(huì)發(fā)現(xiàn)import模塊會(huì)報(bào)錯(cuò):No such module 'XXX')
3. 制作靜態(tài)庫
3.1手動(dòng)制作一個(gè)靜態(tài)庫
你自己根據(jù)網(wǎng)上資料的方法操作生成,但如果給其他庫中定義的類增加OC分類時(shí),需要注意是否需要增加-Objc編譯標(biāo)識(shí)。
3.2 通過cocoapods
方法一:
在庫的podspec文件中聲明生成靜態(tài)庫,這樣在pod install之后,xcode最終會(huì)編譯為靜態(tài)庫。
# 在庫的podspec文件中寫如下設(shè)置
s.static_framework = true
方法二:
在Podfile中集成庫時(shí)聲明:
use_frameworks! :linkage => :static
或者另一個(gè)方法在pre_install中進(jìn)行hook設(shè)置static_framework
4. 靜態(tài)庫framework里面的構(gòu)成
我們打開手動(dòng)制作的靜態(tài)庫framework,里面的文件有:
| 文件夾 | 內(nèi)容 |
|---|---|
| framework靜態(tài)庫 |
![]() framework靜態(tài)庫.png
|
| Modules | ![]() Modules文件詳情.png
|
5. Modules
蘋果推出Modules主要是為了支持模塊化編程,提供更清晰的代碼組織和更好的命名空間管理。Modules文件夾里放著.modulemap和.swiftmodule文件。.modulemap是用于C\OC,.swiftmodule用于swift。
有了Modules后,對(duì)于OC庫、Swift庫、OC+Swift混編的庫,外部使用時(shí)都可以導(dǎo)入模塊,不需要導(dǎo)入頭文件,使得開發(fā)更加方便。
@import WebKit.WebKitLegacy; //in Objective-C
import WebKit.WebKitLegacy //in Swift
細(xì)節(jié)可以閱讀:開發(fā)進(jìn)階-Module與Swift庫
6. 制作靜態(tài)庫注意點(diǎn)
6.1 靜態(tài)庫符號(hào)沖突
符號(hào)沖突指的是在OC/C的靜態(tài)庫中,全局變量、靜態(tài)變量或函數(shù)名如果與應(yīng)用程序或其他靜態(tài)庫中的全局符號(hào)相同,導(dǎo)致沖突發(fā)生符號(hào)重復(fù)定義錯(cuò)誤. (親測(cè),如果多個(gè)靜態(tài)庫中有相同名稱的OC分類,不會(huì)導(dǎo)致符號(hào)沖突,對(duì)于相同名稱分類中相同方法的調(diào)用,最終調(diào)用的方法實(shí)現(xiàn)是較晚編譯的庫中方法。)
使用Swift庫一般不會(huì)產(chǎn)生符號(hào)沖突,Swift 引入了模塊化編程的概念,每個(gè) 模塊擁有自己的命名空間,不同模塊中相同名稱的符號(hào)不會(huì)發(fā)生沖突。
6.2 靜態(tài)庫中OC分類中的方法找不到運(yùn)行時(shí)奔潰
場(chǎng)景
假設(shè)一個(gè)靜態(tài)庫,庫中有OC寫的分類,但分類所屬的類定義不在庫中如NSString。
在把這個(gè)靜態(tài)庫集成到工程里后,如果編譯設(shè)置other linker flags沒有添加-ObjC,那么在使用這個(gè)OC分類的方法時(shí),就會(huì)在運(yùn)行時(shí)奔潰: unrecognized selector sent to class ..
原因
由于標(biāo)準(zhǔn)UNIX靜態(tài)庫的實(shí)現(xiàn)、鏈接器和Objective-C的動(dòng)態(tài)特性之間的問題,出現(xiàn)了“選擇器未識(shí)別”運(yùn)行時(shí)異常。Objective-C并沒有為每個(gè)函數(shù)(或方法,在Objective-C中)定義鏈接器符號(hào),而是只為每個(gè)類生成鏈接器符號(hào)。如果使用類別擴(kuò)展預(yù)先存在的類,則鏈接器不知道如何將核心類實(shí)現(xiàn)的對(duì)象代碼與類別實(shí)現(xiàn)相關(guān)聯(lián)。這樣可以防止在生成的應(yīng)用程序中創(chuàng)建的對(duì)象響應(yīng)類別中定義的選擇器。
這也就是說,靜態(tài)庫中的OC分類的方法沒有與類關(guān)聯(lián)起來。
改法
如果是手動(dòng)集成這種靜態(tài)庫,需要在主工程中的other linker flags添加-ObjC。
-ObjC參數(shù)的作用:
This flag causes the linker to load every object file in the library that defines an Objective-C class or category. While this option will typically result in a larger executable (due to additional object code loaded into the application), it will allow the successful creation of effective Objective-C static libraries that contain categories on existing classes.
此標(biāo)志使鏈接器加載庫中定義Objective-C類或類別的每個(gè)對(duì)象文件。雖然此選項(xiàng)通常會(huì)導(dǎo)致更大的可執(zhí)行文件(由于應(yīng)用程序中加載了額外的對(duì)象代碼),但它將允許成功創(chuàng)建有效的Objective-C靜態(tài)庫,其中包含現(xiàn)有類的類別。
有興趣可以了解具體原因 iOS靜態(tài)庫中類的分類問題和符號(hào)沖突問題。
另外,如果使用的庫是動(dòng)態(tài)庫,也不會(huì)有這個(gè)問題,原因是動(dòng)態(tài)庫是運(yùn)行時(shí)才鏈接的。
動(dòng)態(tài)庫是在運(yùn)行時(shí)被動(dòng)態(tài)加載到內(nèi)存中的,而不是在編譯時(shí)被靜態(tài)鏈接。這意味著在動(dòng)態(tài)庫加載時(shí),它的所有代碼才會(huì)被加入到進(jìn)程的地址空間中。相比之下,靜態(tài)庫在編譯時(shí)就會(huì)被鏈接到可執(zhí)行文件中,可能會(huì)引起加載的時(shí)機(jī)問題。
CocoaPods集成靜態(tài)庫沒有這個(gè)問題
如果是使用CocoaPods集成靜態(tài)庫,那么會(huì)自動(dòng)給工程添加編譯參數(shù)-ObjC, 所以使用CocoaPods集成的靜態(tài)庫中的OC分類不會(huì)有這個(gè)問題。
Pods-工程名.debug.xcconfig中
OTHER_LDFLAGS = $(inherited) -ObjC -framework "AFNetworking"
四、了解動(dòng)態(tài)庫
1. iOS中的動(dòng)態(tài)庫
系統(tǒng)提供的framework都是動(dòng)態(tài)庫類型,比如UIKit.framework、libc++.tbd;在集成庫時(shí),如果是靜態(tài)庫類型,那么靜態(tài)庫內(nèi)容最終是在主二進(jìn)制中;如果是動(dòng)態(tài)庫,會(huì)放在ipa中的Frameworks目錄。

如果是cocoapods集成庫,那么如果在庫的spec中沒有指定s.static_framework = true時(shí),在podfile以下寫法都是生成動(dòng)態(tài)庫:
方法一:
use_frameworks!
方法二:
use_frameworks! :linkage => :dynamic #使用動(dòng)態(tài)鏈接
2. 動(dòng)態(tài)庫的格式介紹
.tbd(Text-Based Stub Library)
.tbd 文件是一種文本格式的庫文件描述符,主要包含了庫的元數(shù)據(jù)信息,如符號(hào)列表、版本信息等。
在 iOS 中,.tbd 文件通常用于描述系統(tǒng)框架和庫,例如 iOS SDK 提供的框架。
這些文件并不包含實(shí)際的二進(jìn)制代碼,而是提供了一個(gè)輕量級(jí)的描述,用于編譯和鏈接時(shí)確定庫的接口和依賴關(guān)系。
在 Xcode 構(gòu)建過程中,.tbd 文件會(huì)被用于生成實(shí)際的動(dòng)態(tài)庫鏈接信息。
.dylib
.dylib 文件是實(shí)際的動(dòng)態(tài)庫文件,包含了編譯好的二進(jìn)制代碼、數(shù)據(jù)等。
在 iOS 中,.dylib 文件用于存儲(chǔ)動(dòng)態(tài)鏈接庫的實(shí)現(xiàn),可以由系統(tǒng)或第三方提供。
這些文件是真正的共享庫,運(yùn)行時(shí)動(dòng)態(tài)加載到應(yīng)用程序中,提供所需的功能。
.framework
目錄結(jié)構(gòu)是規(guī)范化,是一種更為結(jié)構(gòu)化的庫格式,用于更方便地組織和使用共享代碼和資源。
XCFramework
引入了對(duì)多平臺(tái)和多架構(gòu)的支持,可以包含適用于不同平臺(tái)和處理器架構(gòu)的二進(jìn)制版本。類似于胖二進(jìn)制。
系統(tǒng)動(dòng)態(tài)庫
各種格式都有。從 Xcode7 在導(dǎo)入系統(tǒng)動(dòng)態(tài)庫時(shí),可以發(fā)現(xiàn).dylib文件變成了.tbd文件。.tbd文件相比.dylib文件來說包大小更小,實(shí)際使用的還是dylib的二進(jìn)制代碼庫。
stackoverflow的回答
比如: libsqlite3.tbd是個(gè)文本文件,其安裝名是libsqlite3.dylib.
archs: [ armv7, armv7s, arm64 ]
platform: ios
install-name: /usr/lib/libsqlite3.dylib
current-version: 216.4
compatibility-version: 9.0
exports:
- archs: [ armv7, armv7s, arm64 ]
symbols: [ __sqlite3_lockstate, __sqlite3_purgeEligiblePagerCacheMemory,
__sqlite3_system_busy_handler, __sqlite_auto_profile,
...
So it appears that the .dylib file is the actual library of binary code that your project is using and is located in the /usr/lib/ directory on the user's device. The .tbd file, on the other hand, is just a text file that is included in your project and serves as a link to the required .dylib binary. Since this text file is much smaller than the binary library, it makes the SDK's download size smaller.
二方庫
我們創(chuàng)建的動(dòng)態(tài)庫一般是framework和xcframework格式。
3. 動(dòng)態(tài)庫的鏈接
Static frameworks are linked at compile time. Dynamic frameworks are linked at runtime
| 庫類型 | 鏈接時(shí)期截圖 |
|---|---|
| 靜態(tài)庫 | ![]() 靜態(tài)庫.png
|
| 動(dòng)態(tài)庫 | ![]() 動(dòng)態(tài)庫.png
|
3. 動(dòng)態(tài)庫的優(yōu)缺點(diǎn)
系統(tǒng)的動(dòng)態(tài)庫是各個(gè)APP可以共享一份的,這樣能節(jié)省內(nèi)存。自己生成的動(dòng)態(tài)庫僅限于自己APP內(nèi)部共享,即和擴(kuò)展、插件等進(jìn)程共享。
動(dòng)態(tài)庫也可支持設(shè)置啟動(dòng)時(shí)不加載,在實(shí)際用到的時(shí)使用dlopen加載;
http://www.itdecent.cn/p/08b0cb296278
缺點(diǎn):
動(dòng)態(tài)庫相比做成靜態(tài)庫,最終的ipa包體積會(huì)更大一點(diǎn)點(diǎn);啟動(dòng)時(shí),動(dòng)態(tài)庫需要獨(dú)立加載并動(dòng)態(tài)鏈接符號(hào),所以啟動(dòng)耗時(shí)多。
五、XCFramework
XCFramework 是蘋果新出的庫類型,在 Xcode 11 及 cocoapods 1.9 以上版本被支持,與普通動(dòng)態(tài)庫/靜態(tài)庫最大的區(qū)別是將多個(gè)平臺(tái)(iOS, macOS, tvOS, watchOS, iPadOS, carPlayOS,模擬器)的二進(jìn)制庫,捆綁到一個(gè)可分發(fā)的.xcframework捆綁包中,支持所有的蘋果平臺(tái)和架構(gòu)。
對(duì)比使用 .framework 格式,使用 .xcframework 格式 APP 包大小和啟動(dòng)速度都有提升。
相關(guān)資料:
蘋果關(guān)于Mach-O的說明文檔:
https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/MachOTopics/0-Introduction/introduction.html#//apple_ref/doc/uid/TP40001827-SW1
蘋果關(guān)于動(dòng)態(tài)庫的說明文檔:
https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/OverviewOfDynamicLibraries.html#//apple_ref/doc/uid/TP40001873-SW1
蘋果關(guān)于靜態(tài)庫OC分類時(shí)的文檔:
Building Objective-C static libraries with categories:
網(wǎng)友總結(jié)文檔:
XCFramework 基礎(chǔ)-用腳本生成
iOS靜態(tài)庫中類的分類問題和符號(hào)沖突問題
iOS之深入解析靜態(tài)庫和動(dòng)態(tài)庫



