0%

iOS getReturnValue 方法导致的崩溃

最近程序在调用 NSInvokation 来获取函数返回值的时候,如果返回值是对象的话就会引起 EXC_BAD_ACCESS 崩溃。

对付 EXC_BAD_ACCESS 问题一般需要在 Edit Scheme... -> Diagnostics 中开启 Zombie Objects 选项,于是在崩溃时调实窗口打印出来了:

1
*** -[CFString release]: message sent to deallocated instance 0x2813f0200

是反射出来的返回值被再次释放了。

获取返回值的代码如下:

1
2
3
id retval;
[invocation invoke];
[invocation getReturnValue:&retval];

这里的 retval 会被释放两次,导致崩溃。

原因及解决方案

网上的解释是在 ARC 下才会导致这种错误。getReturnValue 这个方法不会对内存进行管理,id retval 这句会被认为 retval__strong 类型的,所以在离开这个范围内会调用一次 release,结果就导致了再次释放而崩溃。

所以需要在这里声明一下 retval 不是 __strong 类型的,也就是说这里不持有这个对象,离开范围也不需要对他进行释放。有几种解决方案:

1)

1
2
3
id __unsafe_unretained retVal; // 在这里声明不持有retVal,使用 __weak 或 __autoreleasing 都可以
[invocation invoke];
[invocation getReturnValue:&retval];

2)

1
2
3
4
void *tmpVal;
[invocation invoke];
[invocation getReturnValue:&tmpVal];
id retval = (__bridge id)tmpVal; // 在这里将tmpVal桥接为obj-c对象并进行持有

更推荐第二种写法,添加了从 void*id 的桥接,更加严谨一些。

其他

  • 在调试过程中,发现如果返回的字符串为 __NSCFConstantString 或者 NSTaggedPointerString 时,并不会崩溃,这又涉及到了类簇的概念。

参考链接