論項(xiàng)目中靜態(tài)庫符號(hào)沖突的幾種解決方式

在實(shí)際項(xiàng)目過程中,我們經(jīng)常會(huì)碰到引入三方的靜態(tài)庫后出現(xiàn)符號(hào)沖突的現(xiàn)象,也就是出現(xiàn) duplicate symbols 錯(cuò)誤,那么如何解決這類沖突呢?

這里我們區(qū)分幾種不同的沖突情況

最復(fù)雜的情況: 項(xiàng)目中使用的 libSDKA.a 和 libSDKB.a中有符號(hào)沖突,這里假定兩者都包含了很多同名的代碼等

這里,兩者重復(fù)的符號(hào),并一定是在同一個(gè)文件中,或者即使在同一個(gè).o中,但是,鬼知道它們有沒有對(duì)這些重復(fù)符號(hào)的類啥的方法添加了另外的內(nèi)容,也就是說即使它們兩包含了同一份代碼,也可能是并不兼容的兩個(gè)版本.

這里的解決辦法
最先能想到的肯定是讓這兩個(gè)靜態(tài)庫的開發(fā)者中的任意一個(gè)修改下他們的實(shí)現(xiàn),重新打個(gè)包過來,簡單,且解決的徹底!
不過,現(xiàn)實(shí)中往往只能呵呵,這兩個(gè)庫的開發(fā)者,不一定配合.

那么這個(gè)時(shí)候怎么解決?
剩下的唯一辦法,也就是 二進(jìn)制的符號(hào)重命名了!!
目前,對(duì)于二進(jìn)制的符號(hào)重命名,并沒有什么特別好的辦法,本來,感覺這個(gè)并不難,按說,應(yīng)該有一些工具可以用來重命名二進(jìn)制的符號(hào),很可惜,找了一圈,沒有這樣的工具,如果有,煩請(qǐng)各位大神也告知我一聲,謝謝
于是乎,在受到歐陽大哥的靜態(tài)攔截iOS對(duì)象方法調(diào)用的簡易實(shí)現(xiàn)的啟發(fā),外加最近剛好重新閱讀了pod package的實(shí)現(xiàn)mangle的源碼,于是乎想
我們?nèi)绻全@取了所有需要重命名的符號(hào)
是不是可以直接進(jìn)行二進(jìn)制的字符串替換來實(shí)現(xiàn)符號(hào)重命名呢?
當(dāng)然了,因?yàn)椴]有二進(jìn)制符號(hào)的直接修改工具,所以這里的符號(hào)重命名后的名字的長度一定要同原來的名字是同樣的長度!!!! 否則就破壞了mach-o格式了,也就沒法被識(shí)別和加載了,切記!
理論還是要實(shí)踐來檢驗(yàn),于是
我們用一個(gè)例子來檢驗(yàn),為了更加結(jié)合實(shí)踐,這里,我選擇一個(gè)稍微復(fù)雜點(diǎn)的庫來測(cè)試 FLEX
我們生成個(gè)FLEX的靜態(tài)庫 libFLEX.a
創(chuàng)建一個(gè)demo工程,拖入兩個(gè) libFLEX.a(當(dāng)然,一個(gè)重新命名為libRenameLib.a )
編譯運(yùn)行

重復(fù)符號(hào)

不出意外,報(bào)錯(cuò)了,提示有1028個(gè)dumlicate symbols

我們首先是要提取libFLEX.a中的所有的需要重命名的符號(hào),這個(gè)腳本,我們可以直接提取pod package源碼中的提取部分來修改
這里我附上處理的代碼 getAllname.rb

#!/usr/bin/ruby

