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
- What does Capturing Value means?
- How closure capture variables?
- What does self stands for inside a closure body?
- What is global and nested function?
- Ins and outs of Capture list
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:
[property]
creates a new instance namedproperty
inside the closure body, from the originalproperty
instance of theclass
.[unowned property]
same as no. 1, this time the reference is anunowned
reference.[weak property]
this timeweak
reference.[newProperty = self.property]
createsnewProperty
fromself.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.