I have ‘come of age’ in the ARC era. Although I have done some manual reference counting and think I understand it, I took a test on my Obj-C skills lately and ran across this puzzle. My answer was the simple, obvious and incorrect choice.
To understand it better I created this test project and picked it apart. Try the puzzle yourself and then read my thoughts below. If you see any errors in my logic, please let me know!
The Code
// Test: Once this method is executed, what will be printed to the console?
NSObject *object = [NSObject new];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"A %u", [object retainCount]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"B %u", [object retainCount]);
});
NSLog(@"C %u", [object retainCount]);
});
NSLog(@"D %u", [object retainCount]);
The Choices
|
|
|
|
The initial mistake I made was not thinking about when the blocks would be executed. Because dispatch_async() was putting the block on the main queue, I read this code as a straight procedural list of commands, and came up with answer #2. This was completely wrong. I even missed the first clue that the order of the 4 logged lines was just as important as the retain count being printed.
After creating the test project and seeing for myself what the correct answer was I was flumexed. But, quickly I reminded myself that dispatching a block, even on the same queue, will delay the execution of that code until the next run loop. The 2nd nested block doesn’t even capture object (increasing the retain count) until after the first executed NSLog prints out the retain count for ‘D’.
With the answer #4 clearly true, I could now re-read the code to understand it. I added comments with my thoughts:
// Test: Once this method is executed, what will be printed to the console?
// Both the printing/execution order and retain count are important
NSObject *object = [NSObject new]; // Retain Count: 1
dispatch_async(dispatch_get_main_queue(), ^{
// Captured inside a block, increacing retain count to 2
NSLog(@"A %u", [object retainCount]); // 2
dispatch_async(dispatch_get_main_queue(), ^{
// Captured inside another block, increasing retain count to 3?
// but, NO! It is only 2 still
// Is this because the blocks are async?
// The first block has already completed by the time this
// block executes on the next loop?
//
// The answer is that the enclosing block executes,
// the current block captures 'object' (retain count now 3)
// places this block on the queue, then completes, popping off the stack &
// decrementing the retain count back to 2.
NSLog(@"B %u", [object retainCount]); // 2
});
// The 2nd block has been dispatched, but not yet executed
// initial-retain-count + block-capture + block-capture = 3
NSLog(@"C %u", [object retainCount]); // 3
});
// The 1st block has captured 'object', increasing retain count
// The 2nd block has not yet captured 'object' because the 1st block has not
// executed yet.
// initial-retain-count + block-capture = 2
NSLog(@"D %u", [object retainCount]); // 2