Functional programming: Closure reference cycle and fix
Here, I will try to explain an example of closure reference cycle. Follow the following article to understand more about closures in swift.
Article_1 link → Functional swift : All about closures.
Article_2 link → Lazy var in iOS swift.
Please read the above two articles to continue.
Assuming you have a good knowledge in functional programming and closures, let’s begin.
Consider a class Human
. It is having an init
method which accepts a firstName
and lastName
as parameters. It also has a lazy var closure
called fullName
which accepts nothing and returns a String
. It is of type ()->String
.
Here, when we call the init method , a new Human
object is created . We assign this object to an optional type humanObj
.
In the next line, when we set the value of humanObj
to nil
, the deinit{}
method will get called. You can see the console printing the text given inside the deinit
method.
Now, try to acces the fullName
closure after creating the object. Then try to set the object to nil
.
var humanObj:Human? = Human(firstName: “John”, lastName: “Doe”)let fullName = humanObj?.fullNamehumanObj = nil
The deinit{}
method will not be called as the closure holds a strong reference to self
.
deinit method will get called when an object is deallocated.
The fix
We can break this strong reference cycle using
self
in the capture list with either aweak
or anunowned
reference. If you don’t know what a capture list is, go through the article mentioned at the top.
weak
A weak reference is a reference that does not keep a strong hold on the instance it refers to, and so does not stop ARC from disposing of the referenced instance. This behaviour prevents the reference from becoming part of a strong reference cycle.
Since a weak reference may be nil
, the variable captured becomes optional. Therefore, we should use a guard
to safely unwrap it:
lazy var fullName:() -> String = { [weak self] inguard let weakSelf = self else { return “”}return “\(weakSelf.firstName) \(weakSelf.lastName)”}
Unowned
Like a weak reference, an unowned reference does not keep a strong hold on the instance it refers to. Unlike a weak reference, however, an unowned reference is used when the other instance has the same lifetime or a longer lifetime
lazy var fullName:() -> String = { [unowned self] inreturn “\(self.firstName) \(self.lastName)”}
This means that we should use unowned
just when we are sure that the reference will never be nil
inside the closure, otherwise the app would crash. So, guard check is not required here.
We can use weak
and unowned
with any variable in the capture list and we can also combine with the aliases:
lazy var fullName:() -> String = { [unowned unownedSelf = self] inreturn “\(unownedSelf.firstName) \(unownedSelf.lastName)”}
How to capture self with closures having arguments?
Well, you should capture the self
before the internal closure parameter or before the parenthesis of internal parameters. Check the code below. It explains how to capture self in a single parameter case. If you have multiple parameters, you may also use parenthesis.
IMPORTANT: For non-escaping closures, we don’t have to worry about capturing the self using weak or unowned keywords. It matters only for escaping closures.
Exceptional cases:
Although it is good practice to capture self
weakly in closures, it is not always necessary. Closures that are not retained by the self
can capture it strongly without causing a retain cycle. A few common examples of this are:
Working with DispatchQueue
, animation
closures in GCD
DispatchQueue.main.async {
self.doSomething() // Not a retain cycle
}UIView.animate(withDuration: 1) {
self.doSomething() // Not a retain cycle
}
Another interesting place where self
would not need to be captured strongly is in lazy
variables, that are not closures, since they will be run once (or never) and then released afterwards:
lazy var fullName = {
return self.firstName + " " + self.lastName
}() // not retained. no need to use self with weak or unowned
However, if a lazy
variable is a closure, it would need to capture self weakly/unowned
. The fullName:()->String
closure in the Human class is a good example for this.
Enjoy!!
If you enjoyed reading this post, please share and recommend it so others can find it 💚💚💚💚💚💚 !!!!
You can follow me on Medium for fresh articles. Connect with me on LinkedIn.
If you have any comment, question, or recommendation, feel free to post them in the comment section below!