-[NSInvocation retainArguments]方法是否会复制代码块(blocks)?

7
NSInvocation-retainArguments 方法在你不立即运行 NSInvocation ,而是稍后执行时非常有用;它会保留对象参数,以便在此期间它们仍然有效。

众所周知,块参数应该被复制而不是保留。我的问题是,当参数为块类型时,-retainArguments 是否知道复制而不是保留?文档没有表明它这样做,但这似乎是一件容易而且明智的事情。

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


感谢您的更新! - matt
2个回答

4

这当然是应该的(虽然我自己没有测试过)。根据文档

retainArguments

如果接收器尚未执行此操作,则保留接收器的目标和所有对象参数,并复制其所有C字符串参数和block。

  • (void)retainArguments

讨论

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

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

之前才能触发计时器。


有趣。文档已经改变:https://web.archive.org/web/20120826185131/http://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSInvocation_Class/Reference/Reference.html#//apple_ref/occ/instm/NSInvocation/retainArguments 但它没有记录行为何时发生了变化。当我在一年前尝试在iOS 6中使用retainArguments时,块绝对没有被复制。 - user102008
1
我刚刚测试了一下,在iOS 7中可以复制,但在iOS 6.1中无法复制,所以它已经改变了。 - user102008
他们应该将更改记录为iOS 7更新,而不是让它看起来像一直都是这样...愚蠢的苹果,把把戏留给孩子们吧! - Mr. T

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,这不应该发生,因为这意味着调用retainArguments可能会改变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在某个时候已经为您复制了它。

复制一个块不会改变类型系统关心的内容,即块的签名。当然,它可能从__NSStackBlock__变成__NSHeapBlock__,但这就是复制块的目的:将其移动到堆上以延长其生命周期。 - CodaFi
好观点。然而,-retainArguments也会复制C字符串。而复制C字符串也会改变参数。 - user102008
@CodaFi 我的观点是 block != [block copy],所以如果被复制可能会导致问题。 - Bryan Chen
“输出,ARC已禁用(并崩溃)”,你一定在运行除了你展示的代码之外的其他代码,因为你展示的代码不应该会崩溃。该块在test1结束之前都在作用域内,包括NSInvocation被调用的地方。 - user102008
@user102008 这是一段代码,我正在将其作为单元测试运行。它崩溃了,因为 __NSStackBlock__ 被添加到自动释放池中,所以块的引用超出了作用域。 - Bryan Chen
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接