I've been playing with blocks and encountered a weird behavior. This is the interface/implementation, which just holds a block with the ability to execute it:
@interface TestClass : NSObject {
#if NS_BLOCKS_AVAILABLE
void (^blk)(void);
#endif
}
- (id)initWithBlock:(void (^)(void))block;
- (void)exec;
@end
@implementation TestClass
#if NS_BLOCKS_AVAILABLE
- (id)initWithBlock:(void (^)(void))block {
if ((self = [super init])) {
blk = Block_copy(block);
}
return self;
}
- (void)exec {
if (blk) blk();
}
- (void)dealloc {
Block_release(blk);
[super dealloc];
}
#endif
@end
While a regular instantiation and passing a regular block works:
TestClass *test = [[TestClass alloc] initWithBlock:^{
NSLog(@"TestClass");
}];
[test exec];
[test release];
Using a block with reference to the object which is being created doesn't:
TestClass *test1 = [[TestClass alloc] initWithBlock:^{
NSLog(@"TestClass %@", test1);
}];
[test1 exec];
[test1 release];
Error is EXC_BAD_ACCESS, stack trace on Block_copy(block); Debugger on: 0x000023b2 <+0050> add $0x18,%esp
I kept playing around, and moved the allocation code above the initialization, it worked:
TestClass *test2 = [TestClass alloc];
test2 = [test2 initWithBlock:^{
NSLog(@"TestClass %@", test2);
}];
[test2 exec];
[test2 release];
And combining both snippets works too:
TestClass *test1 = [[TestClass alloc] initWithBlock:^{
NSLog(@"TestClass %@", test1);
}];
[test1 exec];
[test1 release];
TestClass *test2 = [TestClass alloc];
test2 = [test2 initWithBlock:^{
NSLog(@"TestClass %@", test2);
}];
[test2 exec];
[test2 release];
What's going on here?
In an assignment expression, the rvalue is evaluated before being assigned to the lvalue.
This means that in:
the following sequence of operations is performed. Edit: as pointed out by Jonathan Grynspan, there’s no defined order for steps 1 and 2 so it could be the case that step 2 is executed before step 1.
+alloc
toTestClass
test1
, which hasn’t been initialised yet.test1
contains an arbitrary memory address.-initWithBlock:
to the object created in step 1.test1
.Note that
test1
points to a valid object only after step 4.In:
the sequence is:
+alloc
toTestClass
test2
, which now points to aTestClass
object.test2
, which points to theTestClass
object per step 2.-initWithBlock:
totest2
, which was correctly assigned in step 2.test2
.