【问题标题】:Does -[NSInvocation retainArguments] copy blocks?-[NSInvocation retainArguments] 是否复制块?
【发布时间】:2013-04-18 00:52:29
【问题描述】:

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

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

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

【问题讨论】:

  • 感谢您保持更新!

标签: objective-c objective-c-blocks retain nsinvocation


【解决方案1】:

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

保留参数

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

  • (void)retainArguments

讨论

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

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

【讨论】:

  • 有趣。文档已更改:web.archive.org/web/20120826185131/http://developer.apple.com/… 但它没有记录行为是否以及何时更改。一年前我在 iOS 6 中尝试时,retainArguments 肯定不会复制块。
  • 我刚刚测试了一下,在iOS 7中是复制的,在iOS 6.1中没有,所以改了。
  • 他们应该将更改记录为 ios 7 更新,而不是让它看起来一直都是这样......愚蠢的苹果,技巧是为孩子们准备的!
【解决方案2】:

没有。

如果答案是肯定的,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 中的参数。 这将打破许多已经做出的假设。 (即来自NSInvocation 的参数应该与用于创建NSInvocation 的参数相同)


更新

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

@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__,但这就是复制块的目的:将其移动到堆以延长其生命周期。
  • 好点。但是,-retainArguments 也会复制 C 字符串。并且复制 C 字符串也会改变参数。
  • @CodaFi 我的意思是block != [block copy] 所以如果被复制可能会导致问题
  • @user102008 我从来不知道...你能给我参考吗?有时间我会试试的
  • @xlc:是的,retainArguments 方法的文档。 developer.apple.com/library/mac/documentation/Cocoa/Reference/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-10
  • 2011-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多