Ghostboard pixel

Retain Cycles, Weak and Unowned in Swift

Retain Cycles, Weak and Unowned in Swift

Memory management, retain cycles and the usage of the keywords weak and unowned are a little bit confusing. On the other hand it’s very important to understand this topic properly because retain cycles are one of the major reasons for memory problems. But don’t worry! In this article you will learn everything you need to know.

Hint: This post has been updated to Swift 3 and iOS 10

Contents

We will start by discussing the basics of memory management in Swift. Based on this, we will learn what retain cycles are and how we can avoid them by using the keywords weak and unowned. After that, we will look at two common scenarios where retain cycles can occur. We will conclude this article by discussing two ways to detect retain cycles. As always it’s highly recommended to reproduce all steps in a playground.

[toc]

How does memory management work in Swift?

We start by looking at the basics of memory management in Swift. ARC (automatic reference counting) does most of the memory management work for you, which is very good news. The principle is very simple: By default, each reference, that points to an instance of a class, is a so-called strong reference. As long as there is at least one strong reference pointing to an instance, this instance will not be deallocated. When there’s no strong reference pointing to that instance left, the instance will be deallocated. Let’s take a look at the following example:

class TestClass {
    
    init() {
        print("init")
    }
    
    deinit {
        print("deinit")
    }
    
}

var testClass: TestClass? = TestClass()
testClass = nil

After creating the instance, the situation looks as follows:

retain cycle image 1

testClass has a strong reference to an instance of TestClass. If we now set this reference to nil, the strong reference is gone and since there is no strong reference left, the instance of TestClass gets deallocated:

retain cycle image 2

By the way, if you take a look at the console, you can see that everything is working fine because the deinit method will only be called by the system when the instance gets deallocated:
retain cycle log image 1

If the instance of TestClass was not deallocated, there wouldn’t be the message “deinit”. As we will discuss later, placing a log message inside of deinit is a very good way to observe the deallocation of an object.

What is a retain cycle?

So the principle of ARC works very well and most of the times you don’t have to think about it. However, there are situations where it doesn’t work and you have to help a little bit. Take a look at the following example:

class TestClass {
    
    var testClass: TestClass? = nil
    
    init() {
        print("init")
        
    }
    
    deinit {
        print("deinit")
    }
    
}

var testClass1: TestClass? = TestClass()
var testClass2: TestClass? = TestClass()

testClass1?.testClass = testClass2
testClass2?.testClass = testClass1

Again we have a class called TestClass. Now, we create two instances of that class and let these instances point to each other. The situation is visualised in the following picture:

retain cycle image 3

Now let us set our two variables to nil:

testClass1 = nil
testClass2 = nil

But the two instances won’t get deallocated! You can see this because there are not “deinit” messages in the console. Why is this happening? Let’s take a look at the situation:

retain cycle image 4

Each class has lost one strong reference, but there is still one left for each class! This means these instance won’t be deallocated. Even worse, we don’t have any reference to these classes left in our code. This is called a memory leak. If you have several leaks in your app, the memory usage of the app will increase every time you use the app. When the memory usage is to high, iOS will kill the app. That’s the reason why it’s so important to take care of retain cycles. So how can we prevent them?

weak

Using so-called weak references is a way to avoid retain cycles. If you declare a reference as weak, it’s not a strong reference. That means that this reference doesn’t prevent an instance from being deallocated. Let’s change our code and see what happens:

class TestClass {
    
    weak var testClass: TestClass? = nil //Now this is a weak reference!
    
    init() {
        print("init")
        
    }
    
    deinit {
        print("deinit")
    }
    
}

var testClass1: TestClass? = TestClass()
var testClass2: TestClass? = TestClass()

testClass1?.testClass = testClass2
testClass2?.testClass = testClass1

testClass1 = nil
testClass2 = nil

After this small change, we have the debugger output we expected in the first place:

retain cycle log image 2

The following picture shows the situation:

