2011-06-15 67 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