前言
背景是我們項(xiàng)目升級(jí)某個(gè)SDK评姨,結(jié)果發(fā)現(xiàn)項(xiàng)目和SDK出現(xiàn)符號(hào)沖突贪磺。
符號(hào)沖突是接入SDK有可能會(huì)出現(xiàn)的問(wèn)題赋朦,本文便嘗試從技術(shù)角度去解決。
正文
因?yàn)楸旧眄?xiàng)目本身不便公開(kāi)芦岂,所以新建兩個(gè)工程來(lái)模擬這個(gè)場(chǎng)景瘪弓。(工程代碼地址)
LYTestFramework靜態(tài)庫(kù)工程,里面帶有不公開(kāi)的SSUser類(lèi)禽最,模擬SDK腺怯;
LearnSymbol普通工程,模擬項(xiàng)目的主工程川无,里面也有SSUser類(lèi)呛占;
將LYTestFramework手動(dòng)導(dǎo)入LearnSymbol工程:
這樣便出現(xiàn)了兩個(gè)SSUser:
一個(gè)是LYTestFramework.framework內(nèi)不公開(kāi)的SSUser;
@implementation SSUser
// from framework's user
- (void)test {
NSLog(@"framework test");
}
@end
另一個(gè)是LearnSymbol工程內(nèi)自己帶的SSUser懦趋;
@implementation SSUser
// from project's user
- (void)test {
NSLog(@"main test");
}
@end
那么編譯的時(shí)候晾虑,就會(huì)出現(xiàn)duplicate symbol _OBJC_CLASS_$_SSUser
的錯(cuò)誤。
可是仅叫,當(dāng)我真正開(kāi)始運(yùn)行的時(shí)候帜篇,才發(fā)現(xiàn)竟然編譯通過(guò)了:
對(duì)比了這個(gè)新建工程和原工程的Other Linker Flags,發(fā)現(xiàn)是因?yàn)樾鹿こ躺倭艘粋€(gè)-ObjC的設(shè)置诫咱,另外原工程還有-l secXXX的flag笙隙。
回顧下-ObjC 、 -all_load 坎缭、-force_load
這三個(gè)flag的區(qū)別:
-
-ObjC
鏈接器會(huì)加載靜態(tài)庫(kù)中所有的Objective-C類(lèi)和Category竟痰;(導(dǎo)致可執(zhí)行文件變大) -
-all_load
鏈接器會(huì)加載靜態(tài)庫(kù)中所有的Objective-C類(lèi)和Category(這里和上面一樣)签钩;當(dāng)靜態(tài)庫(kù)只有Category時(shí)-ObjC會(huì)失效,需要使用這個(gè)flag坏快; -
-force_load
加載特定靜態(tài)庫(kù)的全部類(lèi)铅檩,與-all_load類(lèi)似但是只限定于特定靜態(tài)庫(kù),所以-force_load需要指定靜態(tài)庫(kù)莽鸿;當(dāng)兩個(gè)靜態(tài)庫(kù)存在同樣的符號(hào)時(shí)柠并,使用-all_load會(huì)出現(xiàn)duplicate symbol
的錯(cuò)誤,此時(shí)可以選擇將其中一個(gè)庫(kù)-force_load富拗;(需要注意兩個(gè)庫(kù)的版本是不是一致的)
所以這里的直接編譯通過(guò)的原因:工程中已經(jīng)有了SSUser類(lèi)的符號(hào)臼予,所以鏈接的時(shí)候會(huì)直接使用工程中的SSUser符號(hào),所以編譯運(yùn)行完的結(jié)果是調(diào)用了工程中的SSUser類(lèi)啃沪,靜態(tài)庫(kù)中的SSUser并沒(méi)有被鏈接粘拾。
而原工程的-l secXXX的鏈接flag是什么意思?
gcc有三個(gè)很像的參數(shù)创千,分別是-l -l -L缰雇,第一個(gè)I是i的大寫(xiě)桃笙,中間的是L的小寫(xiě)l。
- -I怜森,用于指定頭文件的地址殿雪;
- -l暇咆,用于指定具體的靜態(tài)庫(kù)、動(dòng)態(tài)庫(kù)丙曙;
- -L爸业,用于指定庫(kù)文件的地址;
回到我們的工程亏镰,我們往Other Linker Flags添加-ObjC
的flag之后扯旷,再次嘗試編譯。
此時(shí)終于復(fù)現(xiàn)了之前的符號(hào)沖突:
duplicate symbol _OBJC_CLASS_$_SSUser in:
/Users/loyinglin/Library/Developer/Xcode/DerivedData/LearnSymbol-dhlwaeprifzeedegywrvodujmcoj/Build/Intermediates.noindex/LearnSymbol.build/Debug-iphonesimulator/LearnSymbol.build/Objects-normal/x86_64/SSUser.o
/Users/loyinglin/Documents/Learn/LearnDuplicateSymbol/LearnSymbol/LearnSymbol/LYTestFramework.framework/LYTestFramework(SSUser.o)
duplicate symbol _OBJC_METACLASS_$_SSUser in:
/Users/loyinglin/Library/Developer/Xcode/DerivedData/LearnSymbol-dhlwaeprifzeedegywrvodujmcoj/Build/Intermediates.noindex/LearnSymbol.build/Debug-iphonesimulator/LearnSymbol.build/Objects-normal/x86_64/SSUser.o
/Users/loyinglin/Documents/Learn/LearnDuplicateSymbol/LearnSymbol/LearnSymbol/LYTestFramework.framework/LYTestFramework(SSUser.o)
ld: 2 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
接下來(lái)從嘗試技術(shù)的角度去解決這個(gè)問(wèn)題:
解決方案1:去掉主工程的SSUser索抓,用靜態(tài)庫(kù)里面的SSUser钧忽;
不可行,靜態(tài)庫(kù)的SSUser沒(méi)有開(kāi)放頭文件逼肯,無(wú)法使用耸黑;
解決方案2:在主工程的compile source去掉SSUser.m文件,工程中僅用SSUser.h文件來(lái)調(diào)用汉矿;(假設(shè)兩方用的是同個(gè)版本)
嘗試編譯崎坊,符號(hào)沖突可以解決备禀;
運(yùn)行的結(jié)果表示調(diào)用了LYTestFramework中的SSUser:
2019-07-14 14:13:21.767218+0800 LearnSymbol[28982:5102302] framework test
解決方案3:去掉LYTestFramework靜態(tài)庫(kù)中的SSUser符號(hào)洲拇,鏈接時(shí)全部使用主工程的SSUser奈揍;
我們知道靜態(tài)庫(kù)是多個(gè).o文件組成的,那么我們可以找到SSUser.o然后剔除赋续,靜態(tài)庫(kù)依賴(lài)的SSUser會(huì)在鏈接時(shí)找到主工程生成的SSUser.o男翰;
我們先進(jìn)入打包的出來(lái)的LYTestFramework.framework文件夾,目錄如下:
我們?cè)贖eaders的同級(jí)目錄創(chuàng)建一個(gè)目錄pack纽乱,將LYTestFramework這個(gè)文件移動(dòng)到pack目錄中蛾绎。
用ar -t LYTestFramework
指令,可以看到這個(gè)庫(kù)中的.o文件包括SSUser.o鸦列,下面嘗試手動(dòng)移除這個(gè)SSUser.o文件:
- 1租冠、先將LYTestFramework解壓:
ar xv LYTestFramework
; - 2薯嗤、手動(dòng)刪除SSUser.o文件顽爹;
- 3、回到上級(jí)目錄骆姐,重新把.o文件打包:
ar rcs LYTestFramework pack/*.o
镜粤;
再用ar -t LYTestFramework
指令查看,發(fā)現(xiàn)SSUser.o已經(jīng)不見(jiàn)玻褪,重新打包成功運(yùn)行肉渴,結(jié)果表示調(diào)用了主工程的SSUser:
2019-07-17 16:20:33.576468+0800 LearnSymbol[86290:7683465] main test
附1:這為了簡(jiǎn)化邏輯,這里只有模擬器的cpu架構(gòu)带射,沒(méi)有包括armv7/arm64同规,用 lipo -info LYTestFramework指令可以看到:
LYTestFramework is architecture: x86_64;
如果有多種cpu架構(gòu)窟社,需要分別對(duì)每種架構(gòu)進(jìn)行處理捻浦,再合并。
附2:以上的解決方案均是假設(shè)兩方用的是同個(gè)靜態(tài)庫(kù)版本桥爽。如果是不同版本朱灿,則需要修改命名,使得多個(gè)版本的靜態(tài)庫(kù)可以共存钠四。
另一個(gè)Linking中的選項(xiàng):
Dead Code Stripping 是對(duì)程序編譯出的可執(zhí)行二進(jìn)制文件中沒(méi)有被實(shí)際使用的代碼進(jìn)行Strip操作盗扒。
Dead code stripping removes code that the compiler determines is unreachable.
代碼舉例:
總結(jié)
符號(hào)沖突是引入第三方庫(kù)的時(shí)候,有可能會(huì)遇到的問(wèn)題缀去。
當(dāng)庫(kù)A和庫(kù)B的符號(hào)出現(xiàn)沖突時(shí)侣灶,如果庫(kù)A和庫(kù)B沖突的符號(hào),是功能相同的符號(hào)缕碎,則可以選擇去掉其中一個(gè)符號(hào)褥影,選擇只加載其中一個(gè)庫(kù)的符號(hào)。
如果兩個(gè)符號(hào)所表示的意義不同咏雌,比如說(shuō)不來(lái)自同一個(gè)庫(kù)(僅僅是命名一樣凡怎,導(dǎo)致符號(hào)沖突)校焦,或者來(lái)自同一個(gè)庫(kù)但是版本不同,這種只能通過(guò)重命名或者修改庫(kù)的代碼邏輯來(lái)實(shí)現(xiàn)共存统倒。
附錄
靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)的思考
編譯與鏈接過(guò)程的思考
https://blog.csdn.net/djl4104804/article/details/43099061
https://garbageout.wordpress.com/2015/03/24/dead-code-stripping/