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

解決方法是:

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

這里特別給出示范路徑,比如你在項(xiàng)目中導(dǎo)入了XXX.a放在一個(gè)叫aaa的group文件下,那么路徑就是aaa/xxx.a,或者你可以使用全路徑,點(diǎn)擊對(duì)應(yīng)的xxx.a靜態(tài)庫,會(huì)在Xcode的右側(cè)出現(xiàn)該文件的路徑,把它復(fù)制過來就可以了


遇到的問題

根據(jù)新浪微博 SDK 附帶的文檔接入項(xiàng)目后,在模擬器運(yùn)行項(xiàng)目,在調(diào)用注冊(cè)方法時(shí)發(fā)生崩潰。注冊(cè)方法代碼:

1

[WeiboSDK registerApp: @"xxxxxxxx"];

崩潰信息打印如下:

1

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

解決問題遇到的阻礙

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

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

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

無獨(dú)有偶,我在打開新浪微博 SDK 附帶的 Demo 項(xiàng)目時(shí)發(fā)現(xiàn)這個(gè)項(xiàng)目的編譯選項(xiàng)也是-all_load而不是它自己文檔所提示的-ObjC。而且在同樣的開發(fā)環(huán)境下,我的 cocos2d-x 項(xiàng)目會(huì)崩潰,但是新浪微博 SDK 附帶的 Demo 可以正常工作,想必上述兩個(gè)解決方案應(yīng)該是正解

但是在給自己的 cocos2d-x 項(xiàng)目添加了編譯選項(xiàng)后,再次編譯運(yùn)行就發(fā)生了錯(cuò)誤,錯(cuò)誤信息如下:

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)

無論是設(shè)置成-ObjC還是-all_load編譯都會(huì)失敗,都會(huì)報(bào)上述找不到符號(hào)的鏈接錯(cuò)誤。

正確的解決辦法

這里先給出正確的解決辦法再談?wù)劄槭裁匆@么做。正確的做法還是設(shè)置 Other Linker Flags 這個(gè)編譯選項(xiàng),只不過即不用用-ObjC也不能用-all_load,而是要用-force_load path/to/your/libWeiboSDK.a,后面跟的是新浪微博 SDK 靜態(tài)鏈接庫的確切位置。

這一切是為什么?

從編譯鏈接說起

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

言歸正傳,一個(gè)工程的源代碼最終變成二進(jìn)制的可執(zhí)行程序、動(dòng)態(tài)鏈接庫或靜態(tài)鏈接庫要經(jīng)歷這么幾個(gè)過程:

1

源代碼 ==[編譯器]==》 匯編碼 ==[匯編器]==》 對(duì)象文件 ==[鏈接器]==》 可執(zhí)行程序、動(dòng)態(tài)鏈接庫或靜態(tài)鏈接庫

再說說符號(hào)是什么?

通俗的講,我們?cè)谠创a中寫的全局變量名、函數(shù)名或類名在生成的*.o對(duì)象文件中都叫做符號(hào),存在一個(gè)叫做符號(hào)表的地方。

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

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

libWeiboSDK.a 靜態(tài)鏈接庫里有什么?

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

1

ar -x lieWeiboSDK.a

結(jié)果報(bào)錯(cuò)了:

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 開發(fā)時(shí)我們都知道可以用模擬器和真機(jī)來測(cè)試我們的項(xiàng)目,但是這兩個(gè)平臺(tái)的架構(gòu)是不一樣的,模擬器是 i386 x86_64 架構(gòu)的,而我們的設(shè)備是 armv7 arm64 架構(gòu)的。當(dāng)在制作靜態(tài)鏈接庫的時(shí)候也要針對(duì)不同的架構(gòu)制作出針對(duì)真機(jī)和模擬器的兩個(gè)靜態(tài)鏈接庫,而當(dāng)我們想在自己的項(xiàng)目中使用靜態(tài)鏈接庫的時(shí)候,如果在模擬器上運(yùn)行我們要用針對(duì)模擬器的靜態(tài)庫版本,用真機(jī)設(shè)備測(cè)試的時(shí)候還要切換到針對(duì)真機(jī)的靜態(tài)鏈接庫,這樣一來非常的麻煩。

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

1

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

這樣我們就得到了一個(gè)統(tǒng)一版本的靜態(tài)庫libXXX.a,它的好處是同時(shí)支持模擬器架構(gòu)和真機(jī)設(shè)備架構(gòu),缺點(diǎn)是它的體積變大了,也就是說它很肥(fat)。

而libWeiboSDK.a就是這么一個(gè)合體后的靜態(tài)庫,我們照樣可以通過命令來驗(yàn)證這一點(diǎn):

1

lipo -info libWeiboSDK.a

這個(gè)命令會(huì)輸出:

1

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

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

1

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

這樣我們就把針對(duì) i386 平臺(tái)的新浪微博 SDK 靜態(tài)鏈接庫給抽離出來了,我們管它叫l(wèi)ibWeiboSDK.i386.a,現(xiàn)在我們?cè)儆胊r命令解壓它看看里面有什么

1

ar -x libWeibo.i386.a

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

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

為什么會(huì)在運(yùn)行中崩潰?

