Swift closure capture list

Capture list, release me if you can



This will be the last talk about closure on the closure blog post series. On the first two blog post, closure, starting of being creative and Gaining last minute Knowledge on Closure, we covered all the variances of closure expression. We also talked about the why and how of those closure. Best practice to follow. Then some extra like, relation between Function and closure. So we talked a lot. But there is one last ting to talk about. With out this topic the talk of closure is incomplete. The final topic of our entire closure talk is Capture list. So we will talk about history of Capture list 😜. Fear not it is not the boring history; it is the why Capture list is originated on inside of a closure. Why we need the tiny words unowned, weak and the functionality of those keyword. We will find out why self is used inside a closure. So this Swift talk is dedicate to the closure capture list.

The origin of this blog post goes back to the previous blog post on Strong Reference cycles. Because to understand the capturing of variable by closure, we first need to understand the memory management of swift ecosystem.

Enough intro talk, Let us get going on Swift capture list for Closure.

Background

Capturing Value

On the very simplest term Capturing Value means, holding the reference of constants and/or variables and thus blocking the release of those constants and/or variables. It is more like a lockdown on the release process of the constants and/or variables. Closure can capture the values form its surrounding scope. This blog post is dedicated on this topic.

How closure capture variables?

As closure is reference type they can capture the variables when they are referenced on a class. Let us describe a bit more.

We know to be a Strong Reference Cycle, two reference type instance need to hold a strong reference of the other instance. As closure are reference type instance they can certainly generate the Strong Reference Cycle when they are declare on a class. In simple words; when a class have a reference of a closure on its scope and later on the closure reference the class back, maybe to access a property or a method of that class in the closure body, then a Strong Reference Cycle is formed between the class and the closure.

On the following example we will make a bot who will have some fixed lightsaber action and can perform those action when called.


enum LightsaberAction: String{
    case poke
    case slash
    case block
}

class LightsaberMove{
    let actions: [LightsaberAction]
    lazy var perform: () -> String = {
        self.actions.reduce(into: "Action sequence:", { (sequence, action) in
            sequence += " " + action.rawValue
        })
    }
    
    init(actions: [LightsaberAction]) {
        self.actions = actions
    }
    
    deinit {
        "All lightsaber moves been complete"
    }
}

var botMove:LightsaberMove? = LightsaberMove(actions: [.slash, .poke, .poke, .block, .slash])
botMove?.perform()

botMove = nil

The LightsaberAction is the action enum, each case represent a fixed action. The LightsaberAction confirms String raw representable.

The LightsaberMove class has an array of LightsaberAction named actions and also a closure which is here a lazy var, perform. The perform will return a string containing all the moves of that LightsaberMove instance. We are using reduce, hof to generate the String.

As we can see we are enumerating the action property on the perform closure body, when we write self.actions. Why self? Coming up on the next section.

So here on the closure body we are having a property reference of the class instance and also the class instance have a reference of the closure. So that two reference type instance now pointing each other and as they are reference type object they are generating a strong reference cycle here. As a result when we set botMove:LightsaberMove? to nil the deinit() of the class instance was not called. Again a classic example of strong reference cycle.

What does self stands for inside a closure body?

self actually represent an instance. When we use self.someProperty then we are accessing the someProperty of that instance. We actually use self on this purpose on the LightsaberMove init method, when we write self.actions = actions, as actions name was used for both the param in the init method and also in the instance property.

But on closure the self has a very specific purpose. The Swift compiler will force us to write self before accessing any property of the instance on the closure body. By forcing the self as a prefix of an instance’s property Swift makes it clear to the coder that he is using an instance property inside the closure body, so he should be extra careful to handle the Strong Reference Cycle between the instance and the closure.

On the above example that just happened when we wrote self.actions on the closure body. We as coder didn’t take the necessary steps to break down a possible Strong Reference Cycle, as a result we now have memory leak.

We gonna resolve this issue just after the next section. There is also another type of closure which can also generate Strong Reference Cycle. Please continue.

What is global and nested function?

Assuming we already have the closure knowledge, if not we can always visit the intro part of closure, we will talk about global and nested function.

We already talked that function is a special kind of closure. So the only two thing remains, global and nested. And those two keyword are very obvious, right?

So the global function are the regular function. The global function or simply the function is defined on the global scope, the scope can be the scope of a class, struct, or enum.

On the other hand a nested function is defined inside a function. So a function inside a function 🤓. Example please.

Let us assume we are building a part of a game, where there is a life counter, lifeCounter. As usual zero means game over.


var lifeCounter = 1{
    didSet{
        lifeCounter > 0 ? "Still alive" : "Game over"
    }
}

Do we ever talked about didSet? Probably no. May be details on some future post.
In a nutshell: the didSet is a property observer.
Now what is property observer?
Well property observer observers a property’s value and been called by the compiler when the value is changed. There is willSet which is called before setting a value on the property and didSet which is called after setting a value on the property. So the didSet will be called after the value of lifeCounter is set.

We will update the lifeCounter through some nested functions.


func adjustLifeCount(damaged: Bool){
    func increase(){
        lifeCounter += 1
    }
    
    func decrease(){
        lifeCounter -= 1
    }
    
    damaged ? decrease() : increase()
}

Very simple implementation; if not damaged then we will increase the lifeCounter else will decrease the lifeCounter.

Finally to call:


adjustLifeCount(damaged: false)

adjustLifeCount(damaged: true)
adjustLifeCount(damaged: true)

The intension of the above example is to give an introductory idea on nested function; not on the hows and why of nested function. Using nested function is definitely a contradictory issue. But it has its benefits obviously, SO has some more details.

Ok why do we talked about nested function, what relation it has with our current talk on Capture list. Well a very interesting question.

Though the global function are closure, but they do not capture value. On contrary nested function do capture value from the parent function. So a Strong-Reference-Cycle scenario between the nested function and the constant/variable of the parent function can occur.

Ins and outs of Capture list

The solution is very simple. Set a weak reference, either weak or unowned to the class instance or its property on the closure body.

To break down the Strong Reference Cycle of class instance and closure Swift provides a smart solution, Capture list. Now what is capture list? Well, Capture list is a set of rules, those define the relationship among the class instance and the closure. The Capture list is defined much like an array of rules. Each rule states the type of weak reference the closure will use for the class instance or its property. Example will clarify.


class Jedi{
    let name: String
    let actions: [LightsaberAction]
    
    lazy var perform: ()->String = {
        [weak self] in
        self?.actions.reduce(into: "Action sequence:", { (sequence, action) in
            sequence += " " + action.rawValue
        }) ?? ""
    }
    
    init(name: String, actions:[LightsaberAction]) {
        self.name = name
        self.actions = actions
    }
    
    deinit {
        "The Jedi: \(name) is no more..."
    }
}

var yoda: Jedi? = Jedi(name: "Yoda", actions: [.slash, .slash, .block, .poke])
yoda?.perform()
yoda = nil

The above example is very close to the other example LightsaberMove except we are using capture list this time. On the perform closure we are now using a weak self reference of self by writing [weak self]. The in keyword is separating the capture list from the closure body.

Also the weaker self, [weak self], is not the original self of the class instance. The weaker self is a separated instance initialized from the original self. The weaker self has a limited scope, only the body of the corresponding closure; whereas the original self has the scope of the full class.

We cloud have write the [weak self] as [weak self = self]. Swift is smart enough to infer the long statement into the smaller statement. 🤓

Following are the some of the expression of closure list:

  1. [property] creates a new instance named property inside the closure body, from the original property instance of the class.
  2. [unowned property] same as no. 1, this time the reference is an unowned reference.
  3. [weak property] this time weak reference.
  4. [newProperty = self.property] creates newProperty from self.property

There can be some other expression, but we get it, right? 😇

One last thing, we need to understand is that the capture list can work only on reference type instance. So to add a property on a capture list, it has to be a class or a class-bound protocol type.

End Talk

All the source code used for this blog post are available on GitHub.

So finally we are finishing our closure talk. By ending the talk on capture list we completed the whole talk on Swift closure. Whats next? Hmmm protocol maybe. Stay tune. We will be talking soon. Till then take care.

Leave a Reply

Notifications for mobidevtalk! Cool ;) :( not cool