# 提取某個(gè)庫的滿足條件的符號(hào)
def symbols_from_library(library)
    if !library
        puts "文件不存在!"
        return
    end
    # --defined-only :Display only defined symbols
    # -g, --extern-only      Display only external symbols
    syms = `nm -defined-only -extern-only #{library}`.split("\n")  # 獲取一個(gè)滿足符號(hào)的數(shù)組
    result = classes_from_symbols(syms)
    result += constants_from_symbols(syms)

    result.select do |e|
      case e
      when 'llvm.cmdline', 'llvm.embedded.module', '__clang_at_available_requires_core_foundation_framework'
        false
      else
        true
      end
    end
  end

 #獲取所有的class符號(hào)
  def classes_from_symbols(syms)
    classes = syms.select { |klass| klass[/OBJC_CLASS_\$_/] }  #字符串的正則查找,滿足前綴是OBJC_CLASS_$_
    classes = classes.uniq
    classes.map! { |klass| klass.gsub(/^.*\$_/, '') }
  end

  #獲取所有的常量,全局變量符號(hào)
  def constants_from_symbols(syms)
    consts = syms.select { |const| const[/ S /] }
    consts = consts.select { |const| const !~ /OBJC|\.eh/ }
    consts = consts.uniq
    consts = consts.map! { |const| const.gsub(/^.* _/, '') }

    other_consts = syms.select { |const| const[/ T /] }
    other_consts = other_consts.uniq
    other_consts = other_consts.map! { |const| const.gsub(/^.* _/, '') }

    consts + other_consts
  end


  # 輸出所有的符號(hào)
  def create_symbols_file(library)
    if !library
        puts "文件不存在!"
        return
    end
    syms = symbols_from_library(library)
    syms = syms.uniq  #去重
    #puts "syms = #{syms}"
    create_output_symbols_file(syms)
  end



  def create_output_symbols_file(syms)
    all_new_str = ""
    syms = syms.sort_by {|x| x.length} #按長度來排序

    syms.each do |sym|
      symDest = sym.clone;
      symDest[symDest.length - 1] = '2'  #這個(gè)規(guī)則可以自己定,這里我是把符號(hào)的最后一位換為2
      all_new_str += "#{sym}==#{symDest}\n"

    end
    aFile = File.new("./input.txt", "w+")
    if aFile
      aFile.syswrite(all_new_str)
      aFile.rewind
    else
      puts "Unable to open file!"
    end
  end


lib = ARGV[0]
#puts("ARGV= #{ARGV},lib = #{lib}")
create_symbols_file(lib)

使用的時(shí)候 傳入需要獲取修改的符號(hào)的文件名

ruby ./getAllname.rb ../libFLEX.a

在當(dāng)前目錄會(huì)生成一個(gè)input.txt,所有需要重命名的符號(hào)都在這個(gè)文件中.

接下來,使用如下的腳本 rename.rb 來對(duì)二進(jìn)制的符號(hào)進(jìn)行重命名

#!/usr/bin/ruby

require 'fileutils'
require 'pathname'
 
$symbol_file = ''
def disposeBin(oringin,dest)
    binnaryFile = $symbol_file
    command = "LC_CTYPE=C sed -i '' 's/#{oringin}/#{dest}/g' #{binnaryFile}"
    puts("command = #{command}")
    result = `LC_CTYPE=C sed -i '' 's/#{oringin}/#{dest}/g' #{binnaryFile}`
    output = result
    #puts("output = #{output}")
    return output
end
def disposeLine(line)
    arr = line.split("==")
    origin = arr[0].chomp
    dest = arr[1].chomp
    puts ("origin = #{origin},dest = #{dest}")
    return disposeBin(origin,dest)

end


def main
    $symbol_file = ARGV[0]
    fileName = ARGV[1]
    if !$symbol_file || $symbol_file == ''
        puts "文件不存在!"
        return
    end
    if !fileName
        puts "符號(hào)文件不存在!"
        return
    end

    #獲得當(dāng)前執(zhí)行文件的完整路徑
    path =File.dirname(__FILE__)
    #path = Pathname.new(__FILE__).realpath
    puts(path)
    puts("cp -f #{$symbol_file} #{path}/libDispose.a")
    `cp -f #{$symbol_file} #{path}/libDispose.a`
    $symbol_file = "#{path}/libDispose.a"

    File.open(fileName, "r") do |file|
        file.each_line do |line|
            res = disposeLine(line)
            if !res
                return
            end

        end
    end

end


main


使用的時(shí)候

ruby ./rename.rb ../libFLEX.a ./input.txt

傳入的是 二進(jìn)制文件名和上面生成的要重名的符號(hào)文件
將生成的新的libDispose.a拷貝到demo工程中,可以看到,可以編譯運(yùn)行了,并且兩個(gè)都可以使用!!


image.png

當(dāng)然,其實(shí)還有一種方式:靜態(tài)庫動(dòng)態(tài)化
將某個(gè)靜態(tài)庫用動(dòng)態(tài)庫來包含,也就是靜態(tài)庫動(dòng)態(tài)化,這樣也可以解決問題, 我們繼續(xù)可以嘗試下:
建立一個(gè)Dynamic framework的工程(記得添加工程的 other link flags中的-ObjC,不然靜態(tài)庫中的很多內(nèi)容不會(huì)添加到動(dòng)態(tài)庫中)
將 libFLEX.a 添加進(jìn)去,生成動(dòng)態(tài)庫


