Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assigning objects to variable outside a block

The following code crashes, since the contents of sentence go away when the final block exits.

#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;
}

What is the correct / idiomatic way to make this work?

like image 377
Jonathan Watmough Avatar asked Aug 24 '11 01:08

Jonathan Watmough


People also ask

How do you use a variable outside block?

You use blocks to limit the scope of a variable. The main reason of a block is that x is not accessible outside the block. If you want to access a variable outside a bock, declare it outside the block.

Can I use a variable outside of function?

You can't access variables declared inside a function from outside a function. The variable belongs to the function's scope only, not the global scope.

How can I access a variable outside a promise then method?

You need to return the promise from the Spotify. getCurrentUser() , and then when you return names within that it is returned by the outer function.

What does __ block do?

__block is a storage qualifier that can be used in two ways: Marks that a variable lives in a storage that is shared between the lexical scope of the original variable and any blocks declared within that scope. And clang will generate a struct to represent this variable, and use this struct by reference(not by value).


3 Answers

Ok, I went away and played with Xcode for a bit, and here's a model of what's going on, that seems to match what I'm seeing.

The block I used above isn't doing anything special, but the enumerateObjectsUsingBlock code appears to have its own NSAutoreleasePool, so that seems to be what was causing dealloc to be called on objects alloc'ed, but autoreleased inside the block.

The following code matches in behavior what I'm seeing above:

#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;
}

If I remove the innerPool object, then the code works as I originally expected, and presumably the NSRunLoop pool will eventually clean up the various NSString objects.

NOTE: This thread is now the number 2 Google result for 'enumerateObjectsUsingBlock autorelease':

Google 'enumerateObjectsUsingBlock+autorelease'

The first result confirms this answer. Thanks all.

like image 194
Jonathan Watmough Avatar answered Oct 21 '22 04:10

Jonathan Watmough


Ok so I'm not 100% sure what's going on there but in the mean time it works if you change

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;

Updated thanks to @nacho4d

like image 30
Paul.s Avatar answered Oct 21 '22 05:10

Paul.s


As you mentioned, I suspect this is crashing when the autorelease pool runs, as it probably does in enumerateObjectsUsingBlock:. This will be annoying to work around if you have a __block variable. You could use an NSMutableString instead, or simply do this, which is cleaner anyway:

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

Alternatively, if you use ARC, the compiler should eliminate the problem for you.

like image 41
jtbandes Avatar answered Oct 21 '22 04:10

jtbandes