當(dāng)我們把新浪微博 SDK 的靜態(tài)鏈接庫引入我們自己的項(xiàng)目,并 Build 我們自己的項(xiàng)目到模擬器或真機(jī)設(shè)備上運(yùn)行的過程其實(shí)也是一個(gè)編譯鏈接的過程,最終從項(xiàng)目 Build 生成可以在模擬器或真機(jī)設(shè)備運(yùn)行的 App,而這個(gè)過程中對(duì)新浪微博 SDK 的靜態(tài)鏈接庫的處理方式和我們剛剛拆開libWeiboSDK.a的過程差不多:

將 libWeibSDK.a 根據(jù)當(dāng)前所構(gòu)建的平臺(tái)架構(gòu)(模擬器還是真機(jī)設(shè)備)進(jìn)行瘦身將瘦身的靜態(tài)庫解壓拆包將用到的對(duì)象文件鏈接進(jìn)入項(xiàng)目

而我們遇到的崩潰問題恰恰是出在了將用到的對(duì)象文件鏈接進(jìn)入項(xiàng)目這一步。

蘋果的開發(fā)者網(wǎng)站針對(duì)這個(gè)問題有一篇說明文章,我們來引用一下里面的內(nèi)容:

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 是有運(yùn)行時(shí)(runtime)的,一個(gè)方法要執(zhí)行什么代碼是在運(yùn)行時(shí)決定的,而不是在鏈接時(shí)決定的。想要再深入了解 Objective-C 運(yùn)行時(shí)知識(shí)的,可以看看這里

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

因?yàn)樵?Objective-C 中,一個(gè)方法的執(zhí)行是要到運(yùn)行時(shí)才決定的,所以在鏈接時(shí),鏈接器只鏈接類的符號(hào),并不會(huì)鏈接方法的符號(hào)。

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

最后還舉了一個(gè)例子:當(dāng)你在main.m文件中初始化一個(gè)類FooClass的對(duì)象,然后調(diào)用了這個(gè)類FooClass的一個(gè)對(duì)象方法initWithBar,在鏈接器分析由main.m編譯生成的main.o對(duì)象文件時(shí),發(fā)現(xiàn)這個(gè)對(duì)象文件沒有定義符號(hào)FooClass于是就會(huì)去其他.o對(duì)象文件中去尋找FooClass符號(hào)的定義,而至于方法符號(hào)initWithBar的定義在哪里鏈接器是不關(guān)心的,因?yàn)閕nitWithBar的執(zhí)行是由運(yùn)行時(shí)負(fù)責(zé)的,鏈接器不管。

好了,現(xiàn)在問題來了,我們?cè)僦貜?fù)一下這句話:

1

Objective-C 中方法的執(zhí)行實(shí)在運(yùn)行時(shí)決定的,所以鏈接器只鏈接類的符號(hào),不鏈接方法的符號(hào)

我們?cè)倩剡^頭看看崩潰的報(bào)錯(cuò)信息:

1

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

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

我們已經(jīng)解壓出libWeiboSDK.a中的全部.o對(duì)象文件,我們用nm命令導(dǎo)出全部對(duì)象文件中的符號(hào):

1

nm *.o >> libWeiboSDK.symbols.txt

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

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 確實(shí)使用了分類技術(shù)擴(kuò)展了NSArray、NSDictionary和NSString三個(gè) Foundation Framework 下面的類的行為。好,現(xiàn)在可以真相大白了:

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

為什么增加編譯選項(xiàng)可以解決問題?

我們繼續(xù)引用蘋果的開發(fā)者網(wǎng)站針對(duì)這個(gè)問題的說明文章中的內(nèi)容:

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選項(xiàng)后,不管是否被引用到,鏈接器會(huì)把 Objective-C 的類和分類的所有對(duì)象文件全部鏈接,全部鏈接后方法符號(hào)全部被鏈接進(jìn)來,崩潰的問題自然被解決了。

而-all_load選項(xiàng)更徹底,這個(gè)選項(xiàng)會(huì)讓鏈接器把全部的對(duì)象文件都鏈接進(jìn)來,當(dāng)然,代價(jià)就是構(gòu)建的 APP 體積會(huì)變大。

為什么 cocos2d-x 加了編譯選項(xiàng)會(huì)無法編譯通過?

其實(shí)準(zhǔn)確的說法是編譯可以成功進(jìn)行,鏈接器執(zhí)行報(bào)錯(cuò)。我們?cè)倩仡櫼幌录恿?ObjC或-all_load鏈接選項(xiàng)后鏈接器的報(bào)錯(cuò)信息:

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)

根據(jù)報(bào)錯(cuò)信息我們能夠了解到報(bào)錯(cuò)是一個(gè)名叫CCController-iOS.o對(duì)象文件導(dǎo)致的,而這個(gè)文件對(duì)應(yīng)的源代碼是CCController-iOS.mm,通過閱讀源碼我們發(fā)現(xiàn),這個(gè)文件中定義了一個(gè) Objective-C 的類GCControllerConnectionEventHandler,這個(gè)類中的方法引用了GCControllerDidConnectNotification和GCControllerDidDisconnectNotification兩個(gè)類,而這兩個(gè)類實(shí)在GameController Framework中定義的。

而 cocos2d-x 生成的項(xiàng)目默認(rèn)并沒有為我們引入GameController Framework,所以在鏈接時(shí)由于鏈接器找不到對(duì)應(yīng)類的符號(hào)定義,所以才會(huì)報(bào)錯(cuò)。如果你到 Xcode->Target->Buid Phases-> 下 ? Link Binary With Libraries ? 項(xiàng)添加GameController Framework就可以解決問題了,但是這種解決方式很不干凈

正確的姿勢(shì)

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容