I\'ve made these two utility funcions:
+ (void)dispatch:(void (^)())f afterDelay:(float)delay {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t
_f
needs be a strong reference, because otherwise in ARC the block that gets assigned to it may immediately disappear, because there are no strong references to it.
At the same time, the block needs to access a pointer to itself, and, as you discovered, this must be done with a __block
variable. A strong reference from the block to itself will cause a retain cycle, so this must be a weak reference.
Therefore, you need two variables, one strong, and one weak:
+ (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];
}
How do I tell ARC to retain the block at least until the next dispatch call?
I would say, by the method you use with __block
.
The problem with this is that I'm incurring into a retain cycle.
I'm not getting why that would be a problem. You want your timer to fire indefinitely, right? This means that objects associated with it have to live forever as well. As long as you're dispatching the block, it is retained by GCD anyway, but having an additional reference doesn't seem to hurt.
If, at a some point in the future, you decide to cancel the timer, you do so by setting _f = nil
. This will break the retain cycle.
What would be the best (and ARC-compliant) way to write these functions?
Well, the best way would be to use NSTimer
.
But I do think it is interesting to learn how to use GCD. Happily, Apple has a timer example here.
Ok but, doesn't the reference to _f get incremented each time _f is called?
Let's take a look at how __block works. What the system does, is creating a global variable on a heap and passing a reference (say, a pointer with value A) to that memory to your block (say, located at memory value B).
So, you have some memory at address A that references memory at address B, and vice versa. As you see, here each object has a retain count of 1; well, GCD also retains, but this retain count is constant and has no reason to be increasing.
You can null _f
from some other place and then after GCD finishes the block the retain count goes to 0.
why does it get deallocated when I use __weak?
As we've seen, there are two things that affect the ARC count of object at address B: GCD and variable _f
. If you make _f
weak, then after assignment to it, your block still has no retain count from _f
, and it has no count from line B since you haven't actually run the block. Thus it gets immediately deallocated.
Note. That's the beauty of ARC: you will get this behavior every time, and here we can follow all that happens logically and deduce the reason. With garbage collector, this block would be sometimes deallocated and sometimes not, making debugging this problem a hell.