7

NSInvocation-retainArguments方法在您不NSInvocation立即运行但稍后执行时很有用;它保留对象参数,因此它们在此期间保持有效。

众所周知,块参数应该被复制而不是保留。我的问题是,-retainArguments当它是块类型时,是否知道复制而不是保留参数?文档并未表明它确实如此,但这似乎是一件简单而明智的事情。

更新: iOS 7 中的行为似乎发生了变化。我刚刚对此进行了测试,在 iOS 6.1 及之前的版本中,-retainArguments没有复制块类型的参数。在 iOS 7 及更高版本中,-retainArguments不会复制块类型的参数。的文档-retainArguments已更新为说它复制了块,但没有说明行为何时改变(这对于支持旧操作系统的人来说真的很危险)。

4

2 回答 2

4

它当然应该(尽管我自己没有测试过)。根据文档

保留参数

如果接收者尚未这样做,则保留接收者的目标和所有对象参数,并复制其所有 C 字符串参数和块。

  • (void)retainArguments

讨论

在调用此方法之前,argumentsRetained 返回 NO;之后,它返回 YES。

为了提高效率,新创建的 NSInvocation 对象不保留或复制它们的参数,也不保留它们的目标、复制 C 字符串或复制任何关联的块。如果您打算缓存它,您应该指示 NSInvocation 对象保留其参数,因为否则可能会在调用调用之前释放参数。NSTimer 对象总是指示它们的调用保留它们的参数,例如,因为在计时器触发之前通常会有延迟。

于 2014-05-22T17:44:29.263 回答
1

不。

图像如果答案是肯定的,在哪里NSInvocation聪明到可以复制块,它应该做这样的事情:

for (/*every arguments*/) {
    if (/*arg is object. i.e. @encode(arg) is '@'*/) {
        if ([arg isKindOfClss:[NSBlock class]]) {
            arg = [arg copy]; // copy block
        } else {
            [arg retain];
        }
    }
}

问题是arg在复制块时被修改,这不应该发生,因为这意味着 callretainArguments可能会更改NSInvocation.这将打破许多已经做出的假设。(即参数 get fromNSInvocation应该与用于创建的参数相同NSInvocation


更新

刚刚做了测试以确认答案是否定的,但我之前的观点是不正确的......

@interface Test : NSObject

@end

@implementation Test

- (void)testMethodWithBlock:(void (^)(void))block obj:(id)obj cstr:(const char *)cstr {
    NSLog(@"%p %p %p %@", block, obj, cstr, [block class]);
}

@end

@implementation testTests

- (void)test1 {
    __block int dummy;
    Test *t = [[Test alloc] init];
    NSMethodSignature *ms = [t methodSignatureForSelector:@selector(testMethodWithBlock:obj:cstr:)];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:ms];
    void (^block)(void) = ^ {
        dummy++;    // stop this become global block
    };
    id obj = @"object";
    char *cstr = malloc(5);
    strcpy(cstr, "cstr");


    NSLog(@"%@", [ms debugDescription]);

    NSLog(@"%p %p %p %@", block, obj, cstr, [block class]);

    [invocation setSelector:@selector(testMethodWithBlock:obj:cstr:)];
    [invocation setArgument:&block atIndex:2];
    [invocation setArgument:&obj atIndex:3];
    [invocation setArgument:&cstr atIndex:4];

    [invocation invokeWithTarget:t];

    [invocation retainArguments];

    [invocation invokeWithTarget:t];

    free(cstr);
}

@end

输出,ARC 禁用(并崩溃):

2013-04-18 19:49:27.616 test[94555:c07] 0xbfffe120 0x70d2254 0x7167980 __NSStackBlock__
2013-04-18 19:49:27.617 test[94555:c07] 0xbfffe120 0x70d2254 0x7167980 __NSStackBlock__
2013-04-18 19:49:27.618 test[94555:c07] 0xbfffe120 0x70d2254 0x736a810 __NSStackBlock__

启用 ARC:

2013-04-18 19:51:03.979 test[95323:c07] 0x7101e10 0x70d2268 0x7101aa0 __NSMallocBlock__
2013-04-18 19:51:03.979 test[95323:c07] 0x7101e10 0x70d2268 0x7101aa0 __NSMallocBlock__
2013-04-18 19:51:03.980 test[95323:c07] 0x7101e10 0x70d2268 0xe0c1310 __NSMallocBlock__

如您所见,c 字符串被复制retainArguments但不是块。但是启用 ARC 后,问题应该会消失,因为 ARC 在某个时候为您复制了它。

于 2013-04-18T01:14:25.337 回答