Swift Closures - Capturing Self as Weak

In Swift, if I have a closure capturing [weak self], is it good practice to unwrap the optional self at the beginning of the closure?


I believe that the first implementation is better because self could become nil between the statements

And that's why the second implementation is in fact better! If self is not nil at the first statement, the first statement makes it so that self couldn't become nil between the statements. It retains self exactly for the remainder of the block. This is called "the weak–strong dance".

guard let self = self else { return }
// ^^^^ this self is _strong_, not weak; it retains self

Do we need to explicitly use capture list for weak variables in swift closure?

Sort of, in this specific example, but you need to be very careful about how you think about what's happening.

First, yes, this is identical. We can tell that by generating the SIL (swiftc -emit-sil main.swift). Except for the difference in the name of self vs weakSelf, these generate exactly the same unoptimized SIL. In order to make it even clearer, I'll change the name of the variable in the capture list (this is just a renaming, it doesn't change the behavior):

weakSelf

    weak var weakSelf = self
callback = {
weakSelf?.perform()
}

weak_self

    callback = { [weak weakSelf = self] in
weakSelf?.perform()
}

Compare them

$ swiftc -emit-sil weakSelf.swift > weakSelf.sil
$ swiftc -emit-sil weak_self.swift > weak_self.sil
$ diff -c weakSelf.sil weak_self.sil

*** weakSelf.sil 2022-03-27 10:58:13.000000000 -0400
--- weak_self.sil 2022-03-27 11:01:22.000000000 -0400
***************
*** 102,108 ****

// Foo.init()
sil hidden @$s4main3FooCACycfc : $@convention(method) (@owned Foo) -> @owned Foo {
! // %0 "self" // users: %15, %8, %7, %2, %22, %1
bb0(%0 : $Foo):
debug_value %0 : $Foo, let, name "self", argno 1, implicit // id: %1
%2 = ref_element_addr %0 : $Foo, #Foo.callback // user: %4
--- 102,108 ----

// Foo.init()
sil hidden @$s4main3FooCACycfc : $@convention(method) (@owned Foo) -> @owned Foo {
! // %0 "self" // users: %8, %7, %15, %2, %22, %1
bb0(%0 : $Foo):
debug_value %0 : $Foo, let, name "self", argno 1, implicit // id: %1
%2 = ref_element_addr %0 : $Foo, #Foo.callback // user: %4

Except for the order of the users comment for self, they're identical.

But be very, very careful with what you do with this knowledge. This happens to be true because there exists a strong reference to self elsewhere. Going down this road too far can lead to confusion. For example, consider these two approaches:

// Version 1
weak var weakObject = Object()
let callback = {
weakObject?.run()
}
callback()


// Version 2
var weakObject = Object()
let callback = { [weak weakObject] in
weakObject?.run()
}
callback()

These do not behave the same at all. In the first version, weakObject is already released by the time callback is created, so callback() does nothing. The compiler will generate a warning about this, so in most cases this is an unlikely bug to create, but as a rule you should generally do weak captures in the capture list so that it occurs as close to the closure creation as possible, and won't accidentally be released unexpectedly.

When to use [self] vs [weak self] in swift blocks?

[self] indicates that self is intentionally held with a strong reference (and so some syntax is simplified). [weak self] indicates that self is held with a weak reference.

why would I use strong capture [self] inside block as there are chances of memory leak

You would use this when you know there is no reference cycle, or when you wish there to be a temporary reference cycle. Capturing self does not by itself create a cycle. There has to be a cycle. You may know from your code that there isn't. For example, the thing holding the closure may be held by some other object (rather than self). Good composition (and decomposition of complex types into smaller types) can easily lead to this.

Alternately, you may want a temporary cycle. The most common case of this URLSessionTask. The docs are very valuable here (emphasis added):

After you create a task, you start it by calling its resume() method. The session then maintains a strong reference to the task until the request finishes or fails; you don’t need to maintain a reference to the task unless it’s useful for your app’s internal bookkeeping.

Another common example is DispatchQueue, which similarly holds onto a closure until it finishes. At that point, it releases it, killing the cycle and allowing everything to deallocate. This is useful and powerful (and common!), when used with intent. It's a source of bugs when used accidentally. So Swift requires you to state your intentions and tries to make the situation explicit.

When you build your own types that retain completion handlers, you should strongly consider this pattern, too. After calling the completion handler, set it to nil (or {_ in } for non-optionals) to release anything that completion handler might be referencing.

One frustrating effect of the current situation is that developers slap [weak self] onto closures without thought. That is the opposite of what was intended. Seeing self was supposed to cause developers to pause and think about the reference graph. I'm not certain it ever really achieved this, but as a Swift programmer you should understand that this is the intent. It's not just random syntax.

Weird weak self and retain cycle behaviour

First of all, all printers are creating and retaining their own Delayer. The delayer takes a closure and, in turn, retains that closure.

Let's try to walk through them one by one.

Printer1

As you stated yourself, it's pretty clear why it's creating a retain cycle. You are passing the self.action instance method as the closure to the Delayer, and since all closures are reference types, passing self.action will retain its surrounding scope (which is Printer1).

Printer2

Again, pretty obvious here. You're explicitly capturing a weak reference to self inside the closure you're passing to Delayer, hence not creating a retain cycle.

Printer3

Here, a retain cycle is not created, because the self.weakAction property is called immediately, and its result (a closure which holds a weak reference to self) is passed on to Delayer. This, in effect, is the exact same thing as what's happening in Printer2.

Printer4

First, you're capturing a weak reference to self, and then fetching welf?.action and passing the result into Delayer. Again, welf?.action is called immediately, and the result (a pointer to an instance method) is passed on to Delayer. The weak reference to self is only kept for the duration of the surrounding scope (the lazy var creation scope), and passing the action instance method will retain self. This is identical to Printer1.

Printer5

Here, you're first creating a weak reference to self, and then you're capturing that weak reference inside a new closure that is passed to Delayer. Since self is never directly referenced in the passed closure, it will not capture self in that scope, only the welf weak reference. This is pretty much identical to Printer2, but with a slightly different syntax.

Personally, I would opt for the Printer2 way (creating a new closure, retaining a weak reference to self and using that to call self?.action). It makes for the easiest code to follow (as opposed to retaining a variable with a closure that weakly captures self). But, depending on what you're actual use case is, it might of course make sense.

Is it the right way using `[weak self]` in swift closure?

Your pattern has race condition. If self was deallocated at the exact same time as your completion handler closure was executing, it could crash. As a general rule, avoid using the ! forced unwrapping operator if you can.

  1. I’d lean towards the guard “early exit” pattern (reducing nested braces, making code easier to read). The standard Swift 4.2 solution is:

    someTask { [weak self] result in
    guard let self = self else { return }

    self.xxx = yyy
    self.doLongTermWork()
    self.finish()
    }
  2. Before Swift 4.2, which implemented SE-0079, we would have to do something like:

    someTask { [weak self] result in
    guard let strongSelf = self else { return }

    strongSelf.xxx = yyy
    strongSelf.doLongTermWork()
    strongSelf.finish()
    }

    You can see why we prefer the Swift 4.2 improvement, as this strongSelf syntax is inelegant.

  3. The other obvious alternative is just:

    someTask { [weak self] result in
    self?.xxx = yyy
    self?.doLongTermWork()
    self?.finish()
    }

    Sometimes you need the “weak self - strong self dance” (the first two alternatives), but it would not appear to be the case here. This is likely sufficient.

There are other scenarios/edge cases that one might contemplate, but these are the basic approaches.



Related Topics



Leave a reply



Submit