iOS 平臺 Cocos2d-x 項目 接入第三方SDK 的坑(就是靜態庫接入的問題)

解決方法是:

-force_load path/to/your/libWeiboSDK.a 而不是 他提供的-ObjC、-all_load,下面是一些詳細說明

這里特別給出示范路徑,比如你在項目中導入了XXX.a放在一個叫aaa的group文件下,那么路徑就是aaa/xxx.a,或者你可以使用全路徑,點擊對應的xxx.a靜態庫,會在Xcode的右側出現該文件的路徑,把它復制過來就可以了


遇到的問題

根據新浪微博 SDK 附帶的文檔接入項目后,在模擬器運行項目,在調用注冊方法時發生崩潰。注冊方法代碼:

1

[WeiboSDK registerApp: @"xxxxxxxx"];

崩潰信息打印如下:

1

[__NSDictionaryM weibosdk_WBSDKJSONString] : unrecognized selector sent to instance 0x170255780

解決問題遇到的阻礙

新浪微博 SDK 附帶的文檔中有這么一個說明:

在工程中引入靜態庫之后,需要在編譯時添加 ? –ObjC ? 編譯選項,避免靜態庫中類 加載 ? 不全造成程序崩潰。方法:程序 ? Target->Buid ? Settings->Linking ? 下 ? Other ? Linker ?Flags ? 項添加-ObjC

在網上看到遇到同樣崩潰錯誤的人有提到在編譯時添加?-all_load?編譯選項時也可以解決問題。方法也是在 ? Target->Buid ? Settings->Linking ? 下 ? Other ? Linker ?Flags ? 項添加-all_load。

無獨有偶,我在打開新浪微博 SDK 附帶的 Demo 項目時發現這個項目的編譯選項也是-all_load而不是它自己文檔所提示的-ObjC。而且在同樣的開發環境下,我的 cocos2d-x 項目會崩潰,但是新浪微博 SDK 附帶的 Demo 可以正常工作,想必上述兩個解決方案應該是正解

但是在給自己的 cocos2d-x 項目添加了編譯選項后,再次編譯運行就發生了錯誤,錯誤信息如下:

1

2

3

4

5

6

7

8

Undefined symbols for architecture i386:? "_GCControllerDidConnectNotification", referenced from:? -[GCControllerConnectionEventHandler observerConnection:disconnection:] in libcocos2dx iOS.a(CCController-iOS.o)? "_GCControllerDidDisconnectNotification", referenced from:? -[GCControllerConnectionEventHandler observerConnection:disconnection:] in libcocos2dx iOS.a(CCController-iOS.o)? "_OBJC_CLASS_$_GCController", referenced from:? objc-class-ref in libcocos2dx iOS.a(CCController-iOS.o)? (maybe you meant: _OBJC_CLASS_$_GCControllerConnectionEventHandler)

無論是設置成-ObjC還是-all_load編譯都會失敗,都會報上述找不到符號的鏈接錯誤。

正確的解決辦法

這里先給出正確的解決辦法再談談為什么要這么做。正確的做法還是設置 Other Linker Flags 這個編譯選項,只不過即不用用-ObjC也不能用-all_load,而是要用-force_load path/to/your/libWeiboSDK.a,后面跟的是新浪微博 SDK 靜態鏈接庫的確切位置。

這一切是為什么?

從編譯鏈接說起

這里不打算過多的介紹編譯鏈接相關的只是,但是強烈推薦一本書《程序員的自我修養》,光看正標題你可能會擔心這是本沒什么“正經”內容的書,至少我當初第一次看到這書名的時候就是這么認為的,但是我錯了,這本書的副標題是鏈接、裝載與庫。相信我,看過這本書 N 遍之后你自會對程序從源代碼編譯鏈接到生成二進制程序的原理和過程有一個非常透徹的理解,并且更重要的是看過這本書 N 遍之后你會上升幾個層次。

言歸正傳,一個工程的源代碼最終變成二進制的可執行程序、動態鏈接庫或靜態鏈接庫要經歷這么幾個過程:

1

源代碼 ==[編譯器]==》 匯編碼 ==[匯編器]==》 對象文件 ==[鏈接器]==》 可執行程序、動態鏈接庫或靜態鏈接庫

再說說符號是什么?

通俗的講,我們在源碼中寫的全局變量名、函數名或類名在生成的*.o對象文件中都叫做符號,存在一個叫做符號表的地方。

