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

來源:互聯網
上載者:User

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

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

 

這裡特別給出示範路徑,比如你在項目中匯入了XXX.a放在一個叫aaa的group檔案下,那麼路徑就是aaa/xxx.a,或者你可以使用全路徑,點擊對應的xxx.a靜態庫,會在Xcode的右側出現該檔案的路徑,把它複製過來就可以了



我的開發環境
  • Mac OS X 10.10.1Xcode 6.1.1 (6A2008a)Cocos2d-x 3.2新浪微博 SDK for iOS 2015 年 1 月 5 日從githubclone 的版本遇到的問題
    根據新浪微博 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一樣的事情,只不過它更有針對性,它只讓連結器把你指定的靜態連結庫中的全部對象檔案連結進來,這樣更清爽一些。 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.