前几天在iOS app项目中添加了几个第三方库,各有各的用处,因为一些原因,有些库是不开源的。
添加后,发现app编译不通过,错误如下:
从错误描述中都能看出,app在连接过程中,发现了一些重复的符号,即同样的OC类和方法在不同的库中都有实现:liblibPDRCore.a和libsimpleconfiglib.a这两个库有冲突!恰好,这两个库都要用,而且都不开源,仿佛一下子就走进了死胡同,因为没有办法修改这两个库。
网上搜了一下,碰到这种问题的人还真不少,也提出了解决方案:用lipo命令分解其中一个类,删除重复的符号,再用ar命令重新打包成库,我选择修改liblibPDRCore.a这个库:
1.查看包信息:lipo -info liblibPDRCore.a,提示fat file,那么代表这个包是支持多平台的,例如armv7,armv64等
2.取出armv7包:lipo liblibPDRCore.a -thin armv7 -output armv7/liblibPDRCore_armv7.a
3.解压出object file(.o文件):cd armv7&ar xv liblibPDRCore_armv7.a
4.根据出错信息删除PDRSerAsyncSocket.o:rm PDRSerAsyncSocket.o
5.重新打包:ar rcs liblibPDRCore_armv7.a *.o,记得把旧的库删掉
6.重复1-5把arm64什么的一起改了
7.合并为fat库:lipo -create liblibPDRCore_armv7.a liblibPDRCore_arm64.a -output liblibPDRCore.a
好了,现在用新和成的库替换,并编译,发现编译成功了!
且慢!这和主题OC runtime有什么关系?只是用一些工具去掉了重复符号!
是的,确实没有关系,下面才正式进入主题!
编译是成功了,但实际运行起来呢?非常抱歉,crash了,错误如下:
原因可能是:这两个库虽然有一个名字一样的OC类,有些方法也一样,但并不是完全一样,可以认为,这两个库依赖了另一个开源库,但不是同一个版本,仿佛又走进死胡同鸟!
仔细分析了一些出错信息:liblibPDRCore.a某个地方调用了AsyncSocket类的方法acceptOnAddress:port:error:,但libsimpleconfiglib.a中的AsyncSocket却没有这个方法,造成调用异常。
现在轮到OC runtime上场了,我也只是抱着试一试的态度。前段时间正好看这方面内容,了解到OC可以在运行时动态增加、替换一些类的方法,解决一些实际问题。现在我想到一个思路:用一个空函数,替代AsyncSocket类的方法acceptOnAddress:port:error:,看是否正常运行
1.先获取类类型:Class _asyncSocketClass = NSClassFromString(@"AsyncSocket");
2.给AsyncSocket类添加方法:class_addMethod(_asyncSocketClass, NSSelectorFromString(@"acceptOnAddress:port:error:"), IMP, types);,这个函数中,后面两个参数,一个是替换方法,一个是方法参数类型,替换方法参数好搞,直接定义一个C格式的函数,传函数指针即可;方法参数类型怎么弄?
于是,我想到了从libsimpleconfiglib.a库中找一些蛛丝马迹,虽然它和liblibPDRCore.a使用了不同版本的AsyncSocket类,但某些方法应该是类似的,获取可以帮我完成函数class_addMethod所需的最后一个参数
3.用lipo和ar命令获得libsimpleconfiglib.a中的object file:"AsyncSocket.o"
4.查看"AsyncSocket.o"的导出符号:nm AsyncSocket.o,果不其然,发现一个类似的,方法名不同,参数名却一样
5.再看一下liblibPDRCore.a库中的PDRSerAsyncSocket.o的导出符号,确实有"acceptOnAddress:port:error:"这个方法,我们知道,这个目标文件已经被我们去掉了
6.从名称上判断,这两个方法参数一样,而且方法acceptOnInterface确实存在,用method_getTypeEncoding来获取方法的类型即可得到class_addMethod的最后一个参数,用有了这个思路,完成了如下代码:
void impfunc(Class cls, SEL _cmd) { if([NSStringFromSelector(_cmd) isEqualtoString:@"acceptOnAddress"]) { //调用cls的acceptOnInterace的方法 } } - (BOOL)application:... { Class _asyncSocketClass = NSClassFromString(@"AsyncSocket"); Method _acceptOnInterface = class_getInstanceMethod(_asyncSocketClass, NSSelectorFromString(@"acceptOnInterface:port:error:")); class_addMethod(_asyncSocketClass, NSSelectorFromString(@"acceptOnAddress:port:error:"), (IMP)impfunc, method_getTypeEncoding(_acceptOnInterface)); }
7.编译并运行起来之后,确实走到了impfunc,程序也不崩溃了,但相关的功能不正常,我想还是要真正调用一下acceptOnInterface这个方法才行
8.我想到了解析_cmd的参数,取出参数值,然后再调用objc_msgSend发送消息,尝试了很多方法,没有成功;实际上,我还是走了弯路,各位应该也看出来了,class_addMethod的第三个参数,直接用acceptOnInterface的实现不就行了!method_getImplementation可以帮我们做到,于是代码变成如下,编译后运行,成功了!
- (BOOL)application:... { Class _asyncSocketClass = NSClassFromString(@"AsyncSocket"); Method _acceptOnInterface = class_getInstanceMethod(_asyncSocketClass, NSSelectorFromString(@"acceptOnInterface:port:error:")); class_addMethod(_asyncSocketClass, NSSelectorFromString(@"acceptOnAddress:port:error:"), method_getImplementation(_acceptOnInterface ), method_getTypeEncoding(_acceptOnInterface)); }
9.没有了,散会