iOS靜態(tài)庫開發(fā)中引入的第三方庫可能與宿主APP中沖突的解決方案

SDK開發(fā)中我們可能希望使用已有的第三方開源庫酸纲,比如在發(fā)送請求的功能上我們更希望用AFNetworking而非直接使用NSURLSession却桶,又如在實(shí)現(xiàn)socket連接時(shí)我們更希望用SocketRocket而非自己從零實(shí)現(xiàn)万栅。但如果我們直接把AFNetworking的源文件拖到靜態(tài)庫SDK里随闺,而宿主APP也引入了AFNetworking龟梦,這時(shí)運(yùn)行代碼就會報(bào)符號沖突(duplicate symbols)的錯(cuò)誤驾荣。


符號沖突報(bào)錯(cuò)

這時(shí)大部分人的解決方案都是手動修改引入到SDK里的開源庫代碼夺欲,包括類名谓罗、分類名媒至、全局常量名顶别、協(xié)議名等會導(dǎo)致沖突的符號。其實(shí)對于像AFNetworking(v3.2.1)這種源碼量較少的第三方庫來說拒啰,需要修改的地方都要多達(dá)47個(gè)驯绎,可想而知這是一項(xiàng)多么低效和易錯(cuò)的解決方案,而且如果下次需要升級SDK中的該第三方庫谋旦,你需要再重新手動改一遍……下邊我們來一步步深入解決這件麻煩事剩失。
首先我們考慮下怎樣避免每次都要修改第三方庫源碼,如果有一個(gè)單獨(dú)的文件來存原符號和重命名符號的對應(yīng)關(guān)系就好了蛤织,我們自然而然地會想到用宏定義赴叹。創(chuàng)建一個(gè)頭文件,比如叫XNGNamespace.h

// XNGNamespace.h

#define AFURLSessionManager XNGURLSessionManager
#define AFNetworkingReachabilityDidChangeNotification XNGNetworkingReachabilityDidChangeNotification
#define AFImageResponseSerializer XNGImageResponseSerializer
...

然后在你的SDK工程中指蚜,如果你已經(jīng)有一個(gè)預(yù)編譯頭文件(一般為xxx.pch)乞巧,在最上一行引入XNGNamespace.h,否則在Build Settings -> Prefix Header配置該文件的路徑(即把這個(gè)文件作為預(yù)編譯頭文件)摊鸡。這時(shí)你可以在Xcode中看到原本的比如AFURLSessionManager類名顏色變成和宏一樣的顏色绽媒,準(zhǔn)確地說,這個(gè)類現(xiàn)在其實(shí)叫XNGURLSessionManager了免猾。

類名顏色

現(xiàn)在有了這個(gè)文件后是辕,即使要升級SDK中的第三方庫,我們也只需要在這個(gè)文件里做少量增刪了猎提。
但到目前為止最麻煩的這部分事還沒解決获三,畢竟現(xiàn)在還是要靠肉眼找出那些符號,手動編寫宏定義锨苏。有沒有什么命令或腳本幫我們分析出這些符號呢疙教,這正好可以借助nm命令了。nm是Linux下用于查看指定文件(對象文件伞租、可執(zhí)行文件或?qū)ο笪募欤┲蟹柫斜淼拿钫晡剑詾榱擞眠@個(gè)命令,我們需要先做點(diǎn)準(zhǔn)備工作葵诈。

一裸弦、準(zhǔn)備工作

如上所述祟同,我們需要得到一個(gè)可供nm命令分析的文件。新建一個(gè)庫工程理疙,F(xiàn)ramework類型或Static Library類型都可以晕城,將第三方庫的源碼拖入其中,運(yùn)行產(chǎn)出靜態(tài)庫文件沪斟。因?yàn)楹筮叿治鲆彩侵苯优茉贛acOS上广辰,所以這里直接產(chǎn)出當(dāng)前架構(gòu)的debug模式庫即可。如果是Static Library類型主之,我們需要的直接就是.a文件,如果是Framework類型李根,我們需要的是.framework中的那個(gè)同名文件槽奕。這兩種文件分析起來無差異,下文統(tǒng)一用.a的情況來說明房轿。

二粤攒、分析

