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

原文地址

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

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

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

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

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

靜態(tài)庫定義:

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

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

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

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

$gcc -c hello.c

示例代碼用到了 printf () 這個(gè)iO函數(shù)株灸,而該函數(shù)的符號(hào)就位于 printf.o 中,所以我們就需要讓鏈接器鏈接 printf.o擎值,當(dāng)然printf.o 也會(huì)依賴其他目標(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 這個(gè)集成開發(fā)環(huán)境所特有的,目的是讓連接器器 ld 除了默認(rèn)參數(shù)外再根添加額外參數(shù)進(jìn)行鏈接工作进每。

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的鏈接器并不會(huì)為每個(gè)方法建立符號(hào)表汹粤,而是為每個(gè)類建立鏈接符號(hào)。這樣的話靜態(tài)庫中定義了已存在的類的分類田晚,鏈接器就以為這個(gè)類存在了嘱兼,不會(huì)將分類和核心類代碼關(guān)聯(lián)(合并)起來,這樣在最后可執(zhí)行文件中肉瓦,就會(huì)找不到分類里所定義的方法遭京。

例如如下錯(cuò)誤:

Snip20170613_4.png

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

Snip20170613_5.png

如何解決這個(gè)問題鲫趁?

三個(gè)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.

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

上面說得很清楚钉疫,-ObjC 會(huì)解決靜態(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)中去碉克,但是這就存在一個(gè)問題了凌唬,如果兩個(gè)靜態(tài)庫中,都使用了同一份可重定位的目標(biāo)文件(這是一個(gè)很常見的問題漏麦,例如大家的目標(biāo)文件都使用了用以名字 base64.o)就會(huì)發(fā)生 ld: duplicate symbol 符號(hào)沖突問題客税,所以不太建議使用况褪。

-force_load:

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

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

Snip20170613_1.png

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

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

具體做法:

Snip20170613_2.png

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


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

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

-Snip20170613_7.png
Snip20170613_6.png

很明顯就是 MyClass.o 這個(gè)目標(biāo)文件發(fā)生符號(hào)沖突了疼阔, 其實(shí)不這樣看也行戒劫,反正編譯的時(shí)候 Clang 編譯器就就會(huì)有符號(hào)沖突的報(bào)錯(cuò),上圖 Xcode 報(bào)錯(cuò)的那個(gè)圖就是很好的例子婆廊,可以看錯(cuò)那些目標(biāo)文件重復(fù)了迅细。

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

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

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

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

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

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

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

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

編譯,Successful!
運(yùn)行宾舅,完美敬尺!

-dead_strip (2017.8.27 更新)

參數(shù)的作用在于解決我們上面可重定位目標(biāo)文件(.o)中類符號(hào)的沖突問題,如果發(fā)生了這種情況贴浙,使用該參數(shù)就是一個(gè)非常快捷的辦法了署恍,讓 Clang 編譯器幫助我們?nèi)コ貜?fù)符號(hào)的可重定位目標(biāo)文件問題崎溃。
但是使用這個(gè)參數(shù)卻有一個(gè)問題,就是如果我們使用了該參數(shù)盯质,就不能使用 -all_load 或 -force_load袁串,因?yàn)楦哦绻覀冎付俗尵幾g器幫我們決定哪些目標(biāo)文件該被鏈接,哪些不被鏈接(-dead_strip)囱修,那么我們就不能手動(dòng)的強(qiáng)制地讓所有目標(biāo)文件都進(jìn)行鏈接了(-all_load 或 -force_load)赎瑰。如果是這樣的話,我們又回到最初的問題了-ObjC 會(huì)解決靜態(tài)庫中已存在的類的分類問題破镰,那么餐曼,如果分類存在與靜態(tài)庫,但是類并不在靜態(tài)庫的這種情況鲜漩,該怎么辦呢源譬? 顯然,對(duì)于這種情況孕似,還是得使用上面的方法


沒人說你不行踩娘,是你自己說你自己不行
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市喉祭,隨后出現(xiàn)的幾起案子养渴,更是在濱河造成了極大的恐慌,老刑警劉巖泛烙,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件理卑,死亡現(xiàn)場離奇詭異,居然都是意外死亡胶惰,警方通過查閱死者的電腦和手機(jī)傻工,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來孵滞,“玉大人中捆,你說我怎么就攤上這事》蝗模” “怎么了泄伪?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長匿级。 經(jīng)常有香客問我蟋滴,道長,這世上最難降的妖魔是什么痘绎? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任津函,我火速辦了婚禮,結(jié)果婚禮上孤页,老公的妹妹穿的比我還像新娘尔苦。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布允坚。 她就那樣靜靜地躺著魂那,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稠项。 梳的紋絲不亂的頭發(fā)上涯雅,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音展运,去河邊找鬼活逆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛乐疆,可吹牛的內(nèi)容都是我干的划乖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼挤土,長吁一口氣:“原來是場噩夢啊……” “哼琴庵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起仰美,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤迷殿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后咖杂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庆寺,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年诉字,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了懦尝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壤圃,死狀恐怖陵霉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情伍绳,我是刑警寧澤踊挠,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站冲杀,受9級(jí)特大地震影響效床,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜权谁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一剩檀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧旺芽,春花似錦谨朝、人聲如沸卤妒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至共缕,卻和暖如春洗出,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背图谷。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工翩活, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人便贵。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓菠镇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親承璃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子利耍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容