解決方法是:
-force_load path/to/your/libWeiboSDK.a 而不是 他提供的-ObjC担忧、-all_load芹缔,下面是一些詳細說明
這里特別給出示范路徑,比如你在項目中導入了XXX.a放在一個叫aaa的group文件下瓶盛,那么路徑就是aaa/xxx.a最欠,或者你可以使用全路徑,點擊對應的xxx.a靜態(tài)庫蓬网,會在Xcode的右側(cè)出現(xiàn)該文件的路徑,把它復制過來就可以了
遇到的問題
根據(jù)新浪微博 SDK 附帶的文檔接入項目后鹉勒,在模擬器運行項目帆锋,在調(diào)用注冊方法時發(fā)生崩潰。注冊方法代碼:
1
[WeiboSDK registerApp: @"xxxxxxxx"];
崩潰信息打印如下:
1
[__NSDictionaryM weibosdk_WBSDKJSONString] : unrecognized selector sent to instance 0x170255780
解決問題遇到的阻礙
新浪微博 SDK 附帶的文檔中有這么一個說明:
在工程中引入靜態(tài)庫之后,需要在編譯時添加 ? –ObjC ? 編譯選項,避免靜態(tài)庫中類 加載 ? 不全造成程序崩潰禽额。方法:程序 ? Target->Buid ? Settings->Linking ? 下 ? Other ? Linker ?Flags ? 項添加-ObjC
在網(wǎng)上看到遇到同樣崩潰錯誤的人有提到在編譯時添加?-all_load?編譯選項時也可以解決問題锯厢。方法也是在 ? Target->Buid ? Settings->Linking ? 下 ? Other ? Linker ?Flags ? 項添加-all_load。
無獨有偶脯倒,我在打開新浪微博 SDK 附帶的 Demo 項目時發(fā)現(xiàn)這個項目的編譯選項也是-all_load而不是它自己文檔所提示的-ObjC实辑。而且在同樣的開發(fā)環(huán)境下,我的 cocos2d-x 項目會崩潰藻丢,但是新浪微博 SDK 附帶的 Demo 可以正常工作剪撬,想必上述兩個解決方案應該是正解
但是在給自己的 cocos2d-x 項目添加了編譯選項后,再次編譯運行就發(fā)生了錯誤悠反,錯誤信息如下:
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 靜態(tài)鏈接庫的確切位置疫诽。
這一切是為什么?
從編譯鏈接說起
這里不打算過多的介紹編譯鏈接相關(guān)的只是,但是強烈推薦一本書《程序員的自我修養(yǎng)》奇徒,光看正標題你可能會擔心這是本沒什么“正經(jīng)”內(nèi)容的書雏亚,至少我當初第一次看到這書名的時候就是這么認為的,但是我錯了逼龟,這本書的副標題是鏈接评凝、裝載與庫。相信我腺律,看過這本書 N 遍之后你自會對程序從源代碼編譯鏈接到生成二進制程序的原理和過程有一個非常透徹的理解奕短,并且更重要的是看過這本書 N 遍之后你會上升幾個層次。
言歸正傳匀钧,一個工程的源代碼最終變成二進制的可執(zhí)行程序翎碑、動態(tài)鏈接庫或靜態(tài)鏈接庫要經(jīng)歷這么幾個過程:
1
源代碼 ==[編譯器]==》 匯編碼 ==[匯編器]==》 對象文件 ==[鏈接器]==》 可執(zhí)行程序、動態(tài)鏈接庫或靜態(tài)鏈接庫
再說說符號是什么之斯?
通俗的講日杈,我們在源碼中寫的全局變量名、函數(shù)名或類名在生成的*.o對象文件中都叫做符號佑刷,存在一個叫做符號表的地方莉擒。
舉個例子:我們在a.c文件中寫了一個函數(shù)叫foo(),然后在main.c文件中調(diào)用了foo()函數(shù)瘫絮,在將源碼編譯生成的對象文件中a.o對象文件中的符號表里保存著foo()函數(shù)符號涨冀,并通過該符號可以定位到a.o文件中關(guān)于foo()方法的具體實現(xiàn)代碼。
鏈接器在鏈接生成最終的二進制程序的時候會發(fā)現(xiàn)main.o對象文件中引用了符號foo()麦萤,而foo()符號并沒有在main.o文件中定義鹿鳖,所以不會存在與main.o對象文件的符號表中,于是鏈接器就開始檢查其他對象文件壮莹,當檢查到a.o文件中定義了符號foo()翅帜,于是就將a.o對象文件鏈接進來。這樣就確保了在main.c中能夠正常調(diào)用a.c中實現(xiàn)的foo()方法了命满。
libWeiboSDK.a 靜態(tài)鏈接庫里有什么涝滴?
Unix 的靜態(tài)鏈接庫沒什么神秘的,它就是個壓縮包胶台,和平時比較常見的 zip 或 rar 之類的壓縮包一樣狭莱,只不過人家是用一個叫 ar 的壓縮工具壓縮的而已。所以我們給它解壓縮一下概作,看看它里面都有什么腋妙。既然是用 ar 壓縮的,解壓自然也要用 ar 這個工具讯榕。在命令行執(zhí)行:
1
ar -x lieWeiboSDK.a
結(jié)果報錯了:
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ā)時我們都知道可以用模擬器和真機來測試我們的項目匙睹,但是這兩個平臺的架構(gòu)是不一樣的,模擬器是 i386 x86_64 架構(gòu)的济竹,而我們的設備是 armv7 arm64 架構(gòu)的痕檬。當在制作靜態(tài)鏈接庫的時候也要針對不同的架構(gòu)制作出針對真機和模擬器的兩個靜態(tài)鏈接庫,而當我們想在自己的項目中使用靜態(tài)鏈接庫的時候送浊,如果在模擬器上運行我們要用針對模擬器的靜態(tài)庫版本梦谜,用真機設備測試的時候還要切換到針對真機的靜態(tài)鏈接庫,這樣一來非常的麻煩袭景。
前面說過了靜態(tài)鏈接庫就是個壓縮包唁桩,那么我們是否能將這兩個靜態(tài)鏈接庫壓縮成一個靜態(tài)鏈接庫這樣就可以同時支持模擬器和真機設備兩種架構(gòu)了呢?答案是肯定的耸棒。比如我們手頭有一個靜態(tài)鏈接庫的兩個架構(gòu)版本:libXXX.i386_x86_64.a和libXXX.armv7_arm64.a荒澡,那么我們可以通過如下命令來生成一個統(tǒng)一的靜態(tài)鏈接庫:
1
lipo -create libXXX.i386_x86_64.a libXXX.armv7_arm64.a -output libXXX.a
這樣我們就得到了一個統(tǒng)一版本的靜態(tài)庫libXXX.a,它的好處是同時支持模擬器架構(gòu)和真機設備架構(gòu)与殃,缺點是它的體積變大了单山,也就是說它很肥(fat)。
而libWeiboSDK.a就是這么一個合體后的靜態(tài)庫幅疼,我們照樣可以通過命令來驗證這一點:
1
lipo -info libWeiboSDK.a
這個命令會輸出:
1
Architectures in the fat file: libWeiboSDK.a are: armv7 arm64 i386 x86_64
既然是個胖子米奸,那我們就要先給它瘦身才能解壓。我們隨便從里面抽出一個架構(gòu)的靜態(tài)鏈接庫來爽篷,瘦身命令是:
1
lipo -thin i386 libWeiboSDK.a -output libWeiboSDK.i386.a
這樣我們就把針對 i386 平臺的新浪微博 SDK 靜態(tài)鏈接庫給抽離出來了悴晰,我們管它叫l(wèi)ibWeiboSDK.i386.a,現(xiàn)在我們再用ar命令解壓它看看里面有什么
1
ar -x libWeibo.i386.a
解壓完成后你會看到好多好多以.o結(jié)尾的對象文件狼忱,回憶回憶剛剛我們講到的編譯鏈接過程膨疏,這些對象文件就是給鏈接器最終生成靜態(tài)鏈接庫時用到的文件一睁,由于太多了钻弄,我只列出我們要講到的幾個:
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 的靜態(tài)鏈接庫引入我們自己的項目者吁,并 Build 我們自己的項目到模擬器或真機設備上運行的過程其實也是一個編譯鏈接的過程窘俺,最終從項目 Build 生成可以在模擬器或真機設備運行的 App,而這個過程中對新浪微博 SDK 的靜態(tài)鏈接庫的處理方式和我們剛剛拆開libWeiboSDK.a的過程差不多:
將 libWeibSDK.a 根據(jù)當前所構(gòu)建的平臺架構(gòu)(模擬器還是真機設備)進行瘦身將瘦身的靜態(tài)庫解壓拆包將用到的對象文件鏈接進入項目
而我們遇到的崩潰問題恰恰是出在了將用到的對象文件鏈接進入項目這一步复凳。
蘋果的開發(fā)者網(wǎng)站針對這個問題有一篇說明文章瘤泪,我們來引用一下里面的內(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 是有運行時(runtime)的,一個方法要執(zhí)行什么代碼是在運行時決定的育八,而不是在鏈接時決定的对途。想要再深入了解 Objective-C 運行時知識的,可以看看這里
Objective-C does not define linker symbols for methods. Linker symbols are only defined for classes.
因為在 Objective-C 中髓棋,一個方法的執(zhí)行是要到運行時才決定的实檀,所以在鏈接時惶洲,鏈接器只鏈接類的符號,并不會鏈接方法的符號膳犹。
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的對象恬吕,然后調(diào)用了這個類FooClass的一個對象方法initWithBar,在鏈接器分析由main.m編譯生成的main.o對象文件時须床,發(fā)現(xiàn)這個對象文件沒有定義符號FooClass于是就會去其他.o對象文件中去尋找FooClass符號的定義铐料,而至于方法符號initWithBar的定義在哪里鏈接器是不關(guān)心的,因為initWithBar的執(zhí)行是由運行時負責的豺旬,鏈接器不管钠惩。
好了,現(xiàn)在問題來了哈垢,我們再重復一下這句話:
1
Objective-C 中方法的執(zhí)行實在運行時決定的妻柒,所以鏈接器只鏈接類的符號,不鏈接方法的符號
我們再回過頭看看崩潰的報錯信息:
1
[__NSDictionaryM weibosdk_WBSDKJSONString] : unrecognized selector sent to instance 0x170255780
這說明崩潰的原因是在運行時調(diào)用__NSDictionaryM類對象的weibosdk_WBSDKJSONString方法時沒有找到該方法的定義耘分。這里不難看出__NSDictionaryM是Foundation Framework中的類举塔,而方法weibosdk_WBSDKJSONString是新浪微博 SDK 自己定義的方法,新浪在這里使用了分類技術(shù)擴展了__NSDictionaryM類的行為求泰。我們來驗證這一點:
我們已經(jīng)解壓出libWeiboSDK.a中的全部.o對象文件央渣,我們用nm命令導出全部對象文件中的符號:
1
nm *.o >> libWeiboSDK.symbols.txt
然后我們用個文本編輯器打開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ù)擴展了NSArray渴频、NSDictionary和NSString三個 Foundation Framework 下面的類的行為芽丹。好,現(xiàn)在可以真相大白了:
在鏈接時卜朗,鏈接器發(fā)現(xiàn)WBSDKJSONKit.o對象文件中缺少類符號NSArray拔第、NSDictionary和NSString。鏈接器從Foundation Framework中找到了類的符號定義场钉,從而將Foundation Framework中相關(guān)的對象文件鏈接進來由于鏈接器不鏈接方法符號蚊俺,所以weibosdk_WBSDKJSONString這樣的方法符號完全被忽略了。由于類符號的定義在Foundation Farmework中定義逛万,所以WBSDKJSONKit.o對象文件中沒有符號被引用泳猬,鏈接器就沒有把這個對象文件鏈接進來。運行時運行到weibosdk_WBSDKJSONString方法時宇植,由于Foundation Framework中是不存在這個方法的定義的得封,而存在這個方法定義的WBSDKJSONKit.o對象文件又沒有被鏈接器鏈接進來,所以崩潰了指郁。
為什么增加編譯選項可以解決問題忙上?
我們繼續(xù)引用蘋果的開發(fā)者網(wǎng)站針對這個問題的說明文章中的內(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選項后,不管是否被引用到闲坎,鏈接器會把 Objective-C 的類和分類的所有對象文件全部鏈接疫粥,全部鏈接后方法符號全部被鏈接進來洋腮,崩潰的問題自然被解決了。
而-all_load選項更徹底手形,這個選項會讓鏈接器把全部的對象文件都鏈接進來啥供,當然,代價就是構(gòu)建的 APP 體積會變大库糠。
為什么 cocos2d-x 加了編譯選項會無法編譯通過伙狐?
其實準確的說法是編譯可以成功進行,鏈接器執(zhí)行報錯瞬欧。我們再回顧一下加了-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)
根據(jù)報錯信息我們能夠了解到報錯是一個名叫CCController-iOS.o對象文件導致的贷屎,而這個文件對應的源代碼是CCController-iOS.mm,通過閱讀源碼我們發(fā)現(xiàn)艘虎,這個文件中定義了一個 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一樣的事情唯鸭,只不過它更有針對性须蜗,它只讓鏈接器把你指定的靜態(tài)鏈接庫中的全部對象文件鏈接進來,這樣更清爽一些目溉。