做過(guò)iOS的Library開(kāi)發(fā)的都知道,開(kāi)發(fā)者可以創(chuàng)建靜態(tài)庫(kù)工程(Static Library),編譯出來(lái)的產(chǎn)物是.a文件;也可以創(chuàng)建動(dòng)態(tài)庫(kù)工程(Dynamic Library),編譯出來(lái)的產(chǎn)物是.framework文件。然而,你也有可能遇到過(guò),外面是.framework文件,里面包含的卻是.a的文件,還有的二進(jìn)制文件根本沒(méi)有格式后綴。但是系統(tǒng)的動(dòng)態(tài)庫(kù)文件卻是.dylib(xcode 8.0以前)和.tbd(xcode 8.0及以后)格式的。剛開(kāi)始接觸時(shí)肯定對(duì)這些充滿了疑惑,本文將對(duì)這些問(wèn)題進(jìn)行一些探討。
在進(jìn)行探討之前,首先要介紹一下Mach-O。
Mach-O
Mach-O格式全稱為Mach Object文件格式的縮寫(xiě),是mac上可執(zhí)行文件的格式,類似于windows上的PE格式 (Portable Executable ), linux上的elf格式 (Executable and Linking Format)。
mach-o文件類型分為:
- Executable:應(yīng)用的主要二進(jìn)制
- Dylib Library:動(dòng)態(tài)鏈接庫(kù)(又稱DSO或DLL)
- Static Library:靜態(tài)鏈接庫(kù)
- Bundle:不能被鏈接的Dylib,只能在運(yùn)行時(shí)使用dlopen( )加載,可當(dāng)做macOS的插件
- Relocatable Object File:可重定向文件類型
看著貌似很陌生,我們可以類比著相對(duì)熟悉一點(diǎn)的Linux看。Linux中常見(jiàn)的庫(kù)文件有.o文件、.a文件、.so文件。
以一個(gè)簡(jiǎn)單的add函數(shù)源文件為例,介紹一下編譯過(guò)程。
int add(int a,int b)
{
return a+b;
}
先預(yù)處理為.i文件gcc -E add.c -o add.i
再編譯為匯編文件
gcc -S add.i -o add.s
再匯編為二進(jìn)制的.o文件
gcc -c add.s -o add.o
現(xiàn)在.o文件出來(lái)了。它就是C/C++編譯的產(chǎn)物,因?yàn)镃/C++編譯的單元編譯。每一個(gè).c/.cpp文件就是一個(gè)編譯單元,把所有單元都編譯好之后,再連接成一個(gè)完整的程序。所以可以看出:
- .o文件
.o文件是源碼編譯出的二進(jìn)制文件。 - .a文件
.a文件實(shí)質(zhì)上就是.o文件打了個(gè)包。一般把它叫做靜態(tài)庫(kù)文件。它在使用的時(shí)候,效果和使用.o文件是一樣的。 - .so文件
.so文件就不一樣了,它不是簡(jiǎn)單的.o文件打了一個(gè)包,它是一個(gè)ELF格式的文件,也就是linux的可執(zhí)行文件。.so文件可以用于多個(gè)進(jìn)程的共享使用(位置無(wú)關(guān)的才行),所以又叫共享庫(kù)文件。程序在使用它的時(shí)候,會(huì)在運(yùn)行時(shí)把它映射到自己進(jìn)程空間的某一處,其不在使用它的程序中。
OSX或者iOS是類Unix系統(tǒng),和Linux很相似。文件格式也很類似,其中Relocatable Object File和.o文件類型對(duì)應(yīng);Static Library和.a文件類型對(duì)應(yīng);Dylib Library和.so文件類型對(duì)應(yīng)。
iOS Library
在任意一個(gè)iOS工程中,可以進(jìn)入到Build Settings,搜索"Mach-O",可以搜出"Mach-O Type"配置項(xiàng),在下拉菜單里可以看到有5個(gè)選項(xiàng),如圖所示:可以看到,正好和前面的幾種Mach-O文件類型對(duì)應(yīng)。其中Executable用于編譯應(yīng)用,Bundle用于編譯資源包,Relocatable Object File用于編譯單元二進(jìn)制文件。Static Library和Dynamic Library分別用于編譯靜態(tài)和動(dòng)態(tài)庫(kù)。Executable、Bundle以及Relocatable Object File比較好區(qū)分,這里主要探討Static Library和Dynamic Library。
前面提到過(guò),靜態(tài)庫(kù)一般是.a文件,動(dòng)態(tài)庫(kù)一般是.framework文件。為什么說(shuō)一般,因?yàn)殪o態(tài)庫(kù)也可能沒(méi)有后綴;.framework文件其實(shí)只是個(gè)文件夾,真正的二進(jìn)制文件在.framework里面。.framework里面的二進(jìn)制文件也可能是靜態(tài)庫(kù),也有可能是動(dòng)態(tài)庫(kù)。有后綴也可能沒(méi)有后綴。因此有時(shí)候不通過(guò)工具很難區(qū)分。所以這里推薦一款Mach-O格式文件瀏覽器:MachOView。
MachOView下載地址:http://sourceforge.net/projects/machoview/
MachOView源碼地址:https://github.com/gdbinit/MachOView
用MachOView打開(kāi)Mach-O格式文件,可以清楚地看到文件類型,是動(dòng)態(tài)庫(kù)還是靜態(tài)庫(kù)。如果是動(dòng)態(tài)庫(kù),可以看到Shared Library標(biāo)志;如果是靜態(tài)庫(kù),可以看到Static Library標(biāo)志。
動(dòng)態(tài)庫(kù)(這里只講自己編譯的動(dòng)態(tài)庫(kù),因?yàn)橄到y(tǒng)的動(dòng)態(tài)庫(kù)還有些不一樣,后面會(huì)講到)和靜態(tài)庫(kù)的主要區(qū)別有以下幾點(diǎn):
- 在使用靜態(tài)庫(kù)時(shí),把庫(kù)拖進(jìn)工程,設(shè)置好library search path即可使用;在使用動(dòng)態(tài)庫(kù)時(shí),多一個(gè)步驟,要在Build Settings——>General——>Embeeded Binaries中add一下,否則應(yīng)用啟動(dòng)時(shí)會(huì)崩潰。
- 靜態(tài)庫(kù)里不能有資源文件;動(dòng)態(tài)庫(kù)有.framework文件夾包裹著,可以帶資源文件。
- 靜態(tài)庫(kù)會(huì)被編譯進(jìn)應(yīng)用的二進(jìn)制文件里;動(dòng)態(tài)庫(kù)只是被完整地拷貝到應(yīng)用的沙盒里,不會(huì)被編譯進(jìn)應(yīng)用的二進(jìn)制文件里。
- 靜態(tài)庫(kù)實(shí)質(zhì)上就是源碼編譯出的多個(gè).o二進(jìn)制文件打了個(gè)包;動(dòng)態(tài)庫(kù)可以用于多個(gè)進(jìn)程的共享使用(位置無(wú)關(guān)的才行),所以又叫共享庫(kù)文件。程序在使用它的時(shí)候,會(huì)在運(yùn)行時(shí)把它映射到自己進(jìn)程空間的某一處,其不在使用它的程序中。
- 在應(yīng)用啟動(dòng)時(shí),系統(tǒng)會(huì)把應(yīng)用所鏈接的所有靜態(tài)庫(kù)(包括系統(tǒng)的和自己編譯的)全部放進(jìn)分配好的內(nèi)存空間里,如果一次性加載的內(nèi)容過(guò)多,會(huì)造成App啟動(dòng)慢;動(dòng)態(tài)庫(kù)分兩種情況,如果是自己編譯的,則會(huì)被通過(guò)load的方式全部加載進(jìn)內(nèi)存;如果是系統(tǒng)的動(dòng)態(tài)庫(kù)則相對(duì)智能些,只有當(dāng)應(yīng)用用到這個(gè)庫(kù)時(shí),系統(tǒng)才把這個(gè)庫(kù)加載進(jìn)內(nèi)存。
現(xiàn)在對(duì)靜態(tài)庫(kù)應(yīng)該沒(méi)有什么疑問(wèn)了,很容易區(qū)分。但是自己編譯的動(dòng)態(tài)庫(kù)和系統(tǒng)的動(dòng)態(tài)庫(kù)還不是很清晰。有人把自己編譯的動(dòng)態(tài)庫(kù)歸類靜態(tài)庫(kù),可能有兩點(diǎn)理由:1、自己編譯的動(dòng)態(tài)庫(kù)用法和靜態(tài)庫(kù)很類似,而且不能動(dòng)態(tài)加載;2、自己編譯的動(dòng)態(tài)庫(kù)是.framework文件,而系統(tǒng)的動(dòng)態(tài)庫(kù)是.tbd文件,格式都不一樣。
目前,自己編譯的動(dòng)態(tài)庫(kù)用法和靜態(tài)庫(kù)用法確實(shí)很類似,而且不能動(dòng)態(tài)加載。但是不能動(dòng)態(tài)加載的原因是系統(tǒng)的限制。查看蘋(píng)果的API文檔,會(huì)發(fā)現(xiàn)有一個(gè)方法提供了加載可執(zhí)行文件的功能,那就是NSBundle的load方法(底層實(shí)現(xiàn)為dlopen函數(shù)), 如下所示:
然而,這個(gè)方法的使用是有前提的。那就是庫(kù)和app的簽名必需一致。iOS可能是出于安全考慮,在加載可執(zhí)行代碼前,需要校驗(yàn)簽名。load方法的內(nèi)部實(shí)現(xiàn)是調(diào)用了dlopen,而dlopen內(nèi)部還會(huì)調(diào)用dlopen_preflight先校驗(yàn)簽名。如果庫(kù)不是事先打包進(jìn)app,和app使用同一簽名,就會(huì)報(bào)簽名錯(cuò)誤,從而加載不成功。如下圖所示:
那么肯定有人會(huì)問(wèn),既然無(wú)法加載成功,蘋(píng)果為什么要提供這個(gè)方法?答案是,雖然iOS無(wú)法使用,但是Mac OS可以使用,很明顯這個(gè)方法目前是提供給Mac OS使用的。如果以后系統(tǒng)放開(kāi)簽名校驗(yàn),那么iOS中也就可以動(dòng)態(tài)加載了。
至于系統(tǒng)的動(dòng)態(tài)庫(kù)是.tbd文件,不妨先調(diào)查一下這個(gè).tbd。詳見(jiàn):http://www.cocoachina.com/ios/20160506/16141.html
簡(jiǎn)而言之,其實(shí)tbd文件不是一個(gè)庫(kù),而是一個(gè)文本文件,其中包含架構(gòu)信息,和在真實(shí)運(yùn)行時(shí)候二進(jìn)制所在的位置,以及動(dòng)態(tài)庫(kù)的符號(hào)表還有類的一些信息,這些信息在編譯階段足夠了。在系統(tǒng)要使用時(shí)才通過(guò)tbd文件中的鏈接信息加載真正的二進(jìn)制文件。
總結(jié)
- Mach-O格式的幾種文件和iOS工程Build Settings里面的配置項(xiàng)是對(duì)應(yīng)的。
- 可以通過(guò)MachOView工具查看Mach-O格式,不能只看文件后綴。
- iOS工程Build Settings里面的配置項(xiàng)選擇Static Library和Dynamic Library分別編譯出的二進(jìn)制文件還是有本質(zhì)區(qū)別的。
- 系統(tǒng)動(dòng)態(tài)庫(kù)和自己編譯的動(dòng)態(tài)庫(kù)本質(zhì)上是一樣的,只是使用方式不一樣。自己編譯的動(dòng)態(tài)庫(kù)由于簽名校驗(yàn)限制,只能當(dāng)作靜態(tài)庫(kù)一樣使用;系統(tǒng)的動(dòng)態(tài)庫(kù)不受簽名校驗(yàn)限制,可以動(dòng)態(tài)加載。
參考鏈接:
http://blog.csdn.net/zhangjie1989/article/details/54614246
http://www.lxweimin.com/p/54d842db3f69
http://www.cocoachina.com/ios/20160506/16141.html