retain cycle image 5

Only weak references are left and the instances will be deallocated.

There is another important thing about weak you need to know: After deallocating an instance, the corresponding  variable will become nil. This is good because if we access a variable that’s pointing somewhere where no instance is left, there will be a runtime exception. Because only optionals can become nil, every weak variable has to be an optional.

unownend

Besides weak, there is a second modifier that can be applied to a variable: unowned. It does the same as weak with one exception: The variable will not become nil and therefore the variable must not be an optional. But as I explained in the previous paragraph, the app will crash at runtime when you try to access the variable after its instance has been deallocated. That means, you should only use unowned when you are sure, that this variable will never be accessed after the corresponding instance has been deallocated.

Generally speaking it’s always safer to use weak. However, if you don’t want the variable to be weak AND you are sure that it can’t be accessed after the corresponding instance has been deallocated, you can use unowned.

It’s a little bit like using implicitly unwrapped optionals and try!: You can use them, but in almost all cases it’s not a good idea.

Common scenarios for retain cycles: delegates

So what are common scenarios for retain cycles? One very common scenario is the usage of delegates. So image you have a view controllers that has a child view controller. The parent view controller sets himself as the delegate of the child view controller in order to get informed about certain situations:

class ParentViewController: UIViewController, ChildViewControllerProtocol {
    
    let childViewController = ChildViewController()
    
    func prepareChildViewController() {
    
        childViewController.delegate = self
    }
    
}

protocol ChildViewControllerProtocol: class {
    
    //important functions...
    
}

class ChildViewController: UIViewController {
    
    var delegate: ChildViewControllerProtocol?
    
}

If you are doing it this way, there will be a memory leak due to a retain cycle after popping the ParentViewController:

retain cycle image 6

Instead, we have to declare the delegate  property as weak:

weak var delegate: ChildViewControllerProtocol?

retain cycle image 6b

By the way, if you are looking at the definition of UITableView, you can see that delegate  and dataSource properties are also defined as weak:

weak public var dataSource: UITableViewDataSource?
weak public var delegate: UITableViewDelegate?

So remember that you should in almost all cases declare delegates as weak to prevent retain cycles.

Common scenarios for retain cycles: closures

Closures are another scenario where retain cycles are very likely to occur. Let’s take a look at the following situation:

class TestClass {
    
    var aBlock: (() -> ())? = nil
    
    let aConstant = 5
    
    
    init() {
        print("init")
        aBlock = {
            print(self.aConstant)
        }
    }
    
    deinit {
        print("deinit")
    }
    
    
}

var testClass: TestClass? = TestClass()
testClass = nil

We can see in the logs that the instance of TestClass will not be deallocated. The problem is, that TestClass has a strong reference to the closure and the closure has a strong reference to TestClass:

retain cycle image 7

You can solve this by capturing the self reference as weak:

class TestClass {
    
    var aBlock: (() -> ())? = nil
    
    let aConstant = 5
    
    
    init() {
        print("init")
        aBlock = { [weak self] in    //self is captured as weak!
            print(self?.aConstant)
        }
    }
    
    deinit {
        print("deinit")
    }
    
    
}

var testClass: TestClass? = TestClass()
testClass = nil

retain cycle image 7b

Now the instance will be deallocated as we can see in the logs:

retain cycle log image 1

However, there won’t always be a retain cycle when using closure! For example, if you are just locally using the block, there is no need to capture self a weak:

class TestClass {
    
    let aConstant = 5
    
    init() {
        print("init")
        let aBlock = {
            print(self.aConstant)
        }
    }
    
    deinit {
        print("deinit")
    }
    
    
}

var testClass: TestClass? = TestClass()
testClass = nil

The reason is, that there is no strong reference to the block, so the block will be deallocated after the method returns. The same holds true when use for example UIView.animateWithDuration:

class TestClass {
    
    let aConstant = 5
    
    init() {
        print("init")
    }
    
