《转》iOS 平台 Cocos2d-x 项目接入新浪微博 SDK 的坑

最近在作一个 iOS 的 cocos2d-x 项目接入新浪微博 SDK 的时候被“坑”了,最后终于顺利的解决了。发现网上也有很多人遇到同样的问题,可是能找到的数量有限的解决办法写得都不详细,很难让人理解,我来深刻的写一写。html

个人开发环境

  • Mac OS X 10.10.1ios

  • Xcode 6.1.1 (6A2008a)git

  • Cocos2d-x 3.2程序员

  • 新浪微博 SDK for iOS 2015 年 1 月 5 日从 github clone 的版本github

遇到的问题

根据新浪微博 SDK 附带的文档接入项目后,在模拟器运行项目,在调用注册方法时发生崩溃。注册方法代码:objective-c

1
[WeiboSDK registerApp: @"xxxxxxxx"];

崩溃信息打印以下:架构

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

解决问题遇到的阻碍

新浪微博 SDK 附带的文档中有这么一个说明:app

在工程中引入静态库以后,须要在编译时添加   –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.alibXXX.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方法时没有找到该方法的定义。这里不难看出__NSDictionaryMFoundation 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 确实使用了分类技术扩展了NSArrayNSDictionaryNSString三个 Foundation Framework 下面的类的行为。好,如今能够真相大白了:

  • 在连接时,连接器发现WBSDKJSONKit.o对象文件中缺乏类符号NSArrayNSDictionaryNSString

  • 连接器从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,这个类中的方法引用了GCControllerDidConnectNotificationGCControllerDidDisconnectNotification两个类,而这两个类实在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同样的事情,只不过它更有针对性,它只让连接器把你指定的静态连接库中的所有对象文件连接进来,这样更清爽一些。

但愿个人解释已经够深刻了。

 

 

转自:http://leenjewel.github.io/blog/2015/01/08/ios-ping-tai-cocos2d-x-xiang-mu-jie-ru-xin-lang-wei-bo-sdk-de-keng/

相关文章
相关标签/搜索