0x01 用 Xcode 生成二進位制檔案
從 Xcode
入手,在 Build Setting -> Mach-O Type 這個選項下,有著這五個型別。如果建立一個 Framework
,不手動修改的預設配置即為 Dynamic Library
。
檔案型別 | 描述 |
---|---|
Executable | 可執行二進位制檔案 |
Dynamic Library | 動態庫 |
Bundle | 非獨立二進位制檔案,顯式載入 |
Static Library | 靜態庫 |
Relocatable Object File | 可重定位的目標檔案,中間結果 |
一般情況下,無論是 SDK
還是開源類庫,首先考慮的是要使用 Dynamic Library(動態庫)
還是 Static Library(靜態庫)
。很有人真的考慮過是否要使用另外的三種型別來做。
先問可不可以,再問為什麼。現在就來試試分別使用 Executable
, Bundle
, Relocatable Object File
這三種型別來製作 Framework
,並進行程式碼共享,看看是否具有可操作性。
Executable
建立一個 Framework
專案,更改 Build Setting -> Mach-O Type 為 Executable
。CMD + B 編譯專案。可以看到如下兩個錯誤。
clang: error: invalid argument `-compatibility_version 1` only allowed with `-dynamiclib`
複製程式碼
前面說了,建立
framework
型別的專案,預設的Mach-O
型別為Dynamic Library
,既動態庫
。提示明確說明了,-compatibility_version 1
這個修飾屬性只適用於dynamiclib
型別的檔案,在Building Setting
裡面搜尋compatibility_version
,刪除預設值即可。
clang: error: invalid argument `-current_version 1` only allowed with `-dynamiclib`
複製程式碼
同上說明
根據提示去除預設的 -compatibility_version
和 -current_version
兩個配置,繼續編譯,還是出現瞭如下錯誤。
ld: entry point (_main) undefined. for architecture x86_64
複製程式碼
注意:因為是使用的模擬器進行編譯,所以是 x86_64 架構。
對於 Executable 型別,這裡基本是終點了。可執行檔案會直接被系統執行,所以需要一個 _main 執行入口。
Bundle
建立一個 Framework 專案,更改 BuildSetting -> Mach-O Type 為 Bundle。同樣修改了 -compatibility_version
和 -current_version
兩個配置之後,CMD + B 編譯成功。
當然,這樣還是不知道生成的 Framework
是否可以使用。所以我們建立對應的 Target
來進行測試。
建立 Demo
並將生成的 Framework
嵌入進應用後,CMD + B 出現如下的錯誤:
ld: can`t link with bundle (MH_BUNDLE) only dylibs (MH_DYLIB) file `/Users/cbangchen/Library/Developer/Xcode/DerivedData/SDK_Mach_O_Type_Bundle_Demo-gzjgmzohehdzvvbknavovstdtyfm/Build/Products/Debug-iphonesimulator/SDK_Mach_O_Type_Bundle_Demo.framework/SDK_Mach_O_Type_Bundle_Demo` for architecture x86_64
複製程式碼
提示明確的說明了,系統不支援連結 MH_BUNDLE
型別的檔案。
這樣就結束了嗎?不如掙扎一下。
回頭看看上面那個表格,對應 Bundle
型別的描述寫的是 —— 非獨立二進位制檔案,顯式載入。既然是 Bundle
檔案,就來試著用資源載入的方式 load 一下。
首先將 Bundle
用資源載入的方式(Build Phases -> Copy Bundle Resources)進行新增。然後在 VC
裡面寫入下面的語句。
NSString *bundleString = [[NSBundle mainBundle] pathForResource:@"SDK_Mach_O_Type_Bundle_Demo" ofType:@"framework"];
NSBundle *SDKBundle = [NSBundle bundleWithPath:bundleString];
複製程式碼
使用上面的語句獲取到 Bundle 檔案。好像還缺了一點東西。
[SDKBundle load];
複製程式碼
把它給 load
掉。再用 Runtime
來看看有沒有 load
成功。
Class justForTestClass = NSClassFromString(@"JustForTest");
[justForTestClass performSelector:@selector(justForTestMethod)];
複製程式碼
輸出了預設的語句。還是跑起來了。從這裡開始就變得有點意思了,接著來看下一種型別。
Relocatable Object File
建立一個 Framework 專案,更改 BuildSetting -> Mach-O Type 為 Relocatable Object File。同樣修改了 -compatibility_version
和 -current_version
兩個配置之後。CMD + B 編譯出現如下錯誤:
ld: -r and -dead_strip cannot be used together
複製程式碼
移除 Dead Code Stripping
,CMD + B,如下:
-Dead Code Stripping
這個屬性用於刪除檔案中不需要載入的符號,減小二進位制檔案大小。
d: -rpath can only be used when creating a dynamic final linked image
複製程式碼
-rpath
這個符號用來連結其他依賴的二進位制檔案,Relocatable Object File
類似於中間檔案,不能帶有依賴關係。
把 -rpath
移除,CMD + B 編譯成功。開一個 Target 測試一下,也可以正常連結使用。
0x04 五種二進位制檔案的結構
Executable
格式並不是決定一個檔案是否是可執行檔案的原因,在 Windows
作業系統下,可執行程式可以是 .exe 檔案, .sys
檔案或者 .com
等型別的檔案。可以簡單的理解為是作業系統允許的,可以單獨執行的檔案。
當然,前面我們也說了 Executable
需要一個執行入口,這也導致了這種型別只適合用來製作程式本身。
Dynamic Library
動態庫對於 iOS
系統的構成有著極其重要的意義。把共同的程式碼抽離出來,保證系統執行過程中,相同內容的庫只被載入一次。解耦重用就是動態庫出現的直接理由。
注意
蟹 @ValiantCat 同學指正。新增補充如下:
只有相同簽名的動態庫,才可以進行系統級別的共享。
而為了保證這一點,動態庫生成的二進位制檔案並沒有被合併到可執行二進位制檔案中。
否則。
試想,幾乎每一個應用都引用到了 Foundation
和 UIKit
兩個類庫,來構建介面或者進行基本的資料處理,如果每一個應用都嵌入了這樣的兩個庫,那整個系統中就有多餘的 2(n – 1) 個庫了。比較浪費資源。
再來看看這個動態庫的結構:
左邊的一些欄位,和動態庫的使用有著較密切的關係。例如 LC_LOAD_DYLIB
的幾個欄位。描述的是,這個動態庫所依賴的其他類庫。使用 MachOView
開啟其中一行,可以看到下面的資訊。
主要的 Name
欄位指示了依賴庫的連結位置。在載入目標動態庫的時候就會到連結位置,對依賴庫進行載入。另外同時有一個欄位,在功能上和 LC_LOAD_DYLIB
十分相似,就是 LC_RPATH
,這個欄位開啟來看是這樣的。
path
這個欄位有一個連結地址。同樣是依賴庫載入的路徑,和 LC_LOAD_DYLIB
不同的是,path
目錄下的依賴庫並不原本儲存於系統,它們之間的依賴關係是開發者主動新增的。
動態庫的結構裡面,有相當一部分用來記錄和其他類庫之間的依賴關係,由動態庫的特性決定。
Bundle
Bundle
的結構和 Dynamic Library
的結構十分相似。
看起來幾乎是一樣的。
但其實一樣嗎?至少有一樣東西是不一樣的。我們使用型別為 Dynamic Library
這種型別的二進位制檔案,需要在 Xcode
中進行嵌入,直接使用 Bundle
則要求手動載入。而蘋果已經明確禁止在程式執行的時候手動載入可執行程式碼,這種行為,是具有絕對的危險的,我們一定要規避這樣的風險。
Static Library && Relocatable Object File
這張圖比較寡淡,只可以看出 JustForTest.o
的結構和 Relocatable Object File
的整個很像。
當然,那是因為 JustForTest.o
的內容就是 Relocatable Object File
中的內容了,.o
檔案經過進一步的 ar
操作可以歸檔成靜態庫檔案。
可以簡單的理解 Relocatable Object File
是組裝靜態庫和動態庫的零件,而靜態庫和動態庫就是可執行二進位制檔案的元件。這裡用了零件和元件的概念,零件是不可缺少的,元件則是可選的。正好形容 .o 檔案,靜動態庫和可執行二進位制檔案之間的關係。
0x03 Static Library,Relocatable Object File 以及 Dynamic Library 的對比
Bundle
和 Executable
都因為自身的侷限性而不適合用來製作包括 SDK
和開源類庫。這裡不深入來講。而Dynamic Library
相對 Relocatable Object File
以及 Static Library
來說,區別是十分明顯的。
在使用上,Dynamic Library
更靈活;複用性更強;且就安全來說,統一放置在 Payload/Framework
目錄下的自建的動態庫,不參與應用的加殼操作,安全性稍遜一籌。而 Relocatable Object File
以及 Static Library
都是在編譯後直接合併到最後的可執行檔案中的,缺點相對不夠靈活,但安全性稍強。
值得注意的是,動態庫是應用在啟動的時候進行載入的,所以會適當減慢啟動速度。這一點,很值得考慮,權衡。
那如果要偏向靜態的方案,應該選擇 Relocatable Object File
還是 Static Library
?兩種型別的檔案在使用上沒有區別,但在大小上,則稍有差距。來做個實驗。
// .h
+ (void)justForTestMethod;
// .m
+ (void)justForTestMethod {
NSLog(@"? 淺談 SDK 開發");
}
複製程式碼
內容設定為上面的幾行程式碼。分別製作 Relocatable Object File
和 Static Library
型別的 Framework
,架構選擇為 x86_64
。
檔案型別 | 二進位制包的大小 | 檔案描述截圖 |
---|---|---|
Static Library | 21KB | |
Relocatable Object File | 18KB |
可以看到有較小的差別。可能是因為我們用來編譯生成的檔案內容也很少的原因。稍微新增多一些程式碼,用 AFNetworking 來做個例子,原始檔有 273KB。
檔案型別 | 二進位制包的大小 | 不容置疑的證據 |
---|---|---|
Static Library | 780KB | |
Relocatable Object File | 601KB |
這一次的差距就稍微大了一些。而這樣的檔案大小差距,也會隨著程式碼量的增多而越發明顯。如果有縮小應用體積的需求,更改 Mach-O
型別是一種可行的方案。
0x06 結論
如果使用動態庫,需要考慮的是:1. 對於啟動速度的影響。2. 對於保密要求高的線下渠道 SDK,可能會被從 .app/ 中單獨拿出來,反編譯研究具體實現。靜態庫則比較安全一點。而如果要使用靜態庫,建議使用 Relocatable Object File 來減少二進位制檔案的大小。
最後,還要說的是,最好是同時採取 Dynamic Library 和 Relocatable Object File 兩種方式來進行程式碼分發。提供多樣的選擇,滿足不同的需求。
完。