不了解nm命令的同學(xué)可以先看下這個(gè)Tutorial,也建議看下完整的man page囱持。下面以分析AFNetworking庫為例夯接,假如我們的庫名叫libMyAFNetworking,cd到所在目錄執(zhí)行nm libMyAFNetworking纷妆,即可得到每個(gè).o文件中的符號盔几。下圖截取了AFURLRequestSerialization.o中的部分符號。

AFURLRequestSerialization中的符號

通過nm命令的文檔掩幢,我們了解到.o文件中頻繁出現(xiàn)的幾種符號是如下定義:

對于每一個(gè)符號來說逊拍,其類型如果是小寫的,則表明該符號是local的际邻;大寫則表明該符號是global(external)的芯丧。

  • B 該符號的值出現(xiàn)在非初始化數(shù)據(jù)段(bss)中。例如世曾,在一個(gè)文件中定義全局static int test缨恒。則該符號test的類型為b,位于bss section中轮听。其值表示該符號在bss段中的偏移骗露。一般而言,bss段分配于RAM中蕊程。
  • D 該符號位于初始化數(shù)據(jù)段中椒袍。一般來說,分配到data section中藻茂。
    例如:定義全局int baud_table[5] = {9600, 19200, 38400, 57600, 115200}驹暑,會分配到初始化數(shù)據(jù)段中玫恳。
  • S 符號位于非初始化數(shù)據(jù)區(qū),用于small object优俘。
  • T 該符號位于代碼區(qū)text section京办。
  • U 該符號在當(dāng)前文件中是未定義的,即該符號的定義在別的文件中帆焕。
    例如惭婿,當(dāng)前文件調(diào)用另一個(gè)文件中定義的函數(shù),在這個(gè)被調(diào)用的函數(shù)在當(dāng)前就是未定義的叶雹;但是在定義它的文件中類型是T财饥。但是對于全局變量來說,在定義它的文件中折晦,其符號類型為C钥星,在使用它的文件中,其類型為U满着。

一般OC文件

現(xiàn)在我們先不考慮category屬性的getter和setter這種私有方法(下文會單獨(dú)說明)谦炒,所以只關(guān)注類型是大寫字母的符號。我們可以很容易的歸納出

  • 類型是S风喇,且以_OBJC_CLASS_$_開頭的是類名宁改,以__OBJC_LABEL_PROTOCOL_$_開頭的是協(xié)議名,只以下劃線_開頭的是全局常量名
  • 類型是T魂莫,且只以下劃線_開頭的是全局函數(shù)名
  • 類型是D还蹲,且以__OBJC_PROTOCOL_$_開頭的是協(xié)議名,不過我們直接用S的規(guī)則就可以了豁鲤。D類型其實(shí)也存在以_OBJC_CLASS_$_開頭的類名和以下劃線_開頭的全局常量名秽誊,上邊樣例文件中未給出。

有了目標(biāo)后琳骡,我們就可以對于每行符號锅论,用正則[0-9a-f]{16} [STD] (_OBJC_CLASS_\$|__OBJC_LABEL_PROTOCOL_\$)?_([_A-Za-z][^_]\w+)\n來匹配得到目標(biāo)符號了。但這里還有個(gè)比較坑的問題楣号,對于D類型的符號最易,可以看到蘋果官方SDK中的協(xié)議名也會被列出來,考慮到知名第三方庫一般不會和蘋果官方前綴相同炫狱,所以我會過濾掉以官方前綴(如NS藻懒、UI、WK等等)開頭的協(xié)議名视译。

C++文件

有些第三方庫包含C++代碼嬉荆,由于編譯器的name mangling機(jī)制,直接用nm命令只能看到更改后的函數(shù)名酷含。我們可以用Linux的另一個(gè)命令c++filt顯示原本的函數(shù)名鄙早。

// 同樣是PLCrashAsyncDwarfEncoding.o
// nm libCrashReporter-iOS.a
T __ZN7plcrash3PL_5async18dwarf_frame_reader4initEP21plcrash_async_mobjectPK23plcrash_async_byteorderbb

// nm libCrashReporter-iOS.a | c++filt
T plcrash::PL_::async::dwarf_frame_reader::init(plcrash_async_mobject*, plcrash_async_byteorder const*, bool, bool)

不過要注意下汪茧,如果加了c++filt的pipe,得到的符號列表中限番,t類型會變?yōu)?unsigned short"舱污,下邊我們分析category屬性時(shí)要注意這點(diǎn)。

OC category文件

