iOS 動(dòng)態(tài)庫與靜態(tài)庫基礎(chǔ)

在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
_CodeSignature:簽名相關(guān);Headers:公開的頭文件;Modules:模塊頭;ZLLibaTest是二進(jìn)制文件
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目錄。

iOS 動(dòng)態(tài)庫.png

如果是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)庫

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容