2011-06-15 68 views
8

尝试从其修改块的外部访问__block(块可变)变量时,遇到一个奇怪的问题。这是我使用只是为了更好地了解一般块的非常玩具的例子,但现在我有这个方法的控制器创建与使用NSDictionaryenumerateKeysAndObjectsUsingBlock:Obj-C __block变量保留行为

NSDictionary的内容的字符串
- (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,并且在一次运行的情况下它似乎已经打印了一些垃圾内存(无法重现)...

什么导致这个变量被提前释放?我的印象是,给它一个__block定义意味着它在块中使用时被保留,并在块退出时被释放 - 但是该变量被保留和自动释放以便作为字符串文字开始,所以我期望在这种方法最早退出之前不会被处理。

回答

15

这是你的问题:

content = [content stringByAppendingString:contentToAppend]; 

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

您应该做的是使用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

+0

没错。 – donalbain 2011-06-15 21:29:00

5

在内部,-enumerateKeysAndObjectsUsingBlock:正在使用自动释放池。 __block作用域对象不会保留在块的生命周期末尾,因此最终会在块的作用域中创建一个对象,然后在字典的自动释放池耗尽时释放该对象,这些操作都是在您尝试打印content

+0

如何防止字典的自动释放池被耗尽?我在做什么有什么根本性的错误? – donalbain 2011-06-15 19:26:00

+0

您不能重写池,但可以在枚举中保留对象(确保在循环时不泄漏对象!),然后在完成后自动释放或在循环外释放它。 – 2011-06-15 19:53:46

+0

我看到,隐式自动发布的stringByAppendingString可以保留并手动释放以避免该问题,但我猜MutableString使这更容易。我有一种感觉,这不会是我最后一次内存管理相关块的问题,尽管:( – donalbain 2011-06-15 21:30:25