我們先考慮下對于category弥虐,哪些符號會沖突扩灯。首先分類名肯定是要改的,但是只保證分類名不同就萬事大吉了嗎霜瘪?不同分類中的方法和屬性都是往主類的方法列表和屬性列表中插入的珠插,如果我們SDK中使用的第三方庫版本和宿主APP使用的版本不一致,就可能存在分類方法名相同但方法實(shí)現(xiàn)不同颖对,分類屬性名相同但關(guān)聯(lián)對象存取策略不同丧失,導(dǎo)致代碼邏輯錯(cuò)誤。所以除了分類名惜互,我們還有必要修改分類的屬性名和方法名。
下面的例子是SDWebImage的UIImage+ForceDecode.o文件中的符號

UIImage+ForceDecode中的符號

這樣我們可以用另一個(gè)正則[0-9a-f]{16} unsigned short [+-]\[\w+\((\w+)\) ([\w:]+)\]\n匹配分類中所需要的符號了琳拭。這里要注意我們只根據(jù)屬性的getter方法得到屬性名训堆,而不要把setter方法也加入到需要修改的符號行列。

三白嘁、產(chǎn)出宏定義

我們已經(jīng)通過第二步的分析獲取到了所有需要重定義的符號坑鱼,除category屬性外,遍歷一遍絮缅,將加了前綴的符號宏定義為原始符號鲁沥。對于category屬性則需要點(diǎn)額外操作,可以想象下屬性名為foo耕魄,如果要加前綴XN画恰,那么它的getter方法是直接加前綴為-XNfoo,但setter方法不是直接加前綴變?yōu)?code>-XNsetFoo:吸奴,而應(yīng)該是-setXNfoo:了允扇。


完整流程

分析和產(chǎn)出的過程我已經(jīng)寫了個(gè)Python腳本來做,代碼放在這里https://github.com/xuning0/RedefineSymbols
则奥。用法的話考润,比如你要分析的是libMySDWebImage.a,要加的命名空間前綴是ABC读处,那么執(zhí)行python3 redefine_symbols.py --ns ABC libMySDWebImage.a糊治,即可在當(dāng)前目錄產(chǎn)出ABCNamespace.h文件。如上文所說將其拖入你的SDK工程罚舱,設(shè)置為預(yù)編譯頭文件或在已存在的預(yù)編譯頭文件第一行import井辜。以下截圖就是針對SDWebImage產(chǎn)出的ABCNamespace.h部分樣例绎谦。
針對SDWebImage產(chǎn)出的ABCNamespace.h部分樣例

這個(gè)腳本可以覆蓋絕大部分情況,但由于OC屬性命名的特殊性抑胎,在拿到產(chǎn)出文件后最好人工核查category getter和setter這部分的正確性燥滑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市阿逃,隨后出現(xiàn)的幾起案子铭拧,更是在濱河造成了極大的恐慌,老刑警劉巖恃锉,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搀菩,死亡現(xiàn)場離奇詭異,居然都是意外死亡破托,警方通過查閱死者的電腦和手機(jī)肪跋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來土砂,“玉大人州既,你說我怎么就攤上這事÷苡常” “怎么了吴叶?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長序臂。 經(jīng)常有香客問我蚌卤,道長,這世上最難降的妖魔是什么奥秆? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任逊彭,我火速辦了婚禮,結(jié)果婚禮上构订,老公的妹妹穿的比我還像新娘侮叮。我一直安慰自己,他們只是感情好鲫咽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布签赃。 她就那樣靜靜地躺著,像睡著了一般分尸。 火紅的嫁衣襯著肌膚如雪锦聊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天箩绍,我揣著相機(jī)與錄音孔庭,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛圆到,可吹牛的內(nèi)容都是我干的怎抛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼芽淡,長吁一口氣:“原來是場噩夢啊……” “哼马绝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起挣菲,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤富稻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后白胀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體椭赋,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年或杠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哪怔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡向抢,死狀恐怖认境,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挟鸠,我是刑警寧澤元暴,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站兄猩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鉴未。R本人自食惡果不足惜枢冤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铜秆。 院中可真熱鬧淹真,春花似錦、人聲如沸连茧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啸驯。三九已至客扎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間罚斗,已是汗流浹背徙鱼。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人袱吆。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓厌衙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绞绒。 傳聞我的和親對象是個(gè)殘疾皇子婶希,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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