在iOS開發中,將特定功能代碼封裝在一個庫中,對外提供接口調用,這樣方便維護和集成,如網絡庫。庫有靜態庫和動態庫,我們在集成時該選擇哪種?制作自己的庫時,該如何指定?
一、問題引出
在使用CocoaPods管理三方庫時,Podfile
文件中關于use_frameworks!
的使用有以下特點:
說明:
s.static_framework = true\false
用于在庫的podspec文件中聲明是否要生成靜態庫。
Podfile選項 \ Podspec | s.static_framework = true | s.static_framework = false |
---|---|---|
use_frameworks! | 靜態庫 | 動態庫 |
不使用use_frameworks! | 靜態庫,swift項目導入庫時報錯,OC不會 | 靜態庫,swift項目導入庫報錯,OC不會 |
use_frameworks! :linkage => :static | 靜態庫 | 靜態庫 |
use_frameworks! :linkage => :dynamic | 靜態庫 | 動態庫 |
結論:
1.swift項目需要使用
use_frameworks!
選項,后面可以接:linkage => :static等。
2.庫通過s.static_framework = true
指定了生成靜態庫,那么集成時就會是靜態庫。
3.庫沒有通過s.static_framework = true
指定靜態庫,那么集成時可以通過上述表格中方式控制。
現在我們知道了如何通過pod控制集成動、靜態庫,那么動態庫、靜態庫究竟有啥區別?
二、動、靜態庫簡單對比
對比項 | 靜態庫 | 動態庫 |
---|---|---|
文件格式 |
.a 、framework 、xcframework
|
.tbd 、.dylib 、framework 、xcframework
|
編譯鏈接 | 鏈接時合并到可執行文件中;推送擴展等插件依賴了該庫也會拷貝一份 | 編譯鏈接時不合并,會獨立生成一個動態庫類型的Mach-O文件,放在xxx.app/Frameworks/xxx.framework中;其它擴展、插件依賴了不會再生成一份 |
對啟動影響 | 內容跟隨主二進制加載到內存,對啟動影響較小 | 啟動時dyld會加載其Mach-O并進行符號解析,相比靜態庫更耗時 |
包體積 | 如果主工程依賴該庫而擴展插件未依賴,那么包體積會相比做成動態庫小一點 | 相比靜態庫要大一丁點 |
三、了解靜態庫
Static libraries are collections or archives of object files.
即靜態庫是.o文件的集合或歸檔。
1. iOS工程中的靜態庫
靜態庫主要文件形式是.a文件,以及靜態庫類型的framwork。使用CocoaPos集成的庫,如果在Podfile中寫了:
install! 'cocoapods',
# 禁用輸入輸出路徑,不在生成的 Xcode 項目中包含特定的輸入輸出路徑,從而避免一些可能的兼容性問題。
disable_input_output_paths: true,
generate_multiple_pod_projects: true # 讓每個pod依賴庫成為一個單獨的項目引入,這樣大大提升了編譯速度
那么每個pod install
后每個庫會對應一個工程,可以通過在對應工程的build setting
中查看Mach-O type
確定靜態庫還是動態庫。
關于Mach-O基本了解可以查看:iOS中Mach-O概覽
2. framework
靜態庫和.a
的區別
》.a是一個純二進制文件,.framework中有二進制文件、頭文件、資源文件、模塊文件Modules。
》常規開發時,.a文件不能直接使用,至少要有.h文件配合,.framework文件可以直接使用。
》.a + .h + sourceFile + Modules = .framework。
純swift生成靜態庫時,如果生成.a
靜態庫無法直接使用,建議生成framework
靜態庫。(swift語言生成.a文件后,我們拖入項目中使用會發現import
模塊會報錯:No such module 'XXX'
)
3. 制作靜態庫
3.1手動制作一個靜態庫
你自己根據網上資料的方法操作生成,但如果給其他庫中定義的類增加OC分類時,需要注意是否需要增加-Objc編譯標識。
3.2 通過cocoapods
方法一:
在庫的podspec
文件中聲明生成靜態庫,這樣在pod install
之后,xcode最終會編譯為靜態庫。
# 在庫的podspec文件中寫如下設置
s.static_framework = true
方法二:
在Podfile中集成庫時聲明:
use_frameworks! :linkage => :static
或者另一個方法在pre_install中進行hook設置static_framework
4. 靜態庫framework里面的構成
我們打開手動制作的靜態庫framework,里面的文件有:
文件夾 | 內容 |
---|---|
framework靜態庫 |
framework靜態庫.png
|
Modules | Modules文件詳情.png
|
5. Modules
蘋果推出Modules
主要是為了支持模塊化編程,提供更清晰的代碼組織和更好的命名空間管理。Modules文件夾里放著.modulemap
和.swiftmodule
文件。.modulemap
是用于C\OC,.swiftmodule
用于swift。
有了Modules后,對于OC庫、Swift庫、OC+Swift混編的庫,外部使用時都可以導入模塊,不需要導入頭文件,使得開發更加方便。
@import WebKit.WebKitLegacy; //in Objective-C
import WebKit.WebKitLegacy //in Swift
細節可以閱讀:開發進階-Module與Swift庫
6. 制作靜態庫注意點
6.1 靜態庫符號沖突
符號沖突指的是在OC/C的靜態庫中,全局變量、靜態變量或函數名如果與應用程序或其他靜態庫中的全局符號相同,導致沖突發生符號重復定義錯誤. (親測,如果多個靜態庫中有相同名稱的OC分類,不會導致符號沖突,對于相同名稱分類中相同方法的調用,最終調用的方法實現是較晚編譯的庫中方法。)
使用Swift庫一般不會產生符號沖突,Swift 引入了模塊化編程的概念,每個 模塊擁有自己的命名空間,不同模塊中相同名稱的符號不會發生沖突。
6.2 靜態庫中OC分類中的方法找不到運行時奔潰
場景
假設一個靜態庫,庫中有OC寫的分類,但分類所屬的類定義不在庫中如NSString
。
在把這個靜態庫集成到工程里后,如果編譯設置other linker flags
沒有添加-ObjC
,那么在使用這個OC分類的方法時,就會在運行時奔潰: unrecognized selector sent to class ..
原因
由于標準UNIX靜態庫的實現、鏈接器和Objective-C的動態特性之間的問題,出現了“選擇器未識別”運行時異常。Objective-C并沒有為每個函數(或方法,在Objective-C中)定義鏈接器符號,而是只為每個類生成鏈接器符號。如果使用類別擴展預先存在的類,則鏈接器不知道如何將核心類實現的對象代碼與類別實現相關聯。這樣可以防止在生成的應用程序中創建的對象響應類別中定義的選擇器。
這也就是說,靜態庫中的OC分類的方法沒有與類關聯起來。
改法
如果是手動集成這種靜態庫,需要在主工程中的other linker flags
添加-ObjC
。
-ObjC參數的作用:
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.
此標志使鏈接器加載庫中定義Objective-C類或類別的每個對象文件。雖然此選項通常會導致更大的可執行文件(由于應用程序中加載了額外的對象代碼),但它將允許成功創建有效的Objective-C靜態庫,其中包含現有類的類別。
有興趣可以了解具體原因 iOS靜態庫中類的分類問題和符號沖突問題。
另外,如果使用的庫是動態庫,也不會有這個問題,原因是動態庫是運行時才鏈接的。
動態庫是在運行時被動態加載到內存中的,而不是在編譯時被靜態鏈接。這意味著在動態庫加載時,它的所有代碼才會被加入到進程的地址空間中。相比之下,靜態庫在編譯時就會被鏈接到可執行文件中,可能會引起加載的時機問題。
CocoaPods集成靜態庫沒有這個問題
如果是使用CocoaPods集成靜態庫,那么會自動給工程添加編譯參數-ObjC
, 所以使用CocoaPods集成的靜態庫中的OC分類不會有這個問題。
Pods-工程名.debug.xcconfig中
OTHER_LDFLAGS = $(inherited) -ObjC -framework "AFNetworking"
四、了解動態庫
1. iOS中的動態庫
系統提供的framework都是動態庫類型,比如UIKit.framework
、libc++.tbd
;在集成庫時,如果是靜態庫類型,那么靜態庫內容最終是在主二進制中;如果是動態庫,會放在ipa
中的Frameworks
目錄。
如果是cocoapods
集成庫,那么如果在庫的spec中沒有指定s.static_framework = true
時,在podfile以下寫法都是生成動態庫:
方法一:
use_frameworks!
方法二:
use_frameworks! :linkage => :dynamic #使用動態鏈接
2. 動態庫的格式介紹
.tbd
(Text-Based Stub Library)
.tbd 文件是一種文本格式的庫文件描述符,主要包含了庫的元數據信息,如符號列表、版本信息等。
在 iOS 中,.tbd 文件通常用于描述系統框架和庫,例如 iOS SDK 提供的框架。
這些文件并不包含實際的二進制代碼,而是提供了一個輕量級的描述,用于編譯和鏈接時確定庫的接口和依賴關系。
在 Xcode 構建過程中,.tbd 文件會被用于生成實際的動態庫鏈接信息。
.dylib
.dylib 文件是實際的動態庫文件,包含了編譯好的二進制代碼、數據等。
在 iOS 中,.dylib 文件用于存儲動態鏈接庫的實現,可以由系統或第三方提供。
這些文件是真正的共享庫,運行時動態加載到應用程序中,提供所需的功能。
.framework
目錄結構是規范化,是一種更為結構化的庫格式,用于更方便地組織和使用共享代碼和資源。
XCFramework
引入了對多平臺和多架構的支持,可以包含適用于不同平臺和處理器架構的二進制版本。類似于胖二進制。
系統動態庫
各種格式都有。從 Xcode7 在導入系統動態庫時,可以發現.dylib
文件變成了.tbd
文件。.tbd
文件相比.dylib
文件來說包大小更小,實際使用的還是dylib的二進制代碼庫。
stackoverflow的回答
比如: libsqlite3.tbd
是個文本文件,其安裝名是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.
二方庫
我們創建的動態庫一般是framework和xcframework格式。
3. 動態庫的鏈接
Static frameworks are linked at compile time. Dynamic frameworks are linked at runtime
庫類型 | 鏈接時期截圖 |
---|---|
靜態庫 | 靜態庫.png
|
動態庫 | 動態庫.png
|
3. 動態庫的優缺點
系統的動態庫是各個APP可以共享一份的,這樣能節省內存。自己生成的動態庫僅限于自己APP內部共享,即和擴展、插件等進程共享。
動態庫也可支持設置啟動時不加載,在實際用到的時使用dlopen
加載;
http://www.lxweimin.com/p/08b0cb296278
缺點:
動態庫相比做成靜態庫,最終的ipa包體積會更大一點點;啟動時,動態庫需要獨立加載并動態鏈接符號,所以啟動耗時多。
五、XCFramework
XCFramework 是蘋果新出的庫類型,在 Xcode 11 及 cocoapods 1.9 以上版本被支持,與普通動態庫/靜態庫最大的區別是將多個平臺(iOS, macOS, tvOS, watchOS, iPadOS, carPlayOS,模擬器)的二進制庫,捆綁到一個可分發的.xcframework捆綁包中,支持所有的蘋果平臺和架構。
對比使用 .framework 格式,使用 .xcframework 格式 APP 包大小和啟動速度都有提升。
相關資料:
蘋果關于Mach-O的說明文檔:
https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/MachOTopics/0-Introduction/introduction.html#//apple_ref/doc/uid/TP40001827-SW1
蘋果關于動態庫的說明文檔:
https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/OverviewOfDynamicLibraries.html#//apple_ref/doc/uid/TP40001873-SW1
蘋果關于靜態庫OC分類時的文檔:
Building Objective-C static libraries with categories:
網友總結文檔:
XCFramework 基礎-用腳本生成
iOS靜態庫中類的分類問題和符號沖突問題
iOS之深入解析靜態庫和動態庫