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