舉個例子:我們在a.c文件中寫了一個函數叫foo(),然后在main.c文件中調用了foo()函數,在將源碼編譯生成的對象文件中a.o對象文件中的符號表里保存著foo()函數符號,并通過該符號可以定位到a.o文件中關于foo()方法的具體實現代碼。

鏈接器在鏈接生成最終的二進制程序的時候會發現main.o對象文件中引用了符號foo(),而foo()符號并沒有在main.o文件中定義,所以不會存在與main.o對象文件的符號表中,于是鏈接器就開始檢查其他對象文件,當檢查到a.o文件中定義了符號foo(),于是就將a.o對象文件鏈接進來。這樣就確保了在main.c中能夠正常調用a.c中實現的foo()方法了。

libWeiboSDK.a 靜態鏈接庫里有什么?

Unix 的靜態鏈接庫沒什么神秘的,它就是個壓縮包,和平時比較常見的 zip 或 rar 之類的壓縮包一樣,只不過人家是用一個叫 ar 的壓縮工具壓縮的而已。所以我們給它解壓縮一下,看看它里面都有什么。既然是用 ar 壓縮的,解壓自然也要用 ar 這個工具。在命令行執行:

1

ar -x lieWeiboSDK.a

結果報錯了:

1

2

ar: libWeiboSDK.a is a fat file (use libtool(1) or lipo(1) and ar(1) on it) ar: libWeiboSDK.a: Inappropriate file type or format

這里先解釋一下它為什么這么肥(fat)。在做 iOS 開發時我們都知道可以用模擬器和真機來測試我們的項目,但是這兩個平臺的架構是不一樣的,模擬器是 i386 x86_64 架構的,而我們的設備是 armv7 arm64 架構的。當在制作靜態鏈接庫的時候也要針對不同的架構制作出針對真機和模擬器的兩個靜態鏈接庫,而當我們想在自己的項目中使用靜態鏈接庫的時候,如果在模擬器上運行我們要用針對模擬器的靜態庫版本,用真機設備測試的時候還要切換到針對真機的靜態鏈接庫,這樣一來非常的麻煩。

前面說過了靜態鏈接庫就是個壓縮包,那么我們是否能將這兩個靜態鏈接庫壓縮成一個靜態鏈接庫這樣就可以同時支持模擬器和真機設備兩種架構了呢?答案是肯定的。比如我們手頭有一個靜態鏈接庫的兩個架構版本:libXXX.i386_x86_64.a和libXXX.armv7_arm64.a,那么我們可以通過如下命令來生成一個統一的靜態鏈接庫:

1

lipo -create libXXX.i386_x86_64.a libXXX.armv7_arm64.a -output libXXX.a

這樣我們就得到了一個統一版本的靜態庫libXXX.a,它的好處是同時支持模擬器架構和真機設備架構,缺點是它的體積變大了,也就是說它很肥(fat)。

而libWeiboSDK.a就是這么一個合體后的靜態庫,我們照樣可以通過命令來驗證這一點:

1

lipo -info libWeiboSDK.a

這個命令會輸出:

1

Architectures in the fat file: libWeiboSDK.a are: armv7 arm64 i386 x86_64

既然是個胖子,那我們就要先給它瘦身才能解壓。我們隨便從里面抽出一個架構的靜態鏈接庫來,瘦身命令是:

1

lipo -thin i386 libWeiboSDK.a -output libWeiboSDK.i386.a

這樣我們就把針對 i386 平臺的新浪微博 SDK 靜態鏈接庫給抽離出來了,我們管它叫libWeiboSDK.i386.a,現在我們再用ar命令解壓它看看里面有什么

1

ar -x libWeibo.i386.a

解壓完成后你會看到好多好多以.o結尾的對象文件,回憶回憶剛剛我們講到的編譯鏈接過程,這些對象文件就是給鏈接器最終生成靜態鏈接庫時用到的文件,由于太多了,我只列出我們要講到的幾個:

1

2

3

4

5

6

7

8

9

