I've made these two utility funcions:
+ (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];
}
The idea is that you would be able to call:
[self dispatch:block afterDelay:delay]; - to get a block executed after a specific time
and
[self dispatch:block withInterval:delay]; - to get a block executed periodically
Ok now, if I call dispatch:withInterval:, as it is, it will create an error at runtime because when the program tries to execute the line at B the value of _f will be nil; and that in turn happens because _f holds a reference to the value of _f at A.
This could be fixed if I change A to:
__block void (^_f)() = nil;
and with this I'm making a strong reference to _f, so when the code reaches B the value of _f is the final value that was assigned to it. The problem with this is that I'm incurring into a retain cycle.
Finally, I can change A to be:
__block void (^_f)() __weak = nil;
and that should take care of both issues, however I've found that when the code reaches B the value of _f is again nil because, at the time it gets evaluated, _f has already been deallocated.
I have a couple questions:
- On the last scenario, why does _f get deallocated? How do I tell ARC to retain the block at least until the next dispatch call?
- What would be the best (and ARC-compliant) way to write these functions?
Thanks for your time.
_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:
I would say, by the method you use with
__block
.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.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.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.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.