【问题标题】:Keep object alive until a background task finishes保持对象活动直到后台任务完成
【发布时间】:2012-08-31 16:06:06
【问题描述】:

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

+ (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 migrateStore] 之后发布了controller。因为controller 是持有该块的对象,所以我无法调用它。有人对如何解决此问题有任何建议吗?

【问题讨论】:

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


    【解决方案1】:

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

    基本上,控制器对象强引用它的_completion iVar,所以如果你使该块强引用self,那么你就有一个保留周期,它可以让对象保持活动状态,只要你想要。

    编译指示暂时使保留周期警告静音。

    然后,您可以通过在调用处理程序后将完成块设置为 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);
    

    【讨论】:

      【解决方案2】:

      您可以考虑让包含+migrateStoreWithCompletionHandler: 的类在私有数组或类似数组中跟踪所有生成的 MigrationController 实例。这样可以防止controller 过早被释放,并允许您调用完成块。

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

      【讨论】:

      • 感谢您的建议。 +migrateStoreWithCompletionHandler: 但是在发布后只会被调用一次。
      【解决方案3】:

      这是迄今为止我处理的 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);
      }
      

      【讨论】:

      • 使用 CF 调用有效,谢谢。不过感觉有点hacky :)
      • 这违反了内存管理准则并且是错误的。你永远不需要释放你没有保留在同一个函数中的东西。
      • 你错了。请仅在您真正了解内存管理时发表评论。
      • @FabianKreiser 他说对了一半:这确实违反了内存管理准则。他错了一半,因为它们只是指导方针。但我仍然认为这是一种极其危险的方法,尤其是因为它完全破坏了 ARC 提供的所有优势。
      猜你喜欢
      • 1970-01-01
      • 2016-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-19
      • 1970-01-01
      • 1970-01-01
      • 2011-01-03
      相关资源
      最近更新 更多