-rw-r--r-- 1 leenjewel staff 13K Jan 8 15:47 NSData+WBSDKBase64.o -rw-r--r-- 1 leenjewel staff 42K Jan 8 15:47 UIImage+WBSDKResize.o -rw-r--r-- 1 leenjewel staff 12K Jan 8 15:47 UIImage+WBSDKStretch.o -rw-r--r-- 1 leenjewel staff 74K Jan 8 15:47 UIView+WBSDKSizes.o -rw-r--r-- 1 leenjewel staff 58K Jan 8 15:47 WBAidManager.o -rw-r--r-- 1 leenjewel staff 15K Jan 8 15:47 WBAuthorizeRequest.o -rw-r--r-- 1 leenjewel staff 16K Jan 8 15:47 WBAuthorizeResponse.o -rw-r--r-- 1 leenjewel staff 19K Jan 8 15:47 WBBaseMediaObject.o -rw-r--r-- 1 leenjewel staff 265K Jan 8 15:47 WBSDKJSONKit.o

為什么會在運行中崩潰?

當我們把新浪微博 SDK 的靜態鏈接庫引入我們自己的項目,并 Build 我們自己的項目到模擬器或真機設備上運行的過程其實也是一個編譯鏈接的過程,最終從項目 Build 生成可以在模擬器或真機設備運行的 App,而這個過程中對新浪微博 SDK 的靜態鏈接庫的處理方式和我們剛剛拆開libWeiboSDK.a的過程差不多:

將 libWeibSDK.a 根據當前所構建的平臺架構(模擬器還是真機設備)進行瘦身將瘦身的靜態庫解壓拆包將用到的對象文件鏈接進入項目

而我們遇到的崩潰問題恰恰是出在了將用到的對象文件鏈接進入項目這一步。

蘋果的開發者網站針對這個問題有一篇說明文章,我們來引用一下里面的內容:

The dynamic nature of Objective-C complicates things slightly. Because the code that implements a method is not determined until the method is actually called,

這句話解釋起來就是說 Objective-C 是有運行時(runtime)的,一個方法要執行什么代碼是在運行時決定的,而不是在鏈接時決定的。想要再深入了解 Objective-C 運行時知識的,可以看看這里

Objective-C does not define linker symbols for methods. Linker symbols are only defined for classes.

因為在 Objective-C 中,一個方法的執行是要到運行時才決定的,所以在鏈接時,鏈接器只鏈接類的符號,并不會鏈接方法的符號。

For example, if main.m includes the code [[FooClass alloc] initWithBar:nil]; then main.o will contain an undefined symbol for FooClass, but no linker symbols for the -initWithBar: method will be in main.o

最后還舉了一個例子:當你在main.m文件中初始化一個類FooClass的對象,然后調用了這個類FooClass的一個對象方法initWithBar,在鏈接器分析由main.m編譯生成的main.o對象文件時,發現這個對象文件沒有定義符號FooClass于是就會去其他.o對象文件中去尋找FooClass符號的定義,而至于方法符號initWithBar的定義在哪里鏈接器是不關心的,因為initWithBar的執行是由運行時負責的,鏈接器不管。

好了,現在問題來了,我們再重復一下這句話:

1

Objective-C 中方法的執行實在運行時決定的,所以鏈接器只鏈接類的符號,不鏈接方法的符號

我們再回過頭看看崩潰的報錯信息:

1

[__NSDictionaryM weibosdk_WBSDKJSONString] : unrecognized selector sent to instance 0x170255780

這說明崩潰的原因是在運行時調用__NSDictionaryM類對象的weibosdk_WBSDKJSONString方法時沒有找到該方法的定義。這里不難看出__NSDictionaryM是Foundation Framework中的類,而方法weibosdk_WBSDKJSONString是新浪微博 SDK 自己定義的方法,新浪在這里使用了分類技術擴展了__NSDictionaryM類的行為。我們來驗證這一點:

我們已經解壓出libWeiboSDK.a中的全部.o對象文件,我們用nm命令導出全部對象文件中的符號:

1

nm *.o >> libWeiboSDK.symbols.txt

然后我們用個文本編輯器打開libWeiboSDK.symbols.txt查找weibosdk_WBSDKJSONString,我們可以查到如下結果:

1

2

3

4

WBSDKJSONKit.o: 00007ba0 t -[NSArray(WBSDKJSONKitSerializing) weibosdk_WBSDKJSONString] 00007de8 t -[NSDictionary(WBSDKJSONKitSerializing) weibosdk_WBSDKJSONString] 000079cd t -[NSString(WBSDKJSONKitSerializing) weibosdk_WBSDKJSONString]

