【iOS开发】静态库导致的运行时崩溃问题

时间:2022-04-01 15:03:54

使用其他一些第三方静态库时, 如果没有注意按照文档中的提示进行配置, 很容易在程序运行过程中因 “unrecorgnized selector send to instance xxx” 的异常而崩溃. 而且可以发现, 导致崩溃的方法都是Category中的方法. 

苹果官方文档中解释到: 

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 does not define linker symbols for methods. Linker symbols are only defined for classes.
Since categories are a collection of methods, using a category’s method does not generate an undefined symbol. This means the linker does not know to load an object file defining the category, if the class itself is already defined. This causes the same “selector not recognized” runtime exception you would see for any unimplemented method.

翻译过来, 就是因为Objective-C的动态特性使得方法是在运行时才确定的, 所以链接的时候不会为方法(Method)定义连接符号, 而是为类(Class)定义连接符号. 这样当在一个静态库中使用类别来扩展已有类的时候,链接器不知道如何把类原有的方法和类别中的方法整合起来, 所以就导致运行的时候Category的方法无法被找到抛出异常.

解决方法: Other Linker Flags


  • -ObjC

苹果官方文档对这个flag的解释是这样的:

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的类和Category, 这样就能把Category中实现的方法整合起来. 但是由于这样做会使可执行文件变大, 也会整合一些用不到的对象, 所以才没有默认使用-ObjC标志, 而是需要我们手动添加.

  • -all_load

加载所有静态库中的文件. 相比-ObjC, 不同点就是-all_load会将所有的(包括非Objective-C)文件都整合到静态库中.
*注意 : 假如你使用了不止一个静态库,然后又使用了这个参数,那么你很有可能会遇到duplicate
symbol
错误,因为不同的库文件里面可能会有相同的目标文件.

  • -force_load (path_to_archive)

加载指定路径的静态库. 相比-all_load, 不同点就是-force_load只是完全加载了一个库文件,不影响其余库文件的按需加载.

补充:


使用-all_load或者-force_load大部分原因是因为Xcode4.2之前的版本的链接器的bug, 在64位iOS应用环境下当静态库中只有分类而没有类的时候, -ObjC参数就会失效了. 所以为了兼容Xcode4.2之前的版本, 有两种解决方法:

  • 静态库使用者:

使用-all_load或者-force_load代替-ObjC.

  • 静态库开发者:

可以通过在分类中添加一个类的声明和实现, 使得Category源文件中不仅仅只有分类, 同时还有类存在来避免链接器的bug, 从而避免了-ObjC标志失效的麻烦:

/**
* NSObject+Addition.m
*/


// add a dummy class
@interface DUMMY_CLASS_NSObject_Addition : NSObject
@end
@implementation DUMMY_CLASS_NSObject_Addition
@end

@implementation NSObject (Addition)
// some category methods...
@end

但是Xcode4.2之后, 只要使用-ObjC即可. 具体可以参看*的这个回答.


这个问题我解决问题的步骤如下:

1、 首先我碰到以下crash

1.1 unregonize selector crash,这时候首先肯定就想着为啥能够直接从这个crash中获取到啥有用信息啊,于是我就尝试去看arm的汇编,只不过也不用看了,动态语言,肯定都是动态分发消息,看不出啥猫腻。

1.2 于是乎我就想从lldb下手,想通过牛逼的调试手段获得到收获(windows上牛逼的调试能力给我带来了很多以往经验上优势的快感),尝试了chisel的pclass、pinternals等等无计可施,发现没有办法打印出这个类的函数,后面经由同事给出他曾经写过的脚本utils.printMethods = function(className) {
 var count = new new Type("I");
 var classObj = objc_getClass(className);
 var methods = class_copyMethodList(classObj, count);
 var methodsArray = [];
 for(var i = 0; i < *count; i++) {
   var method = methods[i];
methodsArray.push(method_getName(method));
 }
 free(methods);
return methodsArray;
};

可惜目前lldb只支持python脚本,我就没有用上

1.3  接着我就从逆向汇编的角度入口,先是下载了ida,可惜ida mac没有64位版本而放弃改用hopper dissamble,逆向之后问题原因终于搞清楚,那就是分类竟然没有编译进入mach-o文件中,所以很高兴一个里程碑,但是同时又发难了,为啥都在工程里面了还不会编译进去,

1.4  于是乎我就查看了xcode所有编译选项里面的配置,是否有添加对此文件的编译,发现工程配置没有问题【iOS开发】静态库导致的运行时崩溃问题


1.5 那么是否可以根据工程编译的流程逐步定位出到底问题在哪里呢,于是我定位了2个流程

工程编译log和链接过程log

通过编译输出文件的定位,确实定位到了分类文件确实有编译进去,那就更奇怪了,为啥编译了会没有链接呢,我具体查看了编译目录

/Users/fishmai/Library/Developer/Xcode/DerivedData/Build/Intermediates,同时查看了【iOS开发】静态库导致的运行时崩溃问题

看不出有啥编译问题啊,同时链接也没有任何关于分类主类合并的日志

1.6  进一步通过我分类的关键字来定位,采用spotlight全局查找,找到了以下让我为之兴奋的文件/Users/fishmai/Documents/huangyang/iPhone_7.1.0_HuaYangJiaoYou/Classes/module/VideoChatModule/build/VideoChatModule.build/Debug-iphoneos/VideoChatModule.build/Objects-normal/armv7/VideoChatModule.LinkFileList  可惜看了整个文件,也只能确认我的分类没有编译进来,希望再次落空

1.7  后面咨询了资深同事,说需要打一个flag,于是乎就有上上面的资料查询,尼玛设置了还是米有效果,崩溃了,设置项如下【iOS开发】静态库导致的运行时崩溃问题

这时候完全晕菜了

1.8  第二天过来另一个同事给了我最终答案, 原来包含这个静态库的工程也还要进行设置,apple不带这么坑的,本身工程设置了,调用的工程也需要设置,真是坑大,网络上的讲解也没有讲解清楚,都说的是或者关系,设置其中-all_load或者-force_load,汗死我,总算搞定了