【问题标题】:How do I ensure a controller is retained until after it completes processing when converting to ARC?在转换为 ARC 时,如何确保控制器在完成处理之前一直保留?
【发布时间】:2011-10-23 20:47:42
【问题描述】:

我在我的应用程序中使用以下模式并正在过渡到 ARC。基本上,一个对象保留一个控制器的实例,并在通过委托协议通知它已完成时释放该控制器。我不使用 iVar/property b/c startProcess 可以调用 N 次来处理 N 件事。

下面的例子:

// start a process in a controller
- (void)startProcess
{
    MyController *controller = [[MyController alloc] init];
    // set the delegate, the delegate is defined as (nonatomic, assign)
    controller.delegate = self;
    [controller start];
}

// when the delegate is notified, release the controller
- (void)myControllerDidFinish:(MyController):controller
{
    // do something with results
    [controller release];
}

当上述实现转换为ARC时,控制器在startProcess结束后不再保留,因此不会进行处理,也永远不会收到委托消息。

问题:将我的项目转换为使用 ARC 时,如何修改上述实现以在不在实例化控制器的对象中创建 iVar 的情况下正常工作? Apple 的文档中有一个类似的示例来过渡到 ARC,但它涉及使用块。我宁愿不要用完成块替换委托协议。

编辑:在代码中添加了关于如何定义委托的注释

编辑:澄清第一段以解释为什么持有控制器的 iVar/属性不起作用

【问题讨论】:

    标签: objective-c ios xcode4.2 automatic-ref-counting


    【解决方案1】:

    为什么不直接创建一个NSMutableArray 实例变量pendingControllers,然后在其中添加您的控制器?由于数组保留了它们的成员,您的代码将如下所示:

    // start a process in a controller
    - (void)startProcess
    {
        MyController *controller = [[MyController alloc] init];
        // set the delegate, the delegate is defined as (nonatomic, assign)
        controller.delegate = self;
        [controller start];
    
        if (pendingControllers == nil) {
            pendingControllers = [[NSMutableArray alloc] init];
        }
        [pendingControllers addObject:controller];
        [controller release];
    }
    
    // when the delegate is notified, release the controller
    - (void)myControllerDidFinish:(MyController):controller
    {
        // do something with results
        [pendingControllers removeObject:controller];
        if ([pendingControllers count] == 0) {
            // if ARC is enabled, remove the call to -release.
            [pendingControllers release], pendingControllers = nil;
        }
    }
    

    这样可以避免问题。完成块正确的答案,它们是 Apple 未来使用的方法,但这种方法现在可以使用。

    【讨论】:

    • 是的,我想到了,但看起来很难看。我完全赞成使用块,这只是一个大项目,我正在尝试转换为 ARC,同时尽可能少地重写代码。我还考虑向控制器添加一个完成块,并在块中定义当前的委托处理程序。
    • 如果您觉得它不那么难看,您可以改用NSMutableSet。无论哪种方式,您都将解决控制器被释放的问题。
    • +1 b/c 这会起作用,但@alex 的答案对我来说效果更好。谢谢。
    【解决方案2】:

    通常,控制器有责任在完成任务时保留自己。如果您的控制器在后台线程上运行任务,那么它应该由NSThread 的实例自动保留。如果使用NSURLConnection 通过网络获取数据,则控制器应保留为delegate

    如果您不执行这样的任务,则可以在执行任务时使用合成循环保留来保留控制器。这可以通过创建一个对象来完成,我称之为ObjectRetainer,它只是具有__strong id 属性。当控制器开始其任务时,它应该有一个 __strong ObjectRetainer 实例变量,该变量被设置为一个新的 ObjectRetainer 以保留控制器。这样,控制器将保留一个 ObjectRetainer,该 ObjectRetainer 正在保留控制器,从而防止其中一个被释放。

    当控制器完成其任务并调用了所有必要的委托方法时,它应该将ObjectRetainer 实例变量设置为nil。这将释放ObjectRetainer,然后释放控制器。

    ObjectRetainer 界面可能如下所示:

    @interface ObjectRetainer : NSObject {
        __strong id object;
    }
    @property (nonatomic, strong) __strong id object;
    @end
    

    您应该在控制器的标头中声明一个 ivar:__strong ObjectRetainer _retainer。然后,在控制器的start方法中:

    - (void)start {
        ...
        _retainer = [[ObjectRetainer alloc] init];
        _retainer.object = self;
    }
    

    控制器完成后,只需将_retainer 设置为 nil:

    - (void)performBackgroundTask {
        ....
        [delegate myControllerDidFinish:self];
        _retainer = nil;
    }
    

    【讨论】:

    • 很好,这很完美!这是一种 hack,但实施起来很简单。我敢肯定,随着时间的推移,我会更改实现以使用块,但现在一切正常。
    【解决方案3】:

    更简单:使控制器成为数据成员...@synthesized 方法会发挥作用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-12-30
      • 1970-01-01
      • 2013-11-21
      • 2018-01-23
      • 2016-09-12
      • 2012-08-04
      • 2013-08-24
      相关资源
      最近更新 更多