Keep object alive until a background task finishes

陌路散爱 提交于 2019-11-30 22:34:39

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.

- (id)initWithCompletionBlock:(MigrationControllerCompletion)completion
{
    self = [super init];

    if (self)
    {
        _completion = ^(BOOL success) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
            if (completion) completion(self, success);
#pragma clang diagnostic pop
            _completion = nil;
        };
    }

    return self;
}

Then, in your code, when you want to call the completion handler, you don't have to pass self because it's already there...

_completion(success);

You could consider having the class containing +migrateStoreWithCompletionHandler: keep track of all the generated MigrationController instances in a private array or similar. That would keep controller 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.)

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 to nil when the completion block is invoked.

2) Only do this when you really know what you're doing!
Use CFRetain() and CFRelease() directly:

+ (void)migrateStoreWithCompletionHandler:(MigrationControllerCompletion)completion
{
    MigrationController *controller = [[MigrationController alloc] initWithCompletionBlock:^(MigrationController *migrationController, BOOL finished, ...) {
        if (completion != nil)
            completion(migrationController, finished, ...);

         CFRelease((__bridge void *)migrationController);
    }];

    [controller migrateStore];

    // Make 'controller' live until the completion block is invoked
    CFRetain((__bridge void *)controller);
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!