Obj-C __block变量保留行为

时间:2011-06-15 19:11:09

标签: objective-c memory-management objective-c-blocks

我在尝试从修改它的块外部访问__block(块可变)变量时遇到一个奇怪的问题。这是一个非常好的玩具示例,我只是为了更好地理解块,但目前我有一个控制器使用此方法创建一个字符串,其内容为NSDictionary,使用{{1 } NSDictionary

enumerateKeysAndObjectsUsingBlock:

当我使用包含内容的字典运行此方法时:

- (NSString*) contentsOfDictionary:(NSDictionary*)dictionary
{
    __block NSString *content = @"";

    [dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
        NSString* contentToAppend = [NSString stringWithFormat:@"Object:%@ for key:%@\n", obj, key];
        content = [content stringByAppendingString:contentToAppend];
        NSLog(@"Content in block:\n%@", content);
    }];

    NSLog(@"Content out of block:\n%@", content);

    return content;
}

在块中正确修改了Value Key "Queen" "card" "Hearts" "suit" "10" "value" 变量,每次迭代都得到这个输出:

......阻止内容:

content

......阻止内容:

Object:Queen for key:card

......阻止内容:

Object:Queen for key:card
Object:Hearts for key:suit

一旦代码跳出块,访问Object:Queen for key:card Object:Hearts for key:suit Object:10 for key:value 字符串会抛出content并且在一次运行时它似乎打印了一些垃圾内存(无法重现)。 ..

是什么导致此变量提前解除分配?我的印象是,给它一个EXC_BAD_ACCESS定义意味着它在一个块中使用时保留并在块退出时释放 - 但是该变量被保留并自动释放以通过作为一个字符串开始字面意思所以我希望它不会在这个方法最早退出之后被解除分配。

2 个答案:

答案 0 :(得分:15)

这是你的问题:

content = [content stringByAppendingString:contentToAppend];

-stringByAppendingString:返回一个新的自动释放对象。该对象的地址存储在content中。每个都经历这个(隐式)循环 - 也就是说,每次调用提供的块 - 正在创建一个全新的对象,然后将该新对象的地址分配给content。这些对象都不会超过其包含的自动释放池。

您应该做的是使用NSMutableString并直接将contentToAppend附加到可变字符串。例如:

- (NSString*) contentsOfDictionary:(NSDictionary*)dictionary
{
    NSMutableString *content = [NSMutableString string];
    [dictionary enumerateKeysAndObjectsUsingBlock:
    ^(id key, id obj, BOOL *stop){
        NSString* contentToAppend = [NSString stringWithFormat:
            @"Object:%@ for key:%@\n", obj, key];
        [content appendString:contentToAppend];

        NSLog(@"Content in block:\n%@", content);
    }];

    NSLog(@"Content out of block:\n%@", content);
    return content;
}

请注意,不再需要__block,因为您未在块中的任何位置分配content

答案 1 :(得分:5)

在内部,-enumerateKeysAndObjectsUsingBlock:正在使用自动释放池。 __block范围内的对象不会在块的生命周期结束时保留,因此最终会在块的范围内创建一个对象,然后在字典的自动释放池耗尽时释放该对象,这一切都在您尝试之前发生打印content的值。