NSInvocation是iOS框架中对命令模式的一种OC封装,使用起来非常方便。在方法调用,或者消息转发中都可以使用到。以下是学着别人demo使用时,封装的多参数调用函数:
|
|
示例类如下:
|
|
调用如下:
|
|
以上,解决了使用performSelector调用时只能传递单个参数的尴尬问题。但是,问题来了~~
运行时,会发生crash。try-catch也无法捕获异常,觉得颇为诡异。之后,再打开zombie检测后,发现如下输出:
|
|
原来是返回值被过渡释放导致。
究其原因,可以在NSInvocation的类文档中查到,主要就一句:
This class does not retain the arguments for the contained invocation by default.
即为,本类默认不对参数进行保留操作。
实质上,不仅如此,通过调用getReturnValue:方法并查阅说明可以知道,此方法只是根据给定的指针,创建buffer并将返回值写入其中。并未对其进行内存管理。
现在回到代码。由于performSelector函数中,只是使用了临时变量指向并返回,导致NSInvocation的返回值对象的引用计数实际上是0,即创建完就要被释放。所以,当performSelector函数执行完成后,实际上返回值已经被释放。由于外侧调用者也没有对其进行保留等其他操作,导致调用者的作用域结束后,返回值会再次收到release操作,即发生crash。
那么,如何解决此问题呢?
两种方式:
- 在调用NSInvocation对象的getReturnValue:方法时,不要传入OC对象指针,使用C指针(void *),并在最终桥接转换为OC对象。这种是本人比较推荐的方式,由于此实质上是往buffer内写入内容,使用C指针会更加严谨,而且避免了ARC内存释放导致的crash,返回时桥接转换为对象即可(这种情况也兼容所有数据类型)。
将performSelector的代码修正为:
|
|
- 使用__unsafe_unretained修饰OC指针,避免ARC对返回值进行内存管理。使用__weak同样可以达到效果,不过感觉上有点懵,需要继续深入查看内存管理相关的内容了。。。
|
|
问题解决。。。
测试程序代码:测试NSInvocation调用