说说NSObject的 performSelector 系列函数
记录调试这个bug的过程
说说遇到的bug
说之前先说说遇到的bug,公司项目,Target最低终于支持iOS8以上,web页以前使用的是UIWebView,iOS8之后,苹果推出了WKWebView,新写的功能需要使用到WKWebView,在之前使用UIWebView的时候,H5为了兼容安卓和iOS,会做平台类型判断,这次团队决定使用iOS,安卓和H5通用的一套javaScript和原生的APP交互SDK,最终挑选了DSBridge,文档上说是一款三端易用的现代跨平台 Javascript bridge, 通过它,你可以在Javascript和原生之间同步或异步的调用彼此的函数,
联调过后,一切完美,同步,异步方式都可以调用,但是最终打包给测试同学的时候,崩溃了,经过一番检查,发现在同步调用情况下崩溃,并且在DEBUG模式下没有崩溃,在RElease模式下崩溃,javascript回调客户端注册的方法的时候直接 EXC_BAD_ACCESS 经过一番检查,在同步或者异步方式回调的时候使用了 performSelector这个方法,先看看DSBridge部分源代码:
-(NSString *)call:(NSString*) method :(NSString*) argStr {
...只贴重要部分
// 异步调用
SuppressPerformSelectorLeakWarning(
[JavascriptInterfaceObject performSelector:selasyn withObject:arg withObject:completionHandler];
);
// 同步调用
id ret;
SuppressPerformSelectorLeakWarning(
ret=[JavascriptInterfaceObject performSelector:sel withObject:arg];
);
}
DSBridge这样设计,会因为异步调用的时候客户端通过completionHandler直接回调web端,而同步的时候得到一个返回值,在执行js,通过js回调给web端,其中JavascriptInterfaceObject是客户端注册的调用对象,可以看到使用了performSelector,同事确定release模式下崩溃,我看到EXC_BAD_ACCESS 首先想到的是某个对象被释放掉了,但是又给这个对象发了消息,但是日志都能正确,一点一点定位位置,直到无意中将同步调用方法的接收参数ret 去掉,才发现没有崩溃了,确定本次崩溃和performSelector有关。
仔细想想performSelector函数到底返回什么,说不清楚,NSObject.h中没有注释,文档中写的返回值是void,函数定义的返回的是一个id类型。于是写一个Demo测试,在debug模式下,给有个对象通过performSelector发送2个消息,这2个方法分别是方法A和方法B,方法A有返回值,方法B返回值为void,得到结果方法A的返回值是调用函数的返回值,调用方法B 直接崩溃。终于确定问题。
为什么会崩溃呢,和调用方法是否有返回值有关,以前也知道performSelector编译器不会对对象,方法,进行检验,会有内存泄露的可能产生,performSelector会把编译时做的事情放到了运行时期,点击这里去看一些performSelector的详细介绍,因为不知道即将调用的selector是否有返回值,只有到了运行期才去检测,调用了返回值为Void的一些函数,相当于直接是 id obj = void,当然是OC语法不允许的,直接崩溃是必然的。
为什么我们自己的项目在Debug模式下正常呢,于是在项目中写了局代码测试,发现会返回一个nil,猜测可能在哪个地方在进行了方法交换,查了代码,没有查到,可能是在某个SDK中,如图:
总结
本次调试bug慢比较慢的原因:
- 对performSelector只知道怎么去用,细节部分没想过,比如函数的返回值,将编译时期可以报出的错误放到了运行时期,等等
- DSBridge源码看过,但是看的不仔细,花了好一会才了解整体调用过程。
- 查bug的时候会去猜测,可能是哪个原因,然后通过注释代码来找问题(知识掌握的不牢固)
- 在使用第三方库的时候一定要读懂核心部分源码,在遇到问题的时候才能够快速定位问题。
归根结底都是对知识掌握的不牢固,踩的坑还不够多。
所有的bug都有他们必现的原因,只是我没有找到原因而已。
我以为的不一定是我以为的
原本以为已经解决了这个问题,但是自己写Demo的时候是在模拟器上测试,公司项目是在真机上测试,都在真机上运行都没有崩溃,写的demo也返回了第一个固定参数,公司项目传入参数{"action":"customservice"} 就直接崩溃 在真机上和在模拟器上有什么不同吗?模拟器和真机都是iOS 11.3系统,自己公司的项目为什么会发生崩溃呢?