image.png

將這個(gè)生成的DymamicContainer.framework添加到測(cè)試工程中,運(yùn)行,也是可以的,只是會(huì)出現(xiàn)運(yùn)行時(shí)警告


image.png

雖然編譯是通過了,不過這種方式帶來的問題,還是比較麻煩的
如果兩個(gè)重復(fù)的符號(hào)的實(shí)現(xiàn),完全一致,那非常好,皆大歡喜
如果兩個(gè)重復(fù)的符號(hào)的實(shí)現(xiàn)是不一致的,那由于不確定加載的會(huì)是哪個(gè),導(dǎo)致最后的行為是不可預(yù)知的
這種實(shí)現(xiàn),其實(shí)并不好,通常只是在兩個(gè)庫的重復(fù)符號(hào)的實(shí)現(xiàn)完全一致的情況下才比較好
相對(duì)來說,第一種辦法,是一種非常好的方式:

并不需要兩個(gè)靜態(tài)庫中的重復(fù)符號(hào)的實(shí)現(xiàn)是一致的!!

三方庫包含了某個(gè)開源代碼

例如,某靜態(tài)庫libSDK.a中融入了開源代碼AFnetworking,對(duì)于這種比較簡單的情況來說,我們有兩種處理方式

第一種處理方式:核心就是將.a分離成.o文件,然后把重復(fù)的.o文件去掉再重新打包成.o文件.
查看libSDK.a的架構(gòu)信息
lipo -info libSDK.a 或者 file libSDK.a

file libWeChatSDK.a
libWeChatSDK.a: Mach-O universal binary with 4 architectures: [i386:current ar archive] [arm_v7] [x86_64] [arm64]
libWeChatSDK.a (for architecture i386): current ar archive
libWeChatSDK.a (for architecture armv7):    current ar archive
libWeChatSDK.a (for architecture x86_64):   current ar archive
libWeChatSDK.a (for architecture arm64):    current ar archive

或者

lipo -info libWeChatSDK.a
Architectures in the fat file: libWeChatSDK.a are: i386 armv7 x86_64 arm64

一般的靜態(tài)庫都會(huì)包含真機(jī)arm64,armv7和模擬器x86_64三種架構(gòu).
對(duì)于每種架構(gòu)要分別處理,然后再合并(為什么要分離? 因?yàn)槊糠N架構(gòu)里面都有同樣的.o文件啊,如果你不分離,不是亂套了....)

對(duì)于每種架構(gòu),例如arm64:

  1. 創(chuàng)建一個(gè)臨時(shí)文件夾 mkdir arm64,分離出arm64架構(gòu)
lipo -thin arm64 libWeChatSDK.a -output arm64/libWeChatSDK_arm64.a

2)解壓出object file(即.o后綴文件)

cd arm64 && ar -xv libWeChatSDK_arm64.a

輸出的結(jié)果是
.
├── .DS_Store
├── AppCommunicateData.o
├── WXApi+ExtraUrl.o
├── WXApi+HandleOpenUrl.o
├── WXApi.o
├── WXApiObject.o
├── WXLogUtil.o
├── WapAuthHandler.o
├── WeChatApiUtil.o
├── WeChatIdentityHandler.o
├── WechatAuthSDK.o
├── .SYMDEF
└── base64.o
這里的
.SYMDEF 文件是符號(hào)定義,其內(nèi)容是要被要被加載的符號(hào).

  1. 找到?jīng)_突的.o,例如AppCommunicate.o,刪除它
rm AppCommunicate.o

其實(shí)如果我們知道要移除的是哪個(gè).o,那么可以直接使用命令

ar -d libWeChatSDK_arm64.a AppCommunicate.o

從.a中直接刪除.o,省卻了先分離,刪除,再合并!

  1. 重新打包object file
