递归块中的ARC行为

时间:2013-10-27 19:54:37

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

我已经制作了这两个实用功能:

+ (void)dispatch:(void (^)())f afterDelay:(float)delay {
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay*NSEC_PER_SEC)),
                    dispatch_get_main_queue(),
                    f);
  }

+ (void)dispatch:(void (^)())f withInterval:(float)delay {
    void (^_f)() = nil; // <-- A
    _f = ^{
        f();
        [self dispatch:_f afterDelay:delay]; // <-- B
    };
    [self dispatch:_f afterDelay:delay];
}

这个想法是你可以打电话:

[自我发送:阻止 afterDelay:延迟]; - 在特定时间后执行块

[自我发送:阻止 withInterval:延迟]; - 定期执行一个块

现在好了,如果我按原样调用 dispatch:withInterval:,它将在运行时创建错误,因为当程序尝试执行 B 时的行时 _f 的值为 nil ;而这又发生了,因为 _f A 处对 _f 的值进行了引用。

如果我将 A 更改为:

,则可以修复此问题
__block void (^_f)() = nil;

并且我正在强烈引用 _f ,所以当代码达到 B 时, _f 的值是最终的分配给它的值。这个问题是我正在进入一个保留周期。

最后,我可以将 A 更改为:

__block void (^_f)() __weak = nil;

应该处理这两个问题,但是我发现当代码达到 B 时, _f 的值再次出现 nil 因为,在评估时, _f 已被取消分配。

我有几个问题:

  • 在最后一个场景中,为什么 _f 会被取消分配?如何告诉ARC至少在下次调度电话之前保留该块?
  • 编写这些函数的最佳(和ARC兼容)方法是什么?

感谢您的时间。

2 个答案:

答案 0 :(得分:4)

  

如何告诉ARC至少在下次调度电话之前保留该块?

我会说,通过您使用__block的方法。

  

问题在于我正在进入保留周期。

我不明白为什么那会是一个问题。你想让你的计时器无限期地开火,对吧?这意味着与之关联的对象也必须永远存在。只要您派遣该块,它仍然由GCD保留,但是有额外的参考似乎并没有受到伤害。

如果您将来某个时候决定取消计时器,请设置_f = nil。这将打破保留周期。

  

编写这些函数的最佳(和ARC兼容)方法是什么?

嗯,最好的方法是使用NSTimer。 但我确实认为 有趣的是学习如何使用GCD。令人高兴的是,Apple有a timer example here

  

好的但是,每次调用_f时,对_f的引用是否都会增加?

让我们来看看__block是如何工作的。系统的作用是在堆上创建一个全局变量,并将引用(比如一个值为A的指针)传递给该块的内存(例如,位于内存值B)。

因此,您在地址A处有一些内存,它引用地址B处的内存,反之亦然。如您所见,此处每个对象的保留计数为1;好吧,GCD也保留,但这个保留计数是不变的,没有理由增加。

你可以从其他地方取消_f,然后在GCD结束后,保留计数变为0。

  

为什么在使用__weak时会被释放?

正如我们所看到的,有两件事会影响地址B上对象的ARC计数:GCD和变量_f。如果你使_f弱,那么在分配给它之后,你的块仍然没有来自_f的保留计数,并且它没有来自B行的计数,因为你实际上没有运行该块。因此它立即被解除分配。


注意:的 这就是ARC的美丽:你每次都会得到这种行为,在这里我们可以遵循逻辑上发生的一切并推断出原因。使用垃圾收集器时,这个块有时会被释放,有时也不会,这使得调试这个问题变得很糟糕。

答案 1 :(得分:1)

_f需要一个强大的参考,因为否则在ARC中,分配给它的块可能会立即消失,因为没有强引用它。

同时,块需要访问指向自身的指针,并且,正如您所发现的,这必须使用__block变量来完成。从块到自身的强引用将导致保留周期,因此这必须是弱引用。

因此,您需要两个变量,一个强,一个弱:

+ (void)dispatch:(void (^)())f withInterval:(float)delay {
    __block __weak void (^_f_weak)() = nil; // a weak __block variable for the block to capture
    void (^_f)() = nil; // a strong variable to hold the block itself
    _f_weak = _f = ^{ // both variables will point to the block
        f();
        [self dispatch:_f_weak afterDelay:delay];
    };
    [self dispatch:_f afterDelay:delay];
}