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?
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.
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.
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.
__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).
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With