Skip to main content
Kelvas blog
  1. Posts/

Swift: weak and unowned

·1396 words·7 mins

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.

It is important to note that by default in Swift a reference is noted as strong.
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?

%%{init: {'theme': 'dark', 'themeCSS': 'svg {background-color: black}'}}%% graph LR; Soldier-->|Strong reference|Weapon Weapon-->|Strong reference|Soldier

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.

%%{init: {'theme': 'dark', 'themeCSS': 'svg {background-color: black}'}}%% graph LR; Soldier-->|Strong reference|Weapon Weapon-.->|Weak reference|Soldier

This also implies that a weak reference cannot be used with the let keyword since it will at some point be passed to nil.

weak with let create compilation error

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!

weak with protocol compilation error

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.

%%{init: {'theme': 'dark', 'themeCSS': 'svg {background-color: black}'}}%% graph LR; Player-->|Strong reference|Skill Skill-.->|Unowned reference|Player

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 :

XCode memory pane

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.

Sources #