There’s an illuminating bug in Friday’s Grand Central Dispatch (GCD) directory monitoring demo which serves as a cautionary tale for anyone interested in using GCD (or blocks in general). It’s sort of a circular reference problem, triggered by the fact that blocks automatically retain certain things when they’re created. This can blindside the iOS developer since, on that platform, memory management is a largely manual process.
The Bug
The problem occurs when we set the event handler for our dispatch source:
// Set the block to be submitted in response to an event
dispatch_source_set_event_handler(_src, ^{[self updateFns];});
The ^{[self updateFns];}
block will retain the self
object, and the block itself will be retained by the GCD system. This means that as long as monitoring is running, the RootViewController
will never be deallocated. If the RootViewController
were popped off its navigation controller’s stack while monitoring was running, it would never be deallocated, due to the reference ultimately retained by GCD, and the fact that there would then be no way for the user to terminate monitoring, even if he knew this were necessary. (I previously described this as “sort of” a circular reference problem, because while the controller doesn’t retain the block directly, it instructs GCD to retain it.)
This won’t be a problem in this particular demo because there’s no way to dismiss the RootViewController
. (It manages the demo’s only view.) In the general case, however, this is a problem.
The Fix
The key to the fix is the __block
storage type. Variables of this type are not automatically retained by blocks which reference them, so code like this will solve our problem:
// Set the block to be submitted in response to an event
__block id unretained_self = self;
dispatch_source_set_event_handler(_src, ^{[unretained_self updateFns];});
There’s an obvious hazard here, in that we need some mechanism to ensure that unretained_self
isn’t deallocated out from underneath GCD; we do this by canceling the dispatch source in dealloc
, thus ensuring that the block will never execute after the object that created it is deallocated. Note that this logic only holds up because the dealloc
call is made on the same (main) thread as that on which the block is invoked.
You can download the patched demo here.