In Swift, When Using "[Weak Self] In", Should I Double Up on It When Nested Inside Another Closure

Do we need to repeat `guard let self = self else { return }` inside each nested closure to keep strong self?

Yes, one is enough.
If you write

guard let self = self else { return }

you'll create a new local variable that will hold a strong reference to the outside weak self.

It's the same as writing

guard let strongSelf = self else { return }

and then use strongSelf for the rest of the block.

Do capture lists of inner closures need to redeclare `self` as `weak` or `unowned`?

The [weak self] in anotherFunctionWithTrailingClosure is not needed.

You can empirically test this:

class Experiment {
func someFunctionWithTrailingClosure(closure: @escaping () -> Void) {
print("starting", #function)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
closure()
print("finishing", #function)
}
}

func anotherFunctionWithTrailingClosure(closure: @escaping () -> Void) {
print("starting", #function)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
closure()
print("finishing", #function)
}
}

func doSomething() {
print(#function)
}

func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
self?.anotherFunctionWithTrailingClosure { // [weak self] in
self?.doSomething()
}
}
}

// go ahead and add `deinit`, so I can see when this is deallocated

deinit {
print("deinit")
}
}

And then:

func performExperiment() {
DispatchQueue.global().async {
let obj = Experiment()

obj.testCompletionHandlers()

// sleep long enough for `anotherFunctionWithTrailingClosure` to start, but not yet call its completion handler

Thread.sleep(forTimeInterval: 1.5)
}
}

If you do this, you will see that doSomething is never called and that deinit is called before anotherFunctionWithTrailingClosure calls its closure.

That having been said, I might still be inclined to use the [weak self] syntax on anotherFunctionWithTrailingClosure to make my intent explicit.

Referring to weak self inside a nested block

It depends.

You only create a retain cycle if you actually store the block (because self points to the block, and block points to self). If you don't intend to store either of the blocks, using the strong reference to self is good enough --- block will be released first after it got executed, and then it will release it's pointer to self.

In your particular example, unless you're performing more operations which are not shown, you don't need to create any weakerWeakerEvenWeakerSelfs.

Where does the weak self go?

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.

Should I use weakSelf in nested blocks?

In this situation, you are not worried about cyclic references. What you are worried about is a situation where the object self isn't actually needed anymore, but using self inside a nested block would keep it unnecessarily alive. For example, if you have a view controller that should go away when the view is removed by the screen, but you download an image that you would like to display in the controllers view. If the image arrives long after the view is already gone, you don't want the view controller alive anymore.

Best is

__weak typeof (self) weakSelf = self;

before calling the outermost method. Then within every block that ought to use self, you add

typeof (self) strongSelf = weakSelf;

and use strongSelf within that block. Depending on the situation, you might want to check that strongSelf isn't nil at that point, but sending messages to strongSelf when it is nil has no effect, so if all you do is sending messages and getting or setting properties, then a check for nil is not necessary.

What happens if you don't do this? The difference will be that self may be kept alive unnecessarily into the innermost block, if you use self everywhere (or just in the innermost block).



Related Topics



Leave a reply



Submit