cd .. && ar rcs libWeChatSDK_arm64 arm64/*.o

-s表示無論ar 命令是否修改了庫內(nèi)容都強(qiáng)制重新生成庫符號(hào)表
當(dāng)然,還可以用

libtool -static -o ../libWeChatSDK_arm64.a *.o

這兩個(gè)生成的.a效果是一樣的

在當(dāng)前目錄生成了去除了AppCommunicate.o后的libWeChatSDK_arm64
這個(gè)時(shí)候,可以確認(rèn)下這個(gè)新的libWeChatSDK_arm64還有沒有那個(gè)AppCommunicate.o了,用命令

ar -t libWeChatSDK_arm64

可以看到,列表里已經(jīng)沒有AppCommunicate.o了.說明ok了

將其他幾個(gè)架構(gòu)(armv7s, x86_64,i386)等重復(fù)上面的1)-4)步驟

最后將去掉AppCommunicate.o的各種架構(gòu),合并成新的.a文件

lipo -create libWeChatSDK_arm64 libWeChatSDK_armv7.a  -output  libWeChatSDK-new.a

覆蓋掉項(xiàng)目中原來的文件,即可!!

當(dāng)然,這種方式也是有缺點(diǎn)的 ,你不知道這個(gè)靜態(tài)庫中重復(fù)的.o文件是不是同你項(xiàng)目中的版本兼容,如果不兼容呢,這樣去掉了,會(huì)導(dǎo)致行為的錯(cuò)誤.或者說,假如這個(gè)靜態(tài)庫中包含的重復(fù)符號(hào)所在的文件進(jìn)行了他們自定義的修改呢?這樣去掉,也是有問題的.

第二種辦法:
由于,libSDK.a中包含了AFnetworking的符號(hào),那簡單啊,直接把項(xiàng)目中用到了AFnetworking整個(gè)的重命名,也就是重命名包括類名左医、分類名宇挫、全局常量名袱蚓、協(xié)議名等會(huì)導(dǎo)致沖突的符號(hào).這也是目前很多項(xiàng)目中的做法,這種做法,修改的地方太多了,且會(huì)造成另外一種問題,就是每次如果要升級(jí)AFnetworking,那么要整個(gè)的再重新修改一次,太麻煩了,屬于典型的吃力不討好!
我們不去除libSDK.a中用到的AFnetworking,我們可以建立個(gè)預(yù)編譯的頭文件,將AFnetworking的所有符號(hào)都重新宏定義成另外的名字,這個(gè)在# iOS靜態(tài)庫開發(fā)中引入的第三方庫可能與宿主APP中沖突的解決方案
這個(gè)文章中作者有提供一個(gè)簡單腳本來生成.

當(dāng)然,除了 符號(hào)重定義+pch

還可以使用 GCC_PREPROCESSOR_DEFINITIONS 的形式

xcodebuild GCC_PREPROCESSOR_DEFINITIONS='$(inherited) PodsDummy_FLEX=PodFLEX_PodsDummy_FLEX FHSRangeSlider=PodFLEX_FHSRangeSlider FHSSnapshotNodes=PodFLEX_FHSSnapshotNodes' CONFIGURATION_BUILD_DIR=build clean build -configuration Debug -sdk iphonesimulator -arch x86_64 -target RenameLib -project RenameLib.xcodeproj

兩種方式添加都可以

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子恤煞,更是在濱河造成了極大的恐慌姓赤,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件既们,死亡現(xiàn)場(chǎng)離奇詭異濒析,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)啥纸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門号杏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人斯棒,你說我怎么就攤上這事盾致。” “怎么了荣暮?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵庭惜,是天一觀的道長。 經(jīng)常有香客問我穗酥,道長护赊,這世上最難降的妖魔是什么惠遏? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮骏啰,結(jié)果婚禮上节吮,老公的妹妹穿的比我還像新娘。我一直安慰自己判耕,他們只是感情好透绩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著祈秕,像睡著了一般渺贤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上请毛,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天志鞍,我揣著相機(jī)與錄音,去河邊找鬼方仿。 笑死固棚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的仙蚜。 我是一名探鬼主播此洲,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼委粉!你這毒婦竟也來了呜师?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤贾节,失蹤者是張志新(化名)和其女友劉穎汁汗,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體栗涂,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡知牌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斤程。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片角寸。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖忿墅,靈堂內(nèi)的尸體忽然破棺而出扁藕,到底是詐尸還是另有隱情,我是刑警寧澤疚脐,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布亿柑,位于F島的核電站,受9級(jí)特大地震影響亮曹,放射性物質(zhì)發(fā)生泄漏橄杨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一照卦、第九天 我趴在偏房一處隱蔽的房頂上張望式矫。 院中可真熱鬧,春花似錦役耕、人聲如沸采转。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽故慈。三九已至,卻和暖如春框全,著一層夾襖步出監(jiān)牢的瞬間察绷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工津辩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拆撼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓喘沿,卻偏偏與公主長得像闸度,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蚜印,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355