Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ARC behavior within a recursive block

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.

like image 677
Ale Morales Avatar asked Oct 27 '13 19:10

Ale Morales


Video Answer


1 Answers

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.

like image 98
ilya n. Avatar answered Sep 19 '22 21:09

ilya n.