    deinit {
        print("deinit")
    }
    
    func doSomething() {
        UIView.animate(withDuration: 5) {
            
            let aConstant = self.aConstant
            
            //fancy animation...
        }
    }
    
    
}

var testClass: TestClass? = TestClass()
testClass?.doSomething()
testClass = nil

So if there’s no strong reference to the block, you don’t have to worry about a retain cycle.

You can of course also use unowned instead of weak, and our previous example is indeed a situation were you can do it safely:

class TestClass {
    
    var aBlock: (() -> ())? = nil
    
    let aConstant = 5
    
    
    init() {
        print("init")
        aBlock = { [unowned self] in
            print(self.aConstant)
        }
    }
    
    deinit {
        print("deinit")
    }
    
    
}

var testClass: TestClass? = TestClass()
testClass = nil

You can do it safely because if TestClass is deallocated so will the block. It can’t happen that the block tries to access the TestClass reference when it’s deallocated. However, in my opinion it’s good practice to use weak even in this situation. It’s a little bit more work because you have to deal with an optional, but it’s always safer.

Detecting retain cycles by using log messages in deinit

After we have learned what retain cycles are and what we can do to avoid them, we have to discuss how we can detect them. My favourite method is using log message in the deinit method. This is probably not a very elegant way to do so, but it’s a very effective one.

deinit {
      print("deinit")
}

If we don’t see the log message in the console although we are expecting the corresponding instance to be deallocated, we know that something is going wrong. It’s especially useful for view controllers – you should really put it in every view controller. For example, when we are popping a view controller, we know that the message should appear. If it does, we know that everything is fine. If it doesn’t, there’s some work to do for you.

Detecting retain cycles by using Instruments

People often talk about using instruments as a good way to detect retain cycle. In fact, I’m not using it very often because the workflow is a little bit special and for me looking at the log messages has the same value. Nevertheless, it’s a lot about personal preferences, so let’s talk about this way.

Image the following situation: We have an app, that has three view controllers and these controllers gets pushed by a navigation controller. So the first view controller pushes view controller two and view controller two pushes view controller three. After tapping the back button twice, we are again on view controller one and expecting the other two view controllers to be deallocated.

For this example I implemented a retain cycle between view controller two and view controller three.

Instrument can display leaks automatically, but unfortunately it’s not always working. But there’s a manual way that’s working very good.

In order for this way to work, you have to ensure that all of your view controller have the same suffix, for example “ViewController” or “VC”. Then we start instruments by choosing “Product -> Profile”. Choose the “Leaks” template:

Instruments template

You can see all allocations in the “Allocation summary” area and there is a lot going on. In order to have a much better overview, we add a new record type “ViewController (isSuffix)” in the right panel and remove the check at “*”:

Instruments record type

Now it will be much easier. We start the app by pressing the record button. For every view controller that is pushed, a new entry will appear:

Allocation Summary 1

SecondViewController has been pushed:

Allocation Summary 2

ThirdViewController has been pushed:

Allocation Summary 3

So far, so good. However, after pressing the back button, ThirdViewController is still alive:

Allocation Summary 4

This means ThirdViewController hasn’t been deallocated, so we know that something is going wrong and that there’s probably some kind of leak due to a retain cycle.

If all of your view controllers always have the same suffix, you can use this technique to go through the whole app and look for memory leaks.

[thrive_text_block color=”blue” headline=”Summary”]In this article you’ve learned the basics of memory management in Swift, what a retain cycle is and how you can prevent retain cycles to occur. In addition, you’ve learned how you can detect them. This is a lot of stuff and you’ll probably have to read the article a few times to understand all the details. But after applying these ideas to a real app, you will become very familiar with this topic. Please post your comments below.[/thrive_text_block]

References

Image: @ niroworld / shutterstock.com
Swift: weak and unowned
Building Memory Efficient Apps
The Swift Programming Language – Automatic Reference Counting