103

I often do this,

let when = DispatchTime.now() + 2.0
DispatchQueue.main.asyncAfter(deadline: when) {
   beep()
}

and in one app we often do this

tickle.fresh(){
    msg in
    paint()
}

but if you do this

let when = DispatchTime.now() + 2.0
DispatchQueue.main.asyncAfter(deadline: when) {
   tickle.fresh(){
      msg in
      paint()
   }
}

of course you have to do this

let when = DispatchTime.now() + 2.0
DispatchQueue.main.asyncAfter(deadline: when) { [weak self] _ in
   tickle.fresh(){
      msg in
      self?.paint()
   }
}

or, maybe this

let when = DispatchTime.now() + 2.0
DispatchQueue.main.asyncAfter(deadline: when) {
   tickle.fresh(){
      [weak self] msg in
      self?.paint()
   }
}

or maybe this

let when = DispatchTime.now() + 2.0
DispatchQueue.main.asyncAfter(deadline: when) { [weak self] _ in
   tickle.fresh(){
      [weak self] msg in
      self?.paint()
   }
}

W T H should we do?

All three suggestions seem to work perfectly. What's the full depth of meaning here? And which should one do? Is a strong reference to a weak reference, a weak or strong reference? To be or not to be? That's the question!

Fattie
  • 27,874
  • 70
  • 431
  • 719

1 Answers1

238

First of all, note that you generally don't need to worry about retain cycles with DispatchQueue.main.asyncAfter, as the closure will be executed at some point. Therefore whether or not you weakly capture self, you won't be creating a permanent retain cycle (assuming that tickle.fresh also doesn't).

Whether or not you put a [weak self] capture list on the outer asyncAfter closure depends entirely on whether you want self to be retained until the closure is called (after the time you set). If you don't need self to remain alive until the closure is called, put [weak self] in, if you do, then don't put it in.

Whether or not you put a [weak self] on the inner closure (the one passed to tickle.fresh) depends on whether you've already weakly captured self in the outer closure. If you haven't, then you can put [weak self] in order to prevent the inner closure from retaining it. If however, the outer closure has already weakly captured self, then the inner closure will already have a weak reference to self, thus adding [weak self] to the inner closure will have no effect.

So, to summarise:


DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
   tickle.fresh { msg in
      self.paint()
   }
}

self will be retained by both the outer and inner closure.


DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
   tickle.fresh { msg in
      self?.paint()
   }
}

self will not be retained by either closure.


DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
   tickle.fresh { [weak self] msg in
      self?.paint()
   }
}

Same as the above, the additional [weak self] for the inner closure has no effect, as self is already weakly captured by the outer closure.


DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
   tickle.fresh { [weak self] msg in
      self?.paint()
   }
}

self will be retained by the outer closure, but not the inner closure.


Of course, it might be that you don't want self to be retained by the outer closure, but you do want it to be retained by the inner closure. In such cases, you can declare a local variable in the outer closure in order to hold a strong reference to self, when you can then capture in the inner closure:

DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
   guard let strongSelf = self else { return }
   tickle.fresh { msg in
      strongSelf.paint()
   }
}

Now, self won't be kept alive by the outer closure, but once it's called, if self still exists, it will be kept alive by the inner closure until that closure has been deallocated.


In response to:

Is a strong reference to a weak reference, a weak or strong reference?

Weak references are implemented as optionals, which are value types. Therefore you cannot directly have a strong reference to one – instead you first have to unwrap it, and then take a strong reference to the underlying instance. In this case you're simply dealing with a strong reference (exactly like my example above with strongSelf).

However, if a weak reference is boxed (this happens with closure capture – the value type will be put into a heap-allocated box) – then you can indeed have a strong reference to that box. The effect of this is equivalent to a weak reference to the original instance, you just have an invisible bit of extra indirection.

In fact, this is exactly what happens in the example where the outer closure weakly captures self and the inner closure 'strongly captures' that weak reference. The effect is that neither closure retains self.

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • 1
    @JoeBlow It means that `self` will be kept alive by the outer closure, but once it has been executed, it could be deallocated *before* the inner closure gets executed (assuming that it's executed asynchronously). And in the final example, I'm using optional binding with `guard let`, which unwraps the weak `self`, giving me a strong reference to it :) – Hamish Feb 02 '17 at 01:06
  • 1
    @JoeBlow Sure, there's no harm in using both – although as I say, the inner `[weak self]` is unnecessary if the outer closure captures `self` weakly. – Hamish Feb 02 '17 at 01:12
  • Your note has been noted @JoeBlow :) I added a response to your question "*Is a strong reference to a weak reference, a weak or strong reference?*" late last night btw, don't know whether you saw it or not. The answer is it can be either – depending on how you're taking the strong reference to the weak reference. – Hamish Feb 02 '17 at 16:47
  • 1
    Very nice answer. – GoldenJoe Oct 24 '17 at 22:04
  • 1
    Really nice and clear answer, is there any documentation related to these ARC inner closures concepts? – Andrea Gorrieri Feb 01 '19 at 14:40
  • Your headline "First of all, note that you generally don't need to worry about retain cycles with " is misleading. With dispatchQueues it's NEVER a concern of 'retain cycles' The concern is mainly about 'strong references', 'abandoned memory', basically an object that is referenced from a root/global object. Roots/globals are like GCD or runloop. e.g. with Runloop and (repeating) timers. The runloop is never releasing `self` its target. `self` doesn't have a reference back to the runloop. Hence it never gets deallocated. (1/2) – mfaani Oct 14 '19 at 02:30
  • The memory graph for abandoned memory is a **one** way linear graph that goes all the way back to a root object. For retains cycles is either a cycle or a '**two** way' line in isolation. For more see [here](https://developer.apple.com/videos/play/wwdc2016/410/?time=1952). Also see [How can I create a reference cycle using dispatchQueues?](https://stackoverflow.com/questions/56061092/how-can-i-create-a-reference-cycle-using-dispatchqueues) and [here](https://stackoverflow.com/questions/56261915/does-xcode-memory-graph-offer-any-smart-visual-indicators-for-strong-references?noredirect=1&lq=1) – mfaani Oct 14 '19 at 02:30
  • I have to add a caveat to the notion that, when using `asyncAfter`, a weakification is in all cases not necessary. I haven't tried it lately, but there was a time when UIViewController instances died as soon as they were removed from the view hierarchy, leading to a crash when accessing them asynchronously. Weakification mitigated this. To be fair, it probably was a bug in UIKit's memory management. – manmal Feb 18 '20 at 20:28