iOS靜態(tài)庫中類的分類問題和符號沖突問題(Xcode other Link Flags)

原文地址

什么是可執(zhí)行文件?

要理解靜態(tài)庫我們就得清楚最終可執(zhí)行文件(.out)的生成過程了

目標(biāo)文件的生成過程

當(dāng)我們寫的源代碼 hello.c 經(jīng)過上述4個步驟:預(yù)處理(Prepressing)、編譯(Compilation)、匯編(Assembly)和鏈接(Linking)后,就生成了我們的可執(zhí)行文件 a.out 了。
注意:可重定位目標(biāo)文件是(.o) 而 可執(zhí)行文件是 (.out),以下文章描述都遵循這種叫法。

當(dāng)我們明白到什么是可執(zhí)行文件后,那么再來看看究竟什么是靜態(tài)庫?

靜態(tài)庫定義:

其實一個靜態(tài)庫可以簡單地看成一組目標(biāo)文件(.o)的集合,即很多目標(biāo)文件經(jīng)過壓縮打包后形成的一個文件。
比如我們在Linux中最常用的C語言靜態(tài)庫libc位于 /usr/lib/libc.a,它屬于 glibc 項目的一部分。而 glibc 本身是用C語言開發(fā)的,它由成百上千個C語言源代碼文件組成,也就是說,編譯完成以后有相同數(shù)量的目標(biāo)文件,比如輸入輸出有 printf.o,scanf.o;文件操作有fread.o,fwrite.o;時間日期有date.o,time.o;內(nèi)存管理有malloc.o等。把這些零散的目標(biāo)文件直接提供給庫的使用者,很大程度上會造成文件傳輸、管理和組織方面的不便,于是通常人們使用 ar 壓縮程序?qū)⑦@些目標(biāo)文件壓縮到一起,并且對其進行編號和索引,以便于查找和檢索,就形成了libc.a這個靜態(tài)庫文件。

例如當(dāng)我們使用如下代碼的時候

#include<stdio.h>
int main(int argc, const char* argv[] )
{
   printf("Hello World");
}

我們先用編譯器和匯編器將上述代碼生成目標(biāo)文件 hello.o。

$gcc -c hello.c

示例代碼用到了 printf () 這個iO函數(shù),而該函數(shù)的符號就位于 printf.o 中,所以我們就需要讓鏈接器鏈接 printf.o,當(dāng)然printf.o 也會依賴其他目標(biāo)文件,而 printf.o 等文件又在 靜態(tài)庫 libc.a 當(dāng)中,
我們就需要讓鏈接器 (ld) 靜態(tài)鏈接到靜態(tài)庫 libc.a 中去尋找示例代碼中需要用到的目標(biāo)文件,并生成目標(biāo)文件 hello.out。

$ ld -static -e main hello.o /usr/lib/libc.a -o hello.out

我們今天的主題主要在于鏈接與靜態(tài)庫這一步中


Xcode 中配置鏈接器(other linker flags)

other linker flags 是 xcode 這個集成開發(fā)環(huán)境所特有的,目的是讓連接器器 ld 除了默認參數(shù)外再根添加額外參數(shù)進行鏈接工作。

Object-C 鏈接特性:

The "selector not recognized" runtime exception occurs due to an issue between the implementation of standard UNIX static libraries, the linker and the dynamic nature of Objective-C. Objective-C does not define linker symbols for each function (or method, in Objective-C) - instead, linker symbols are only generated for each class. If you extend a pre-existing class with categories, the linker does not know to associate the object code of the core class implementation and the category implementation. This prevents objects created in the resulting application from responding to a selector that is defined in the category.

Object-C的鏈接器并不會為每個方法建立符號表,而是為每個類建立鏈接符號。這樣的話靜態(tài)庫中定義了已存在的類的分類,鏈接器就以為這個類存在了,不會將分類和核心類代碼關(guān)聯(lián)(合并)起來,這樣在最后可執(zhí)行文件中,就會找不到分類里所定義的方法。

例如如下錯誤:

Snip20170613_4.png

就看 log 可以看出,是 NSString 的一個分類方法 designByOhterLinker 找不到實現(xiàn)了,而這個方法,確實是一個靜態(tài)庫里面的一個分類方法。

Snip20170613_5.png

如何解決這個問題?

三個Linker 參數(shù):

  • -ObjC
  • -all_load
  • -force_load
  • -dead_strip (8.27日更新)
-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.

加入這個參數(shù)后,鏈接器會將靜態(tài)庫中的每個類和分類加載到最后的可執(zhí)行文件,當(dāng)然,這個參數(shù)會導(dǎo)致可執(zhí)行文件比較大,原因是加載了更多的額外對象的代碼到可執(zhí)行文件當(dāng)中去,但是這會解決 Objec-C 中靜態(tài)庫中已存在的類包含的分類問題。

上面說得很清楚,-ObjC 會解決靜態(tài)庫中已存在的類的分類問題,那么,如果分類存在與靜態(tài)庫,但是類并不在靜態(tài)庫的這種情況,該怎么辦呢?

Important: For 64-bit and iPhone OS applications, there is a linker bug that prevents -ObjC from loading objects files from static libraries that contain only categories and no classes. The workaround is to use the -allload or -forceload flags.

