关于ARC与NSInvocation的一个问题

涉及二级指针/void传参的函数调用要特别留意引用计数管理的问题,因为二级指针/void传参是不涉及自动引用计数管理的,所以需要自己在合适的平衡引用计数关里。

一、问题

给定如下代码

@implementation TestHelper
- (NSString*)stringObjects
{
    NSMutableString *s = [NSMutableString string];
    return s;
}

- (void)test_invocation
{
    SEL s = @selector(stringObjects);
    NSMethodSignature*signature = [self methodSignatureForSelector:s];
    NSInvocation*invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = self;
    invocation.selector = s;
    [invocation invoke];
    
    id obj = nil;
    [invocation getReturnValue:&obj];
    
}
@end

执行完test_invocation后会发生什么现象呢?

结果会crash,crash的原因是over-release;

二、分析

这里涉及了NSInvocation getReturnvalue的一个问题

Declaration

  • (void)getReturnValue:(void *)retLoc;
    Parameters

buffer
An untyped buffer into which the receiver copies its return value. It should be large enough to accommodate the value. See the discussion below for more information about buffer.

invocation执行后的函数返回值会进行memcpy给buffer;
这里就隐含了这函数是一个非ARC的调用;

如果传入的是一个ARC内存管理的指针buffer,并且invocation的函数返回值又是一个ARC内存管理的对象,那这里可能会有ARC处理错误的问题;

对于函数stringObjects其返回的对象是一个autorelease的对象,因此该对象会被加入当前autoreleasepool并且再稍后被释放;

而对于test_invocation函数其声明了一个obj对象,这个对象隐含了其实一个__strong修饰的对象,因此ARC会自动给其生成release代码;但是[NSInvocation getReturnValue:]并不是ARC规范的函数,因此这里不会有自动ARC的代码插入,从而导致obj的值指向了一个对象,但是指向时没有触发ARC生成retain的操作;但由于obj是一个__strong修饰的指针,所以其会被ARC自动加上release操作;

最终就是:stringObjects返回了一个autorelease的对象,接着test_invocation又像这个对象发了一次release消息,再接着当前代码结束后autoreleasepool又尝试pop并release该对象,造成同一个对象被release了2次,但其引用计数最多时仅为1次;因此第二次就出现内存访问错误了;

三、结语

我们还是要注意各个函数的传参说明,不要踩了坑了;
同时也要牢记ARC的准则;另外对于涉及传参二级指针或者void*的都要留意引用计数的问题.


顺带,如果只允许修改以上的stringObjects方法,请问怎么修改能保证不挂呢?理论上至少有5种方式/原理。

猜你喜欢

转载自blog.csdn.net/rhythmkay/article/details/86539524