出處:http://chuquan.me/2021/02/14/understand-ios-library-and-framework/
在軟體開發中,靜態庫和動態庫在各個方面為我們提供了便利。在計算機專業相關課程中,我們學習過靜態庫和動態庫的一些理論,那麼這些理論如何對映到 iOS 開發之中呢?iOS 中有很多相關概念和術語,對此,我並不是非常清晰。為了能夠向編譯最佳化的最終目標更近一步,我花了些時間進行了學習,並總結此文以供回顧複習。如果有什麼寫得不對的地方,歡迎指正。
靜態庫 VS 動態庫靜態庫和動態庫的共同點在於:它們都是編譯好的二進位制檔案;不同點在於:它們的用法不同。下面兩張圖可以看出兩者在用法上的差異。
對於靜態庫,在應用程式進行編譯連結時,會將靜態庫中的 被使用的部分 都新增到應用程式的可執行檔案,這意味著應用程式的可執行檔案大小會隨著靜態庫數量的增加而增大。在執行時,靜態庫會隨著應用程式的可執行檔案一起載入到同一程式碼區。在 iOS 開發中,應用程式的可執行檔案就是 ipa 解壓後,包內容中與 app 同名的可執行檔案。
對於動態庫,事實上可以根據其載入時機分為兩種: 動態連結庫 、 動態載入庫 。
動態連結庫 :當載入目標主程式的可執行檔案時,動態庫也會被載入到記憶體中,即 在程式啟動時載入 。在 Build Phase 中的 Linked Framework and Library 階段中宣告的就是動態連結庫,它們隨著動態載入庫 :當用到相關功能時,使用 dlopen 或其他方式對相關動態庫進行載入,即 在程式啟動後加載 。在 iOS 開發中,在專案設定【General】->【Frameworks, Libraries, and Embedded Content】中,定義了應用程式所依賴的靜態庫和動態庫。
對於 系統動態庫 ,可以將 Embed 屬性設定成 Do Not Embed ,因為 iOS 系統提供了相關的庫,我們無需將它們再嵌入到應用程式的 ipa 包中,如: Foundation.framework 、 UIKit.framework 。對於 使用者動態庫 ,需要將 Embed 屬性設定成 Embed ,因為連結發生在執行時,連結器需要從應用程式的 ipa 包中載入完整的動態庫。對於 靜態庫 ,需要將 Embed 屬性設定成 Do Not Embed ,因為連結發生在編譯時,編譯完成後相關程式碼都已經包含在了應用程式的可執行檔案中了,無需在應用程式的 bundle 中再儲存一份。Library VS Framework在 iOS 開發中,有兩個令人難以分清的概念: 框架 (Framework)、 庫 (Library)。下面,我們來捋一捋這兩個概念。
庫是所有 UNIX 系統共有的,可移植;框架是 OSX/iOS 特有的,不可移植。為了提供對 OSX/iOS 的高階特性的支援,同時又不願意這些特性被移植到其他平臺,蘋果提出了 框架 的概念。不過,從本質而言,框架是基於庫實現的,可以認為框架是對庫進行了封裝,是一種特殊形式的庫。
框架 = 庫(靜態庫/動態庫)+ .h(標頭檔案) + bundle(資源包)
從本質而言, 庫是一個二進位制檔案 。因此,對於 OSX/iOS 和 UNIX 而言,其具體格式也有所不同。在 OSX/iOS 中,庫採用 Mach-O 格式進行儲存;在 UNIX 中,庫採用 ELF 格式進行儲存。除此之外,庫在兩種作業系統中的命名字尾也有所一定的差異。 對於 OSX/iOS,靜態庫的字尾是 .a ,動態庫的字尾是 .dylib ;在 UNIX 中,靜態庫的字尾是 .a ,動態庫的字尾是 .so 。
在 OSX/iOS 中,還有一種字尾為 .tbd 的動態庫。 .tbd 檔案本質上是一個 YAML 文字檔案,其描述了需要連結的動態庫資訊,其主要目的是 減小應用程式的下載大小 。當應用程式引用了使用者裝置中 /usr/lib/ 目錄下的 .dylib 動態庫時,會自動生成一個 .tbd 檔案,其僅僅描述了專案中引用的動態庫資訊,因此可以減小應用程式的下載大小。具體細節見 傳送門 。
Umbrella Framework保護傘框架(Umbrella Framework)本質上可以認為是 對普通框架進行了封裝,其包含了多個框架並隱藏它們彼此之間複雜的依賴關係 。舉個例子,Cocoa 框架就是一個保護傘框架,其包含了三個框架:AppKit、CoreData、Foundation。
/* Cocoa.h Cocoa Framework Copyright (c) 2000-2004, Apple Computer, Inc. All rights reserved.*/#import <Foundation/Foundation.h>#import <AppKit/AppKit.h>#import <CoreData/CoreData.h>
Framework Bundle Structure
上文提到框架本質上就是對庫進行了封裝,其包含一個或多個共享庫以及相關的支援檔案。下面,我們分別來看看普通框架和保護傘框架的目錄結構。
首先,我們需要找到框架的儲存位置。事實上,框架儲存在檔案系統中的多個位置:
/System/Library/Frameworks :儲存蘋果提供的框架,如: Foundation.framework , AVFoundation.framework/Library/Frameworks :儲存第三方框架。iOS 上該目錄為空。如: OpenVPN.framework , Carthage.framework 。~/Library/Frameworks :儲存使用者提供的框架(如果有的話)。此外,應用程式也可能會包含自己的框架,在其 Contents/Frameworks 目錄下儲存了應用程式專用的框架。其實,這裡對應的就是應用程式 ipa 包中的 Frameworks 目錄。
透過檢視這些路徑下的框架結構,我們可以發現大多是普通框架,其目錄結構大致如下:
MyFramework.framework/ Headers -> Versions/Current/Headers MyFramework -> Versions/Current/MyFramework Resources -> Versions/Current/Resources Versions/ A/ MyFramework Resources/ English.lproj/ InfoPlist.strings Info.plist Current -> A
框架使用了與應用程式不同的 bundle 結構。框架基於早期的 bundle 格式, 支援同時將多個版本的框架和標頭檔案存放在一個 bundle 中 ,也就是上述這種結構。這種型別的 bundle 被稱為 Versioned Bundle 。
系統透過目錄名稱上的 .framework 副檔名來識別框架。框架目錄下的各個目錄和檔案的作用如下:
Versions :存放了多個版本的框架,分別用 A 、 B 、 C … 這樣的子目錄進行儲存。透過一個 Current 符號連結來選擇使用哪個版本的框架。Resources :基於 Versions/Current 的符號連結,指向當前版本的框架的 Resources 目錄。MyFramework :基於 Versions/Current 的符號連結,指向當前版本的框架的 MyFramework 目錄。Headers :基於 Version/Current 的符號連結,指向當前版本的框架的 Headers 目錄,存放了希望暴露給開發者的公開標頭檔案。上面介紹了普通框架的結構,而保護傘框架的結構其實跟普通框架的結構基本一致,唯一區別在於:保護傘框架會多一個 Frameworks 目錄,用於儲存其封裝的子框架。如下所示是 Core Servives 保護傘框架的結構:
CoreServices.framework/ CoreServices -> Versions/Current/CoreServices CoreServices_debug -> Versions/Current/CoreServices_debug CoreServices_profile -> Versions/Current/CoreServices_profile Frameworks -> Versions/Current/Frameworks Headers -> Versions/Current/Headers Resources -> Versions/Current/Resources Versions/ A/ CoreServices CoreServices_debug CoreServices_profile Frameworks/ CarbonCore.framework CFNetwork.framework OSServices.framework SearchKit.framework WebServicesCore.framework Headers/ Components.k.h CoreServices-gcc3.p CoreServices-gcc3.pp CoreServices.h CoreServices.p CoreServices.pp CoreServices.r Resources/ Info-macos.plist version.plist Current -> A
除此之外,我們在構建自定義框架時,會發現框架結構下會有一個 Modudles 目錄,該目錄下預設有一個 module.modulemap 檔案。事實上, module.modulemap 是一個模組對映檔案,能夠使框架支援模組(以及子模組),在編譯時也能夠利用模組編譯的優勢進行加速 。關於模組相關的內容,可見傳送門。
注意:CocoaPods 會將每個 pod 轉換為 Umbrella Framework 並新增 module map 使其支援模組。因此,每個 Pod 的 Supporting File 中會有兩個對應的檔案: PodName-umbrella.h (Umbrella Header)、 PodName.modulemap (Module Map)。
CocoaPods在 Podfile 中有兩個選項經常令人迷惑: use_frameworks! 、 use_modular_headers! 。那麼這兩者有什麼區別呢?
我們知道在 Xcode 9 之前,不支援 Swift 靜態庫編譯,因此 Swift pod 不得不使用動態庫編譯,即使用 use_frameworks! 。但是,引用了大量動態庫會導致應用程式啟動時間變長。
好在 Xcode 9 之後開始支援 Swift 靜態庫編譯。為了充分利用該特性,從 CocoaPods 1.5.0 開始,對於 Swift pod,開發者不用必須在 Podfile 中指定 user_frameworks! 以強制使用動態庫編譯。不過,要注意的是,如果一個 Swift pod 依賴了一個 OC pod,那麼我們要為對應的 OC pod 開啟 modular headers ( use_modular_headers! 就會開啟 modular headers )。那麼,Swift 引用 OC 時為什麼要開啟 modular headers ? 事實上,開啟 modular headers 的本質就是將 pod 轉換為 Modular(也就是支援模組),而 Modular 是可以直接在 Swift 中 import 的,不需要再經過 bridging-header 進行橋接,從而簡化了 Swift 引用 OC 的方式 。
只有支援了模組的框架,才能支援透過模組化標頭檔案(Modular Header)的方式進行匯入。Clang 支援模組編譯,能夠加速編譯,減少出錯。我們可以透過新增 modulemap 檔案使框架支援模組。
簡化 Swift 引用 OC 的方式是使用 use_modular_headers! 的一個原因,除此之外, use_modular_headers! 還能夠解決一個歷史原因。
在 CocoaPods 誕生之初,其致力於封裝儘可能多的第三方庫。為此,CocoaPods 使用了較為寬鬆的標頭檔案搜尋路徑(Header Search Paths),允許 pod 之間的相互引用,無需考慮名稱空間,不必採用 #import <NameSpace/fileName.h> 的模組匯入方式,允許採用 #import "fileName.h" 的匯入方式。
但是,如果給 pod 新增 module map 使其支援模組化,會導致 #import "fileName.h" 無法正常匯入。使用 use_modular_headers! 可以強制使用更優的模組匯入方式。
在 CocoaPods 1.5.0 中,為了使用模組匯入方式。對於 pod 開發者,可以在 pod_target_xxconfig 內設定 'DEFINES_MODULE' => 'YES' 。對於 pod 使用者,可以在 Podfile 中新增 use_modular_headers! 指定採用模組匯入的方式,也可以透過 :modular_headers => true 配置只讓特定的 pod 採用模組匯入的方式。
總結本文首先介紹了靜態庫和動態庫的區別,然後介紹了 iOS 中庫與框架的概念及其區別,最後介紹了 CocoaPods 中 use_frameworks! 和 use_modular_headers! 的區別。
參考iOS靜態庫與動態庫的區別與打包初識iOS中的動態庫和靜態庫深入剖析iOS動態連結庫iOS 開發中的『庫』(一)iOS 開發中的『庫』(二)細說iOS靜態庫和動態庫Creating a Framework for iOSWhat is the difference between Embedded Binaries and Linked Frameworks透過dlopen使用動態庫Embedding Frameworks In An AppiOS的靜態庫和動態庫Why Xcode 7 shows .tbd instead of .dylib?Overview of Dynamic Libraries“Do Not Embed”, “Embed & Sign”, “Embed Without Signing”. What are they?. What they do?《深入解析 MacOSX 與 iOS 作業系統》CocoaPods 1.5.0 — Swift Static Libraries為什麼應該用模組取代C/C++中的標頭檔案?Introduction to Framework Programming Guide應用 Swift 靜態庫的各種坑iOS - Umbrella Header在framework中的應用Module System of Swiftcocoapods的靜態庫和動態庫作者:楚權的世界
出處:http://chuquan.me/2021/02/14/understand-ios-library-and-framework/