Xcode工程編譯之duplicate symbol問題引發的一些知識


概括:

  1. 文件中重復定義了一個函數、變量(比如全局變量)
  2. 工程中包含同名的文件。

一般的解決方法

  • 1 在使用import 引入頭文件時,由於疏忽,誤引入.m 文件。
  • 2 同名文件放在不同的文件夾下。
  • 3.在 Targets 的 Build Phrases 設置里,查看下 Complie Sources這一項,看看出現問題的類是不是重復的.如果是重復的,刪除掉重新添加也能解決這個問題.
  • 4.文件里面使用C語言定義的全局變量名或是函數名,在導入的時候因為重復產生了沖突。 
    比如:在.m 文件的函數如果是用 C 語言的形式寫的時候,程序在編譯的時候,只看文件名,而不在乎你后面有多少參數,如果是文件名相同的話也是不行的.
    a.m中
    void drawCircle()
    b.m中
    void drawCircle(CGContextRef ctx, int radius, CGFloat centerX, CGFloat centerY)

引入.a產生沖突

參考文章1:duplicate symbol問題解決方法 
參考文章2: iOS 第三方庫沖突的處理 
參考文章3:iOS 解決一個因三方靜態庫沖突產生的duplicate symbol的問題 
1. 如果是兩個靜態庫沖突的話,可以將兩個.a靜態庫解壓,刪除其中一個里面重復的.o文件(編譯時產生的臨時文件),然后用lipo命令合並兩個靜態庫;比如libx.a文件

1 創建臨時文件夾,用於存放armv7平台解壓后的.o文件:
    mkdir armv7

2 取出armv7平台的包:
    lipo libx.a -thin armv7 -output armv7/libx-armv7.a

3 查看庫中所包含的文件列表:
    ar -t armv7/libx-armv7.a

4 進入armv7文件夾,並解壓出object file(即.o后綴文件):
    cd armv7 && ar xv libx-armv7.a

5 找到沖突的包(比如 JSONKit),刪除掉
    rm JSONKit.o

