/ swift

Capturing Self with Swift 4.2

Swift blocks are an increasingly common way of providing callback type behavior to asynchronous functions.

Swift 4.2 introduced an interesting change recently, but first let's take a step back and review what "capturing" means.

Swift blocks look like this:

doSomething(then: {
  // do something else
})

Due to the mechanics of memory management, if we need access to local variable within that block, it must be captured.

var dog = Dog()
doSomething(then: {
  dog.bark() // dog must be captured so it will live long enough
})

The Swift compiler automatically manages this for you. dog will be captured (thus increasing its retain count) and you can ensure that even though the dog instance falls out of the current scope, the block is still retaining it.

Sometimes you don't want this behavior. The most common reason why is the case where you need to call a method on self inside the block. The block will retain self and you've just created a bi-directional dependency graph, otherwise known as a retain cycle.

retain-cycle

This is bad because it means self can't ever be released as long as the block lives. Often times the blocks are stored as properties on the instance, or as observers for KVO or notifications, and their lifetime is bound to self.

If you've done this, congratulations... you have a memory leak.

Avoiding Retain Cycles

To avoid this, you can provide a capture binding declaration to the block definition to influence when a variable should be strongly held or not:

let dog = Dog()
doSomething(then: { [weak dog] in
    // dog is now Optional<Dog>
    dog?.bark()
)

(So unless we strongly reference dog in a property or through some other means, it will fall out of scope, and the dog reference inside the block will be nil.)

This works for self as well:

doSomething(then: { [weak self] in
    self?.doSomethingElse()
)

By declaring a capture as weak, you're telling the block to "nil-out" this reference if the original is released.

The Strong-weak dance

If you need to guarantee that the items in the block execute you can create a new strong reference inside the block. For instance the variable became nil while the block was executing, preventing some of the behavior from executing.

doSomething(then: { [weak self] in
    guard let strongSelf = self { else return }
    strongSelf.doSomethingElse()
)

In the above example, we ensure that our strongSelf reference exists for the scope of the block.

This method is fairly common in Swift projects, but always felt a little odd.

Side note: many have pointed out that you can do this as well:

guard let `self` = self else { return }

however this has been noted as a compiler bug and you should probably avoid using it, as it can change in a future release.

Back to Swift 4.2

Ok, back to the title of this post.

Swift 4.2 recently adopted a proposal to add this to the language:

guard let self = self else { return }

This avoids some of the weird wording, but it also strikes me as a little odd. I tweeted about this:

I also think lines like the above would be incredibly puzzling for someone brand new to the language.

That said, I do think the syntax is a lot cleaner, and I'll probably start using this in my projects.

What do you think about this change?