說得很清楚,使用-all_load 或 -force_load 就可以解決上述問題。

-all_load:

該參數(shù)把所找到的目標(biāo)文件都加載到可執(zhí)行文件當(dāng)中去,但是這就存在一個問題了,如果兩個靜態(tài)庫中,都使用了同一份可重定位的目標(biāo)文件(這是一個很常見的問題,例如大家的目標(biāo)文件都使用了用以名字 base64.o)就會發(fā)生 ld: duplicate symbol 符號沖突問題,所以不太建議使用。

-force_load:

該參數(shù)的作用跟 -all_load 其實是一樣的,但是 -force_load 需要指定要進行全部加載的庫文件的路徑,這樣的話,只要完全加載一個庫文件,不影響其余庫的可重定位目標(biāo)文件的按需加載。

但是也有一種最頭痛,就是當(dāng)兩個靜態(tài)庫中使用了相同的目標(biāo)文件

Snip20170613_1.png

上圖的兩個上圖的兩個 libMyOtherStaticLibrary.a 和 libMyStaticLibrary 中的 MyClass.o 類發(fā)生了沖突
那么,這個時候有兩種解決方法:

1、利用 -force_load 讓鏈接器指定編譯把其中一個靜態(tài)庫的目標(biāo)文件,不加載另一個靜態(tài)庫的重復(fù)目標(biāo)文件

具體做法:

Snip20170613_2.png

但是這么做有一個弊端,如果這兩個靜態(tài)庫同時都使用到了分類(基本上都會使用吧)那么如果只讓編譯器加載其中一個靜態(tài)庫的目標(biāo)文件 (-force_load),而不將另一個靜態(tài)庫中的分類合并加載到目標(biāo)文件的話,也是會導(dǎo)致運行的時候?qū)е律鲜龅谋罎栴}。但是如果 -foce_load 兩個靜態(tài)庫,又會有符號沖突,那么,怎么辦呢?


2、簡單來說就是去除某個靜態(tài)庫中的重復(fù)目標(biāo)文件,然后再打包

具體做法:
1)通過使用壓縮工具命令 ar -t 去查看兩個靜態(tài)庫文件里的目標(biāo)文件那些存在沖突
如下:

-Snip20170613_7.png
Snip20170613_6.png

很明顯就是 MyClass.o 這個目標(biāo)文件發(fā)生符號沖突了, 其實不這樣看也行,反正編譯的時候 Clang 編譯器就就會有符號沖突的報錯,上圖 Xcode 報錯的那個圖就是很好的例子,可以看錯那些目標(biāo)文件重復(fù)了。

2)將其中一個靜態(tài)庫中的重復(fù)目標(biāo)文件去掉,然后再次打包成靜態(tài)庫使用

  • 首先利用 lipo 命令將其中一個iOS靜態(tài)庫的文件解壓出來(因為iOS的靜態(tài)庫文件是一個將不同 CPU 架構(gòu)靜態(tài)庫合并的一個打包文件)。
-Snip20170613_9.png

可以看出 libMyOtherStaticLibrary.a 中包含了 armv7 跟 arm64 兩種架構(gòu)的靜態(tài)庫文件

  • 分別將兩種不同架構(gòu)的靜態(tài)庫文件提取出來
-Snip20170613_10.png
-Snip20170613_11.png
  • 使用 ar 壓縮工具分別將這兩個不同架構(gòu)的靜態(tài)庫文件與另一個發(fā)生沖突的靜態(tài)庫文件中的目標(biāo)文件剔除出去。
-Snip20170613_12.png

通過上面命令看出已成功將 MyClass.o 剔除出靜態(tài)庫

  • 利用 lipo 將兩個不同架構(gòu)的靜態(tài)庫重新打包封裝成 iOS 的靜態(tài)庫文件
-Snip20170613_13.png
5cac35f4-a80d-4e91-8d23-137d1d946c47.png

然后在 libMyOtherStaticLibraryOut.a 這個靜態(tài)庫重新放到工程當(dāng)中去替換原來的 libMyOtherStaticLibrary.a

Other linker flags 只需用 -ObjC 就可以了

編譯,Successful!
運行,完美!

-dead_strip (2017.8.27 更新)

參數(shù)的作用在于解決我們上面可重定位目標(biāo)文件(.o)中類符號的沖突問題,如果發(fā)生了這種情況,使用該參數(shù)就是一個非??旖莸霓k法了,讓 Clang 編譯器幫助我們?nèi)コ貜?fù)符號的可重定位目標(biāo)文件問題。
但是使用這個參數(shù)卻有一個問題,就是如果我們使用了該參數(shù),就不能使用 -all_load 或 -force_load,因為,如果我們指定了讓編譯器幫我們決定哪些目標(biāo)文件該被鏈接,哪些不被鏈接(-dead_strip),那么我們就不能手動的強制地讓所有目標(biāo)文件都進行鏈接了(-all_load 或 -force_load)。如果是這樣的話,我們又回到最初的問題了-ObjC 會解決靜態(tài)庫中已存在的類的分類問題,那么,如果分類存在與靜態(tài)庫,但是類并不在靜態(tài)庫的這種情況,該怎么辦呢? 顯然,對于這種情況,還是得使用上面的方法


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

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

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