6 重新打包,生成libx-armv7.a
    object file:cd .. && ar rcs libx-armv7.a armv7/*.o,可以再次使用[3]中命令確認是否已成功將文件去除

7 將其他幾個平台(armv7s, i386)包逐一做上述[1-6]操作

8 重新合並為fat file的.a文件libMiPushSDK-new.a:
    lipo -create libx-armv7.a libx-armv7s.a libx-i386.a -output libMiPushSDK-new.a

9 拷貝到項目中覆蓋源文件:
    cp libMiPushSDK-new.a /Users/tony/Desktop/XXXProject/Lib/libMiPushSDK.a

 ar總結:

-t  顯示庫文件中所包含的文件
-x  自庫文件中取出成員文件
-v  程序執行時顯示詳細的信息
-r  將文件插入庫文件中
-c  建立庫文件
-s  若庫文件中包含了對象模式,可利用此參數建立備存文件的符號表。

 

2.工程文件和靜態庫沖突的話,報錯會顯示XXX.o文件。

1 Build Phrase里面搜索這個類名
2 顯示出來的那幾個 .m文件給remove掉就OK
或者(不建議下邊這樣)
1 在Xcode左側,工程文件目錄結構中找到.m文件
2 中並將Target Membership的勾選去掉,效果一樣
注意:
    對工程Add Files to一份拷貝的文件夾之后,默認選中了Target Membership。所以在工程目錄的結構簡單的整理一下后,有時會出現這種錯誤。 

拓展

1 拓展:

lipo的一些解釋:
    lipo 是一個在 Mac OS X 中處理通用程序(Universal Binaries)的工具。現在發售或者提供下載的許多(幾乎所有)程序都打上了“Universal”標志,意味着它們同時具有 PowerPC 和 Intel 芯片能夠處理的代碼。不過既然你可能不在意其中的一個,你就能夠使用 lipo 來給你的程序“瘦身” 

1.1 ios中合成靜態庫:

合成靜態庫:

將模擬器和設備的靜態庫文件合並成一個文件輸出了,以后在發布可以庫的時候不用發一個模擬器版的和一個真機版的了,這樣子的一個庫可以在編譯的時候自動識別需要連接的庫

lipo –create Release-iphoneos/libiphone.a Debig-iphonesimulator/libiphone.a –output libiphone.a

1.2 查看A.a中的.m文件:

查看.m文件

  看一下該文件包含幾種arch
    輸入:file A.a 
    輸出:
    A.a: Mach-O universal binary with 2 architectures
    A.a (for architecture armv7):   current ar archive random library
    A.a (for architecture arm64):   current ar archive random library
    結論:
    可以看到該文件包含兩種arch,分別是armv7和arm64。

    2  由於下面抽離object的時候必須是要單一的庫,所以這里我們抽出armv7並命名為v7.a:
    輸入:lipo A.a -thin armv7 -output v7.a
    輸出:生成一個v7.a文件。
    結論:目錄下多了一個v7.a文件

    3 抽離.a文件的object
    輸入:ar -x v7.a
    輸出:生成一些.o文件
    結論:目錄下多出一些.o文件


    4 獲取文件,比如:View.o文件
    輸入:nm View.o > view.m
    輸出:生成view.m文件
    結論:可以查看view.m文件

2 拓展:

2.1 Target membership是什么?

Target membership是指XCode中,一個文件屬於哪一個工程,在XCode左側的工程面板中選中一個文件,在XCode右側的屬性面板中會顯示其Target Membership。

注意:
以前遇到一個錯誤,就是UIImage創建的時候返回nil,仔細查看發現,圖片的Target Membership選項沒有勾上。這個錯誤比較難以發現,特此記之。

3 拓展:

Other linker flags

    項目編譯時的鏈接方式

3.1 編譯過程:

從C代碼到可執行文件經歷的步驟是:源代碼 > 預處理器 > 編譯器 > 匯編器 > 機器碼 > 鏈接器 > 可執行文件
在最后一步需要把.o文件和C語言運行庫鏈接起來,這時候需要用到ld命令。源文件經過一系列處理以后,會生成對應的.obj文件,然后一個項目必然會有許多.obj文件,並且這些文件之間會有各種各樣的聯系,例如函數調用。鏈接器做的事就是把這些目標文件和所用的一些庫鏈接在一起形成一個完整的可執行文件。
Other linker flags設置的值實際上就是ld命令執行時后面所加的參數

3.2 Xcode里-ObjC, -all_load, -force_load

下面逐個介紹3個常用參數: 

-ObjC:加了這個參數后,鏈接器就會把靜態庫中所有的Objective-C類和分類都加載到最后的可執行文件中

-all_load:會讓鏈接器把所有找到的目標文件都加載到可執行文件中,但是千萬不要隨便使用這個參數!假如你使用了不止一個靜態庫文件,然后又使用了這個參數,那么你很有可能會遇到ld: duplicate symbol錯誤,因為不同的庫文件里面可能會有相同的目標文件,所以建議在遇到-ObjC失效的情況下使用-force_load參數。

-force_load:所做的事情跟-all_load其實是一樣的,但是-force_load需要指定要進行全部加載的庫文件的路徑,這樣的話,你就只是完全加載了一個庫文件,不影響其余庫文件的按需加載。

 

引用.a產生“selector not recognized”錯誤的原因:

1. Unix的標准靜態庫實現和Objective-C的動態特性之間有一些沖突:Objective-C沒有為每個函數(或者方法)定義鏈接符號,它只為每個類創建鏈接符號。這樣當在一個靜態庫中使用類別來擴展已有類的時候,鏈接器不知道如何把類原有的方法和類別中的方法整合起來,就會導致你調用類別中的方法時,出現"selector not recognized",也就是找不到方法定義的錯誤。為了解決這個問題,引入了-ObjC標志,它的作用就是將靜態庫中所有的和對象相關的文件都加載進來。

2. 本來這樣就可以解決問題了,不過在64位的Mac系統或者iOS系統下,鏈接器有一個bug,會導致只包含有類別的靜態庫無法使用-ObjC標志來加載文件。變通方法是使用-all_load 或者-force_load標志,它們的作用都是加載靜態庫中所有文件,不過all_load作用於所有的庫,而-force_load后面必須要指定具體的文件。 

4 拓展:

什么時候該用@class,什么時候該用#import進行聲明

    1.一般如果有繼承關系的用#import,如B是A的子類那么在B中聲明A時用#import

    2. 另外就是如果有循環依賴關系,如:A->B,B->A這樣相互依賴時,如果在兩個文件的頭文件中用#import分別聲明對方,那么就會出現頭文件循環利用的錯誤,這時在頭文件中用@class聲明就不會出錯

    3.還有就是自定義代理的時候,如果在頭文件中想聲明代理的話如@interface SecondViewController:UIViewController時應用#import不然的話會出錯誤,注意XXXXDelegate是自定義的

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM