Swift: weak and unowned
Table of Contents
In a previous article I told you about the keyword @escaping
which is very useful in case of a scope change. If you want to know more about it, I invite you to read the article I previously wrote here :Swift: @escaping.
But the change of scope leads to other consequences for the values “shared” potentially by the two scopes.
Before talking about weak and unowned it is important to go back to some important notions.
ARC or Automatic Reference Counting #
The Automatic Reference Counting or ARC for the friends allows the management of the memory by releasing it when an instance is no longer referenced:
Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. In most cases, this means that memory management “just works” in Swift, and you don’t need to think about memory management yourself. ARC automatically frees up the memory used by class instances when those instances are no longer needed.
final class MyViewController: UIViewController {
let repository = MyRepository()
}
In this case, MyViewController
has a strong reference to repository
.
To learn more, I recommend you to read the following documentation: https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html
The case of the strong reference cycle #
As you may have understood, the ARC will then deallocate memory from the instances when they no longer have a reference.
But what happens when two objects (A and B) have a strong reference to each other?
In this case ARC will not be able to deallocate memory, which will create a memory leak.
This case happens very often if you create a delegate that is not well annotated:
protocol MyDelegate {
func myFunction()
}
class MyViewController : UIViewController {
var delegate: MyDelegate? = nil
}
Here MyViewController
and its delegate MyDelegate
are strong references to each other.
Of course, this is where weak and unowned come in.
weak #
The case of weak is quite simple and speaks for itself: it is a weak reference and therefore can be deallocated by ARC even if it still has a reference.
This also implies that a weak
reference cannot be used with the let
keyword since it will at some point be passed to nil.
The weak variables are then very useful when you can have a strong reference cycle.
If we analyze the code of an UITableView
you will notice that all delegates (delegate
and dataSource
) are both annotated with weak
for their declaration:
@available(iOS 2.0, *)
open class UITableView : UIScrollView, NSCoding, UIDataSourceTranslating {
// ...
weak open var dataSource: UITableViewDataSource?
weak open var delegate: UITableViewDelegate?
// ...
}
So I can only recommend that you do the same with your delegates. But this may cause you some problems:
protocol MyDelegate {
func myFunction()
}
class MyController {
weak var delegate: MyDelegate?
}
And then it’s the drama: **Impossible to compile!
To solve the problem nothing very complicated. Just add an inheritance to class
to your protocol :
Use a class-only protocol when the behavior defined by that protocol’s requirements assumes or requires that a conforming type has reference semantics rather than value semantics. For more about reference and value semantics, see Structures and Enumerations Are Value Types and Classes Are Reference Types.
Which translates into this:
protocol MyDelegate: class {
func myFunction()
}
class MyController {
weak var delegate: MyDelegate?
}
unowned #
A unowned
variable is very similar to a weak
variable with one difference: the compiler makes sure that the variable in question will not be nil
when accessed.
But why use unowned
instead of weak
? According to Apple, here is the reason:
Like a weak reference, an unowned reference doesn’t 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. You indicate an unowned reference by placing the unowned keyword before a property or variable declaration.
In short, if your objects have the same lifetime, it is better to use unowned
and not weak
. Let’s take a concrete example. In the previous case we had a soldier and his weapon. A soldier owns a weapon, but the weapon can still exist even if the soldier dies. These two classes have a weak reference.
But now let’s take the case of a character and his skills (for those who like role playing games). A character has several skills. These evolve with the character.
However, when the character dies, the skills disappear with him. They do not continue to exist. They have the same life span as the character.
From a code point of view, this is what it looks like:
class Player {
let name: String
let skill: Skill?
init(name: String) {
self.name = name
}
}
class Skill {
let power: Int
unowned let player: Player
init(power: Int, player: Player) {
self.power = power
self.player = player
}
}
The case of closures #
Despite the arrival of async / await
there is still a lot of code with closures which were and still are very useful to handle asynchronism.
But then what happens in the case of a closure?
A closure can capture variables or properties of a class, especially when it is @escaping
. It is therefore important to manage the ARC correctly.
The same rules apply for weak
and unowned
: if the lifecycle of both classes is the same, unowned
should be used, otherwise weak
should be preferred.
final class Authenticator {
static let shared: Authenticator = Authenticator()
func logInAsync(username: String, password: String, closure: @escaping ((_ error: Error?)->())) {
// Some code ...
}
func signUpAsync(username: String, password: String, closure: @escaping ((_ error: Error?)->())) {
// Some code ...
}
}
final class MyViewController : UIViewController {
@IBAction func login() {
Authenticator.shared.logInAsync("myUsername", "myPassword") { [weak self] (error: Error?) in
// ...
}
}
}
As you can see in the previous code we use weak
and not unowned
because the Authenticator
singleton does not have the same lifetime as the MyViewController
class.
On the other hand in the following code we prefer to use unowned
because both classes have the same lifetime:
final class UserRepository {
func logInAsync(username: String, password: String, closure: @escaping ((_ error: Error?)->())) {
// Some code ...
}
func signUpAsync(username: String, password: String, closure: @escaping ((_ error: Error?)->())) {
// Some code ...
}
}
final class LoginViewController : UIViewController {
let repository = UserRepository()
@IBAction func login() {
repository.logInAsync(username: "myUsername", password: "myPassword") { [unowned self] (error: Error?) in
// ...
}
}
}
Note that in both cases we do exactly the same thing, but the lifetime is different and that changes everything in this case.
It is also possible to chain the arguments if you have several variables or properties that have been captured by the scope and their cycle is different or not:
final class UserManager {
static let shared: UserManager = UserManager()
}
final class LoginViewController : UIViewController {
let userManager = UserManager.shared
let repository = UserRepository()
@IBAction func login() {
repository.logInAsync(username: "myUsername", password: "myPassword") { [unowned self, weak userManager] (error: Error?) in
// ...
}
}
}
Debugging a memory leak #
As you may have understood, the keywords weak
and unowned
allow to avoid memory leaks. But then how to debug them?
The simplest tool you can use is the print
in the init
and deinit
methods of your various objects:
class MyClass {
init() {
print("MyClass is being initialized")
}
deinit {
print("MyClass is being deinitialized")
}
}
And don’t forget to watch the memory in XCode :
In a future article we will see how to use more powerful tools provided by XCode to help you.
Conclusion #
As you can see the devil is in the details. You can use closures and make strong cyclic references without knowing it and not have any problem.
But when it becomes critical it can be hard to correct these problems. So let’s stay vigilant.