5

我做了这两个实用功能:

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

这个想法是您可以调用:

[自调度:block afterDelay:延迟];- 在特定时间后执行块

[自调度:block withInterval:延迟];- 定期执行一个块

好的,如果我按原样调用dispatch:withInterval:,它将在运行时产生错误,因为当程序尝试在B处执行该行时, _f的值将为nil;这反过来又发生了,因为_fA持有对 _f 值的引用

如果我将A更改为:

__block void (^_f)() = nil;

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

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

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

应该可以解决这两个问题,但是我发现当代码到达B时, _f的值再次为零,因为在它被评估时,_f已经被释放。

我有几个问题:

  • 在最后一种情况下,为什么_f会被释放?我如何告诉 ARC 至少在下一次调度调用之前保留该块?
  • 编写这些函数的最佳(和 ARC 兼容)方法是什么?

谢谢你的时间。

4

2 回答 2

4

我如何告诉 ARC 至少在下一次调度调用之前保留该块?

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

这样做的问题是我正在陷入一个保留周期。

我不明白为什么这会是一个问题。你希望你的计时器无限期地触发,对吧?这意味着与之关联的对象也必须永远存在。只要您正在分派块,GCD 就会保留它,但是有一个额外的引用似乎并没有什么坏处。

如果在未来的某个时刻,您决定取消计时器,您可以通过设置_f = nil. 这将打破保留周期。

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

好吧,最好的方法是使用NSTimer. 但我确实认为学习如何使用 GCD很有趣。令人高兴的是,Apple这里有一个计时器示例

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

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

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

_f您可以从其他地方为空,然后在 GCD 完成块后,保留计数变为 0。

为什么当我使用 __weak 时它会被释放?

正如我们所见,有两件事会影响地址 B 处对象的 ARC 计数: GCD 和 variable _f。如果你做_f弱,那么在分配给它之后,你的块仍然没有保留计数_f,并且它没有从 B 行开始计数,因为你还没有实际运行该块。因此它立即被释放。


笔记。 这就是 ARC 的美妙之处:你每次都会得到这种行为,在这里我们可以按照逻辑发生的一切并推断出原因。使用垃圾收集器,这个块有时会被释放,有时不会,这使得调试这个问题变得很麻烦。

于 2013-10-27T20:08:07.623 回答
1

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

同时,块需要访问指向自身的指针,并且正如您所发现的,这必须通过__block变量来完成。块对自身的强引用会导致一个retain循环,所以这一定是一个弱引用。

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

+ (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];
}
于 2013-10-28T02:45:34.850 回答