這就可以說明新浪微博 SDK 確實使用了分類技術擴展了NSArray、NSDictionary和NSString三個 Foundation Framework 下面的類的行為。好,現在可以真相大白了:

在鏈接時,鏈接器發現WBSDKJSONKit.o對象文件中缺少類符號NSArray、NSDictionary和NSString。鏈接器從Foundation Framework中找到了類的符號定義,從而將Foundation Framework中相關的對象文件鏈接進來由于鏈接器不鏈接方法符號,所以weibosdk_WBSDKJSONString這樣的方法符號完全被忽略了。由于類符號的定義在Foundation Farmework中定義,所以WBSDKJSONKit.o對象文件中沒有符號被引用,鏈接器就沒有把這個對象文件鏈接進來。運行時運行到weibosdk_WBSDKJSONString方法時,由于Foundation Framework中是不存在這個方法的定義的,而存在這個方法定義的WBSDKJSONKit.o對象文件又沒有被鏈接器鏈接進來,所以崩潰了。

為什么增加編譯選項可以解決問題?

我們繼續引用蘋果的開發者網站針對這個問題的說明文章中的內容:

Passing the -ObjC option to the linker causes it to load all members of static libraries that implement any Objective-C class or category. This will pickup any category method implementations. But it can make the resulting executable larger, and may pickup unnecessary objects. For this reason it is not on by default.

加了-ObjC選項后,不管是否被引用到,鏈接器會把 Objective-C 的類和分類的所有對象文件全部鏈接,全部鏈接后方法符號全部被鏈接進來,崩潰的問題自然被解決了。

而-all_load選項更徹底,這個選項會讓鏈接器把全部的對象文件都鏈接進來,當然,代價就是構建的 APP 體積會變大。

為什么 cocos2d-x 加了編譯選項會無法編譯通過?

其實準確的說法是編譯可以成功進行,鏈接器執行報錯。我們再回顧一下加了-ObjC或-all_load鏈接選項后鏈接器的報錯信息:

1

2

3

4

5

6

7

8

Undefined symbols for architecture i386:? "_GCControllerDidConnectNotification", referenced from:? -[GCControllerConnectionEventHandler observerConnection:disconnection:] in libcocos2dx iOS.a(CCController-iOS.o)? "_GCControllerDidDisconnectNotification", referenced from:? -[GCControllerConnectionEventHandler observerConnection:disconnection:] in libcocos2dx iOS.a(CCController-iOS.o)? "_OBJC_CLASS_$_GCController", referenced from:? objc-class-ref in libcocos2dx iOS.a(CCController-iOS.o)? (maybe you meant: _OBJC_CLASS_$_GCControllerConnectionEventHandler)

根據報錯信息我們能夠了解到報錯是一個名叫CCController-iOS.o對象文件導致的,而這個文件對應的源代碼是CCController-iOS.mm,通過閱讀源碼我們發現,這個文件中定義了一個 Objective-C 的類GCControllerConnectionEventHandler,這個類中的方法引用了GCControllerDidConnectNotification和GCControllerDidDisconnectNotification兩個類,而這兩個類實在GameController Framework中定義的。

而 cocos2d-x 生成的項目默認并沒有為我們引入GameController Framework,所以在鏈接時由于鏈接器找不到對應類的符號定義,所以才會報錯。如果你到 Xcode->Target->Buid Phases-> 下 ? Link Binary With Libraries ? 項添加GameController Framework就可以解決問題了,但是這種解決方式很不干凈

正確的姿勢

-force_load path/to/your/libWeiboSDK.a鏈接選項其實是干了和-ObjC、-all_load一樣的事情,只不過它更有針對性,它只讓鏈接器把你指定的靜態鏈接庫中的全部對象文件鏈接進來,這樣更清爽一些。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容

  • 靜態庫與動態庫的區別 首先來看什么是庫,庫(Library)說白了就是一段編譯好的二進制代碼,加上頭文件就可以供別...
    吃瓜群眾呀閱讀 12,039評論 3 42
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,180評論 4 61
  • Swift版本點擊這里歡迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh閱讀 25,508評論 7 249
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,836評論 18 139
  • 時間過的飛快,不知不覺你已經上初中了,我記憶中那個抹著眼淚不愿意去幼兒園的小女孩,已經變成了一個活潑、開朗的...
    月月2005閱讀 238評論 0 1