Obj-C __block变量保留行为

don*_*ain 8 memory-management objective-c objective-c-blocks

当我尝试__block从修改它的块外部访问(块可变)变量时,我遇到了一个奇怪的问题.这是一个非常好的玩具示例,我只是为了更好地理解块一般,但是目前我有一个控制器使用这个方法创建一个字符串,其内容NSDictionary使用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;
}
Run Code Online (Sandbox Code Playgroud)

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

Value    Key
"Queen"  "card"
"Hearts" "suit"
"10"     "value"
Run Code Online (Sandbox Code Playgroud)

所述content变量块内的正确修饰和我与每次迭代的输出:

......内容:

Object:Queen for key:card
Run Code Online (Sandbox Code Playgroud)

......内容:

Object:Queen for key:card
Object:Hearts for key:suit
Run Code Online (Sandbox Code Playgroud)

......内容:

Object:Queen for key:card
Object:Hearts for key:suit
Object:10 for key:value
Run Code Online (Sandbox Code Playgroud)

一旦代码走出块,但访问该content字符串会抛出一次EXC_BAD_ACCESS并且在一次运行时它似乎打印了一些垃圾内存(无法重现)......

是什么导致这个变量提前解除分配?我的印象是,给它一个__block定义意味着它在块中使用时保留并在块退出时释放 - 但变量被保留并自动释放以通过字符串文字开始,所以我期待它在此方法最早退出之后才能解除分配.

Jer*_*man 15

这是你的问题:

content = [content stringByAppendingString:contentToAppend];
Run Code Online (Sandbox Code Playgroud)

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

你应该做的是使用a NSMutableString并直接附加contentToAppend到mutable字符串.例如:

- (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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,__block不再需要,因为您没有分配到content块中的任何位置.


Jon*_*pan 5

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