Following earlier posts on the weak, unowned and lazy keywords, I consider here their use with closures.
Why worry about using weak and unowned in closures?
The first thing to do is to explain why we need to use weak and unowned in closures. And the best people to do that are the people writing the Swift documentation, so let's start with why we need to worry about this in our code:
If you assign a closure to a property of a class instance, and the closure captures that instance by referring to the instance or its members, you will create a strong reference cycle between the closure and the instance. Swift uses capture lists to break these strong reference cycles. (Apple)
How to solve the problem with capture lists
It can feel like a daunting level of responsibility to not create a strong reference cycle inside a closure, but if we take our time to understand the capture list then our anxieties can be alleviated:
A closure expression can explicitly specify the values that it captures from the surrounding scope using a capture list. A capture list is written as a comma separated list surrounded by square brackets, before the list of parameters. If you use a capture list, you must also use the in keyword, even if you omit the parameter names, parameter types, and return type.
Each entry in the capture list can be marked as weak or unowned to capture a weak or unowned reference to the value.
myFunction { print(self.title) } // strong capture
myFunction { [weak self] in print(self!.title) } // weak capture
myFunction { [unowned self] in print(self.title) } // unowned capture
You can also bind an arbitrary expression to a named value in the capture list. The expression is evaluated when the closure is formed, and captured with the specified strength. For example:
// Weak capture of "self.parent" as "parent"
myFunction { [weak parent = self.parent] in print(parent!.title) }" (Apple)
Rules of using weak and unowned in closures
The rules:
- use weak capture if the class instance or property is an optional
- use unowned if the class instance or property is non-optional and can never be set to nil
- "you must ... use the in keyword, even if you omit the parameter names, parameter types, and return type"
Explanations and code examples
A strong capture looks like this:class Parent { var title = "Dad" lazy var parentOf:(String)->String = { (childname:String) in return childname+"'s "+self.title } // DON'T DO THIS UNLESS YOU WANT THE CLOSURE TO KEEP THE INSTANCE ALIVE!!! }And we add a "capture list" to fix it:
class Parent { var title = "Dad" lazy var parentOf:(String)->String = { [unowned self] (childname:String) in return childname+"'s "+self.title } // OK }Note: The capture list is the [unowned self] part.
Difference between unowned and weak
Even if the title property was optional, we would still use unownedclass Parent { var title:String? = "Dad" lazy var parentOf:(String)->String = { [unowned self](childname:String) in return childname+"'s "+self.title! } }because it is the String (in this case) that is optional not the class instance. However, let's suppose we have a child class and inside this class we have an optional that is of type Parent:
class Child { var parent:Parent? init (parent:Parent) { self.parent = parent } lazy var myParent:()->() = { [weak parent = self.parent] in print(parent!.title) } }Now we use weak because the class instance is an optional. We're also using the syntax that goes beyond simply referring to self but allows us to specify a property. Notice how in the body of the closure we reference self.parent now as simply parent. (Tip: Try replacing weak with unowned and you'll see a compiler error.)
Using unowned and weak together
Let's suppose we add a grandparent into the mix but that the grandparent is not optional. We can then simply add this grandparent property to our capture list (but this time we use unowned because the grandparent is an non-optional instance):class Child { var parent:Parent? var grandparent:GrandParent init (parent:Parent, grandparent:GrandParent) { self.parent = parent self.grandparent = grandparent } lazy var myParent:()->() = { [weak parent = self.parent, unowned grandparent = self.grandparent] in print("\(parent!.title) is \(grandparent.title)'s son"); } }And for completeness, here's the GrandParent class:
class GrandParent { var title:String = "Gramps" }And here's how I'm calling it in a very basic way:
let kid = Child(parent: Parent(), grandparent:GrandParent()) kid.myParent()
Comments
Post a Comment