-ObjC 的故事
在开发 iOS 应用时,可能遇见这样的情况:你想实现的某种功能(比如崩溃收集),已经有成熟的产品提供,可行的方案就是集成这个使用了很久,且有专业人员维护,最重要的是“免费”的产品。一番折腾之后,运行时有可能出现”selector not recognized”错误,最后发现是少了如下的配置(Build Settings -> “-ObjC”):
“-ObjC” 的使用场景
据坊间说:如果你集成了有 category 的静态库,有可能出现上述错误。原因就是
"An impedance mismatch between UNIX static libraries and the dynamic nature of Objective-C can cause category methods in static libraries to not be linked into an app, resulting in "selector not recognized" exceptions when the methods aren't found at runtime."
这段话的意思就是:链接器在处理包含Category方法的UNIX的静态库时,没有将Category的方法链接到APP中,造成这个错误。具体的细节在本文的补充部分展开。
可以看出,解决这个错误的方法就是:将Category的方法链接到APP中,这样APP运行时,就能够找到对应的selector。而 –ObjC就可以完成这个任务。
“-ObjC”的作用
将静态库中任何Objective-C代码都链接到APP中。任何Objective-C代码当然也包括Category的方法。可以看出,使用-ObjC可能会链接很多静态库中未被使用的Objective-C代码,极大的增加APP的代码体积。
之所以使用该标志,和Objective-C的一个重要特性:类别(category)有关。根据这里的解释,Unix的标准静态库实现和Objective-C的动态特性之间有一些冲突:Objective-C没有为每个函数(或者方法)定义链接符号,它只为每个类创建链接符号。这样当在一个静态库中使用类别来扩展已有类的时候,链接器不知道如何把类原有的方法和类别中的方法整合起来,就会导致你调用类别中的方法时,出现”selector not recognized”,也就是找不到方法定义的错误。为了解决这个问题,引入了-ObjC标志,它的作用就是将静态库中所有的和对象相关的文件都加载进来。
本来这样就可以解决问题了,不过在64位的Mac系统或者iOS系统下,链接器有一个bug,会导致只包含有类别(没有类)的静态库无法使用-ObjC标志来加载文件。变通方法是使用-all_load 或者-force_load标志,它们的作用都是加载静态库中所有文件,不过all_load作用于所有的库,会强制链接器把目标文件都加载进来,即使没有objc代码,而-force_load在xcode3.2后可用,后面必须要指定具体的文件。
“-ObjC” 的兄弟
和 “-ObjC”作用类似的有以上的五种方案。可以看出,从增加APP代码体积来看,伪符号方案增加得最少”Perform Single-Object Prelink”、 “-force_load” 和 “–ObjC” 次之,”-all_load” 增加得最多。
在开发iOS SDK时,为了方便使用者手动集成,最好是减少使用者需要配置的信息,所以”伪符号”方案和 “Perform Single-Object Prelink”方案是推荐的。另外,第三方SDK常常是闭源的,对于使用者来说,伪符号是透明的,所以从简便性角度看,推荐”Perform Single-Object Prelink”方案。
“selector not recognized”错误的产生根源
iOS工程,从源文件到生成最终的APP文件,通常要经过如下步骤:
源文件经过编译和优化后,会生成目标代码。目标代码中包括符号表,标示了此代码中的全局符号和静态符号,还标示了导入符号等,链接器会根据符号表分析各个目标代码之间的调用关系,然后将使用到的代码进行链接和重定位,最后生成可执行文件。
在编译Objective-C源文件到目标文件时,编译器并不知道方法的对应实现,只能在运行时才知道,所以编译器只会为类生成链接符号,对类中的方法不会生成链接符号。由于Category方法并不对应一个新类,所以不会生成链接符号,链接器也不会将Category方法合并到原始的类中,最终导致链接器忽略了Category方法,不会将其链接到可执行文件中。
上一篇: Android 适配归纳总结
下一篇: PHP SQlite数据库相关函数概览