I am trying to implement a method that executes a task in the background and then calls a block on the main thread:
+ (void)migrateStoreWithCompletionHandler:(MigrationControllerCompletion)completion
{
MigrationController *controller = [[MigrationController alloc] initWithCompletionBlock:completion];
[controller migrateStore];
}
This is the -initWithCompletionBlock:
method:
- (id)initWithCompletionBlock:(MigrationControllerCompletion)completion
{
self = [super init];
if (self)
{
_completion = [completion copy];
}
return self;
}
The background work happens in -migrateStore
. The problem is that ARC releases controller
after [controller migrateStore]
. Because controller
is the object that holds onto the block, I am not able to ever call it. Does anyone have any suggestions on how to work around this issue?
You could consider having the class containing
+migrateStoreWithCompletionHandler:
keep track of all the generated MigrationController instances in a private array or similar. That would keepcontroller
from being deallocated too early, and allow you to call your completion block.You'd need to find a way to release them later, however, to avoid slowly growing your memory usage as you make MigrationControllers. You might consider posting a notification from the controller at the end of
-migrateStore
after you call the completion block, then having your factory class listen for that notification and deallocate the appropriate controller. (You could also get similar behavior with a delegation pattern, if you were so inclined.)Use the dreaded "retain cycle" in your favor.
Basically, the controller object is strongly referencing its _completion iVar, so if you make that block strongly reference
self
then you have a retain cycle, which keeps the object alive for as long as you want.The pragmas temporarily silence the retain cycle warning.
You can then manually break the retain cycle by setting the completion block to nil after calling the handler.
Then, in your code, when you want to call the completion handler, you don't have to pass
self
because it's already there...This is the only true limitation of ARC that I've dealt with so far. There are easy ways to work around it, though:
1) You can create a static variable for the
MigrationController
object and set it tonil
when the completion block is invoked.2) Only do this when you really know what you're doing!
Use
CFRetain()
andCFRelease()
directly: