走近静态库(异常错误处理)
0x00 进击的.a/.framework
那一天,人們回想起了
在它们支配下的恐懼…
以及被它们蹂躏屈辱……
0x01 Do u remember these?
1.0 Undefined symbols for architecture
1 | Undefined symbols for architecture i386/x86_64: |
这TM是什么鬼东西?
要想解决这个Error,我们首先要明白为什么会出现这个错误。先来科普一下iOS设备CPU指令集:
- i386: 对应32位模拟器的CPU
- x86_64: 对应64位模拟器的CPU
- armv7/armv7s: 对应32位真机设备的CPU
- arm64: 对应64位真机设备的CPU
知道了上面的知识,我们再来看看上面的错误描述, 意思就是说在某种CPU指令集下没有找到对应的符号(“_OBJC_CLASS_$_FuckingClazz”),出现这种错误一般是你引用的第三方库不支持某种CPU指令集,那我们就在工程中搜索一下 ‘FuckingClazz’ 看看这个东西是对应那个库。经过搜索后发现他对应libFuckingLibray.a这个库。这个时候你的心里就会飘过千万只羊驼,问候了千万次库提供方。
骂完之后平静下来,生活还得继续,代码还得写。那我们该怎么办呢?
- 去找库提供方让他提供支持多指令集的库(如果库提供方有着良好的程序员自我修养的话,那下面的内容你可以不用看了)。
- 自己动手解决
下面我们就来讲讲怎么动手解决
首先先来个进阶知识,打开终端输入以下命令:1
$: nm a/b/libFuckingLibray.a
这个时候你会发现一堆花花绿绿的看不懂的东西,别急,找到”_OBJC_CLASS_$_FuckingClazz” 复制,在上面命令的执行结果里搜索一发。
你会看到:
1 | libFuckingLibray.a(FuckingLibray.o) (for architecture armv7): |
这个时候你会发现,哦原来这个库只支持armv7和arm64两种指令集。
具体nm命令和更高级的用法,在这里就不细说了自己去问男人:
1 $: man nm
当然还有更简单的一个命令可以直接查看一个库支持哪几种CPU指令集:1
2$: lipo -info a/b/libFuckingLibray.a
Architectures in the fat file: libFuckingLibray.a are: armv7 arm64
好了,找到原因了,那我们来手动为这个FuckingLibray来增加i386和x86_64的支持吧。
- 新建一个EunuchFuckLibray的静态库工程
- 将所有和FuckingLibray暴露出来的头文件(.h)导入到静态库工程,并设置为public的。然后创建每个.h对应的.m文件。
- 为了消除一些方法没实现之类的警告,我们在.m里实现所有.h的声明的方法,方法具体实现,随便写(一般返回值为void的 可以在方法体内 NSLog(@”[FuckingLibray]:<%s> SDK不支持模块器! “,FUNCTION);,有返回值的返回0/NO/nil之类的);
- 将此静态库编译成支持FuckingLibray缺少的那一部分CPU指令集的库
最后我们将这个编译好的库与之前的太监库合并:
1
2$: lipo -create a/b/libFuckingLibray.a c/d/libEunuchFuckLibray.a -output ~/Desktop/libFuckingLibray.a
#执行完这条命令后,你会发现你的桌面上多了一个libFuckingLibray.a文件将工程中的libFuckingLibray.a替换成你刚才生成的
- command + R
1.2 duplicate symbol _OBJC_IVAR_$_XXXXX._opacity in
出现这种链接错误,一种的是工程中的确存在重复的文件或者全局变量定义,这种情况手动查找到删除解决,本文不再多说.另外一工程中的文件与第三方静态库中的文件重复,本文主要针对这种情况给予解决方案
1 | duplicate symbol _OBJC_IVAR_$_MBProgressHUD._opacity in: |
看到这个不要慌,细心看一下错误,意思是说 _OBJC_IVAR_$_XXXXX._opacity
这个符号 重复定义了.
那我们来在工程中找一下XXXXX
这个类,结果发现是一个我们引用的第三方库,再看一下,我们的工程中只有一份啊,怎么会重复定义,把这个库放到其它的工程中就没有问题.这个时候开始怀疑人生了,到底哪里出了错.
那我们只好慢慢排查,当发现我们把之前添加的某个FuckingLibray库从工程中去掉时,就能编译过了,就正常了.这下可以确定是FuckingLibray这个库在搞事情了,那我们还是通过nm
来查看一下1
$: nm libFuckingLibray.a
在结果中查找一下 XXXXX”,结果发现找到了,好了,现在可以下结论了: FuckingLibray这个库把我们工程中使用的一个第三方库也编译进去了.
如果一时找不到其它库替换这个FuckingLibray,那这下该怎么办呢,场面一度陷入江局.
下面我来告诉我怎么做:
- 方法一: 将你工程中的 XXXX 这个库去掉, 编译+运行,完美解决
- 方法二: 将编译进第三方库中的 XXXX这个库去掉,具体方法下面附上一个脚本:
1 |
|
1 |
|
到此,问题算是解决了,大部分情况下工程也能正常编译了,但也会有一些隐患的,比如工程中使用了某个库的1.0.0版本, 而那个该死的不得不用的库把共用的库的2.0.0版本编译进去了,些时可能会出现一些API不兼容的情况,遇到这情况,要么工程升级共用的库到2.0.0版本,要么联系FuckingLibray的作者来更改实现.
1.3 unrecognized selector sent to class 0x1009beda8’
1 | Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[FuckingLibray anythingOtherOperation]: unrecognized selector sent to class 0x1009beda8' |
这TM又是怎么了?
当你使用第三方库时,调用一些第三方库中的分类中的方法时,会造成这种崩溃.
具体原因参考:苹果官方文档
总结一下就是说OC没有为方法定义链接符号,只为类定义了链接符号.而分类只是一些方法的集合,链接器没有将分类中的对象加载进来,所以链接器不知道去哪找这个方法,所以就在链接阶段报错了.
知道了原因,同时苹果告诉我们解决办法是在工程Build Setting中Other Linker Flags,添加 -ObjC
. 但是这个-ObjC
有一个BUG,就是当第三方库中只有分类而没有分类所属的类时,链接器还是不能将分类中的方法实现加载进来.所以就轮到-all_laod
出场了,这个标识符可以让链接器把目标文件都加载进来.但是苹果也说了,加入这些链接标识符号会使生成的可执行文件变大,有时候我们只想针对某个库来添加,那么就又有了-force-load
出场,这个标识符号的用法和前面两个不同,要在后面跟一个路径,-force-load ${SDKPath}
好了,这下问题解决了,command + R