将对象分配给块外的变量

时间:2011-08-24 01:42:18

标签: objective-c objective-c-blocks

以下代码崩溃,因为sentence的内容会在最后一个块退出时消失。

#import <Foundation/Foundation.h>    
int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // simple block test - just iterate over some items and 
    // add them to a string
    NSArray *items = [NSArray arrayWithObjects:@"why ", @"must ", @"this ",nil];
    __block NSString *sentence = @"";   
    [items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
    {
        sentence = [sentence stringByAppendingFormat:@"%@",obj];
    }];
    // crash!
    NSLog(@"Sentence is %@",sentence);
    [pool drain];
    return 0;
}

使这项工作的正确/惯用方法是什么?

3 个答案:

答案 0 :(得分:4)

好吧,我离开了Xcode玩了一会儿,这是一个正在发生的事情的模型,这似乎与我所看到的相符。

我上面使用的块没有做任何特殊的操作,但enumerateObjectsUsingBlock代码似乎有自己的NSAutoreleasePool,所以这似乎是导致dealloc被调用的原因在对象alloc'ed上,但在块内自动释放。

以下代码与我上面看到的行为匹配:

#import <Foundation/Foundation.h> 
int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // simple block test - just iterate over some items and 
    // add them to a string
    typedef void (^AccArrayBlock)(id obj, int idx, BOOL *stop);
    // items to 'process'
    NSArray *items = [NSArray arrayWithObjects:@"why ", @"must ", @"this ",nil];
    int idx = 0;
    BOOL doStop = NO;
    // make sentence mutable, so we can assign it inside block
    __block NSString *sentence = @"";
    // make a similar block to what we'd pass to enumerate...
    AccArrayBlock myBlock = ^(id obj, int idx, BOOL *stop)
    {
        // returns and assigns an autoreleased string object
        sentence = [sentence stringByAppendingFormat:@"(%d) %@  ",idx,obj];
    };
    // enumerate items and call block
    for (NSString *item in items) {
        // create a pool to clean up any autoreleased objects in loop
        // remove this line, and the sentence will be valid after loop
        NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
        myBlock(item, idx++, &doStop);
        // drain the pool, autorelease objects from block
        [innerPool drain];
        if (doStop) {
            break;
        }
    }
    // faults if we drained the pool
    // Program received signal:  “EXC_BAD_ACCESS”.
    NSLog(@"Sentence is %@",sentence);
    [pool drain];
    return 0;
}

如果我删除了innerPool对象,那么代码就像我原先预期的那样工作,并且假设NSRunLoop池最终会清理各种NSString对象。

注意:此主题现在是'enumerateObjectsUsingBlock autorelease'的2号Google结果:

Google 'enumerateObjectsUsingBlock+autorelease'

第一个结果证实了这个答案。谢谢大家。

答案 1 :(得分:1)

好的,所以我不是百分之百确定那里发生了什么,但是如果你改变的话,它会起作用

NSArray *items = [NSArray arrayWithObjects:@"why ", @"must ", @"this ",nil];
NSMutableString *sentence = [[NSMutableString alloc] init];   
[items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
    [sentence appendFormat:@"%@",obj];
}];

NSLog(@"Sentence is %@",sentence); 

[sentence release]; sentence = nil;

感谢@ nacho4d

答案 2 :(得分:1)

正如您所提到的,我怀疑自动释放池运行时会崩溃,因为它可能在enumerateObjectsUsingBlock:中运行。如果您有一个__block变量,这将很烦人。您可以使用NSMutableString,或者只是执行此操作,无论如何都是这样:

for (id obj in items)
{
     sentence = [sentence stringByAppendingFormat:@"%@",obj];
}

或者,如果您使用ARC,编译器应该为您解决问题。