保持对象活动直到后台任务完成

时间:2012-08-31 16:06:06

标签: objective-c ios automatic-ref-counting objective-c-blocks

我正在尝试实现一个在后台执行任务然后在主线程上调用一个块的方法:

+ (void)migrateStoreWithCompletionHandler:(MigrationControllerCompletion)completion
{
    MigrationController *controller = [[MigrationController alloc] initWithCompletionBlock:completion];

    [controller migrateStore];
}

这是-initWithCompletionBlock:方法:

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

    if (self)
    {
        _completion = [completion copy];
    }

    return self;
}

后台工作在-migrateStore进行。问题是ARC在controller之后发布[controller migrateStore]。因为controller是保留在块上的对象,所以我无法调用它。有没有人对如何解决这个问题有任何建议?

3 个答案:

答案 0 :(得分:4)

使用可怕的“保留周期”对你有利。

基本上,控制器对象强烈引用其_completion iVar,因此如果你强烈引用该块self,那么你就有了一个保留周期,它可以让对象保持活动状态。

pragma暂时使保留周期警告静音。

然后,您可以通过在调用处理程序后将完成块设置为nil来手动中断保留周期。

- (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;
}

然后,在您的代码中,当您想要调用完成处理程序时,您不必通过self,因为它已经存在...

_completion(success);

答案 1 :(得分:2)

您可以考虑让包含+migrateStoreWithCompletionHandler:的类跟踪私有数组或类似的所有生成的MigrationController实例。这样可以使controller过早解除分配,并允许您调用完成块。

然而,您需要找到一种方法来释放它们,以避免在创建MigrationControllers时缓慢增加内存使用量。您可以考虑在调用完成块后在-migrateStore末尾从控制器发布通知,然后让您的工厂类侦听该通知并取消分配相应的控制器。 (如果您愿意,也可以使用委托模式获得类似的行为。)

答案 2 :(得分:1)

这是我迄今为止处理的唯一真正的ARC限制。但是,有很简单的方法可以解决这个问题:

1)您可以为MigrationController对象创建静态变量,并在调用完成块时将其设置为nil

2)只有当真的知道你在做什么时才这样做
直接使用CFRetain()CFRelease()

+ (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);
}