Memory Management in Swift: The Strong, The Weak and The Unowned

If you started your iOS development career in Objective-C, especially pre ARC Objective-C, you're probably very familiar with the above-mentioned concepts. If you started with Swift, some of them might be a bit murky, hopefully, by the end of this post, you'll get to know them a bit better.

If you started your iOS development career in Objective-C, especially pre ARC Objective-C, you’re probably very familiar with the above-mentioned concepts. If you started with Swift, some of them might be a bit murky, hopefully, by the end of this post, you’ll get to know them a bit better.

A Bit of History

I guess it’s best to start at the beginning. Most modern languages have a garbage collector. Every class that you create is created on what’s called the heap and every class takes up some memory space in your RAM. Garbage collectors’ role is to go through these objects and simply delete the ones that are not being used. Most garbage collectors are using some form of object graph traversal where a garbage collector would start with the root object and it would try and reach all the objects from the root object, once it’s finished traversing the object graph it would simply look at which objects were not touched by the traversal and delete them. If your garbage collector didn’t delete these objects, the memory footprint of your app would only increase, which would result in your app crashing due to lack of memory.

Garbage collectors are great and they make a developers’Β life a lot easier, that’s why we don’t have it on iOS πŸ™‚ , but they do add a bit of overhead on the CPU because traversing an object graph can be a bit costly. I remember one of the WWDC sessions where the guys mentioned that a garbage collector can add up to 20% CPU overhead on your app. And that’s one of the main reasons we don’t have it on iOS. The truth is, an app that’s using manual memory management running on the same hardware will always outperform an app that is using a garbage collector.

Retain Counts

Obviously, there must be a mechanism on iOS that removes unused objects from the heap, otherwise, we would end up eating up all the memory. The memory model that’s being used is actually quite simple. Every class has what’s called a retain count, this number simply represents a number of classes that are using your class. iOS simply monitors this number for each class and when it reaches ‘0’ it deallocates the class.

Manual Memory Management

The magic is in managing this number and in the old days we had to do it manually. We would increase the retain count by sending a ‘retain’ message to the object and we would decrease the retain count by sending a ‘release’ message to the object. On top of that, certain methods would increase the retain count, like adding a subview, so you would have to call the release right after adding a subview, or you would get a leak.

As you can see, it took a lot of effort to manage memory on iOS, so Apple jumped in and gave us a hand with counting.

Automatic Reference Counting

Or ARC, as it’s called, is nothing more than a clever tool that adds these retain/release messages automatically for us as part of the build phase. ARC will analyse the code and figure out where it needs to send a retain message and where it needs to send a release message. ARC is pretty smart, but it’s definitely not a garbage collector. Because it’s not a garbage collector, it’s not running while your code is executing, so it can’t traverse your object graph and it can’t detect what’s called islands on the graph. They’re simply subgraphs that are unreachable from the root object. We have a name for these subgraphs, we call them retain cycles and they result in memory leaks.

In order to avoid these subgraphs that result in memory leaks, ARC needs your help to better understand your object graph. We can help ARC out by giving it more info about the reference type. Keywords we use for that are strong, weak and unowned.

Strong References

This is the default reference type. Every property that is a reference type (read: class) will have a strong reference by default. And for most cases, this is just fine.

Here is a simple example of a strong reference type:

class Car {
    var name: String = "Default Name"
    var engine: Engine = Engine()
}

class Engine {
    var type: String = "Diesel"
}

let myCar = Car()
print("My new car engine: ", myCar.engine.type)

And the same example in a diagram:

A car has an engine property, which is of an Engine type, which is a strong reference. When you create a car class, an engine class is created as well. A car has a strong pointer to the engine, which increases engines’ retain count, when a car gets deallocated the pointer will disappear, decreasing the engines’ retain count to 0, and the engine will get deallocated as well. The relationship on this object graph is pointing only one way, the car knows about the engine, but not the other way around. So there are no problems. The problems occur if the engine has to know about the car. In other words if you create a two-way relationship.

Weak References

In iOS, we use a delegation pattern quite a lot. Two classes need to know about each other and if one class creates another and is a delegate of that class we have to be careful with the references. Let’s take our car for example. A car created an engine and if the engine class had a pointer to the car class and we assign a value to that car class we might end up with a memory leak if the car class is the same instance that created the engine (in most common scenarios it will be).

For example, this will create a memory leak:

class Car {
    var name: String = "Default Name"
    var engine: Engine = Engine()
}

class Engine {
    var type: String = "Diesel"
    var car: Car?
}

let myCar = Car()
myCar.engine.car = myCar

print("My new car engine: ", myCar.engine.type)

Assigning ‘myCar’ to the ‘myCar.engine.car’ property will create a retain cycle. Now we have two objects that increased each others’ retain count by 1, and there’s no way for them to decrease that count. Meaning the dealloc method will never get called on either of the objects. So we have a memory leak.

We can easily fix this leak by breaking the two-way connection that we created by using a weak reference. The difference between a weak and a strong reference is that a weak reference won’t increase the retain count of the object. Therefore, breaking the retain cycle. In our case, we don’t want the engine increasing the retain count of the car. The car class has an owner and the owner has a strong pointer to it, when the owner of the car nulls out the car pointer, we want both the car and the engine deallocated.

So, fixing the leak is quite simple:

class Car {
    var name: String = "Default Name"
    var engine: Engine = Engine()
}

class Engine {
    var type: String = "Diesel"
    weak var car: Car?
}

let myCar = Car()
myCar.engine.car = myCar

print("My new car engine: ", myCar.engine.type)

We simply changed the car variable to be of ‘weak’ type. This breaks the retain cycle. It’s usually considered best practice to have your parent marked as a weak. In our case it’s the car, but in a more common scenario, if you’re using a delegation pattern, you might want to mark your delegate as weak.

Now, when the car gets deallocated the car variable will be nilled out. Because the weak references are meant to be changed during the lifetime of the class, they will always be vars and never lets. Also, all weak references are of an optional type. Because they can be nilled.

Unowned References

You might have encountered a keyword unowned. In a nutshell, it kinda behaves like an implicitly unwrapped optional. It assumes the reference is there. Unowned references will not increase the retain count, just like weak references won’t. However, if you try and access an unowned reference and it’s not there, it will crash the app. Depending on your app, this might be exactly what you want, crashing an app is still better than running it in an inconsistent state. In most cases, though, it’s better to use weak instead.

Closures

Last but not the least, closures. There is a certain situation when a closure could retain a class. If a closure is saved as an instance variable and within the closure, you’re accessing the properties of the class, you have a retain cycle.

For example:

class Car {
    var name: String = "Default Name"
    var engine: Engine = Engine()
    
    lazy var carDetails: () -> String = {
        return "Name: " + self.name + " Engine: " + self.engine.type
    }
}

class Engine {
    var type: String = "Diesel"
    weak var car: Car?
}

let myCar = Car()
myCar.engine.car = myCar

print("My new car engine: ", myCar.engine.type)

print("Car details: ", myCar.carDetails())

We defined a lazy variable ‘carDetails’ which is actually a closure that takes no parameters and returns a string. Within the closure we reference self. Class ‘Car’ owns the closure ‘carDetails’ and it obviously holds a reference to it and ‘carDetails’ is holding a reference to its parent ( an instance of the ‘Car’ class) by accesing the parent via ‘self’ reference.

The relationship between the ‘Car’ and ‘carDetails’ is actually the same as a relationship between ‘Car’ and ‘engine’. If you think about this closure as a class that has been defined inline, things might look a lot clearer for you.

It’s easy to break this retain cycle. Just make ‘self’ within the closure weak:

lazy var carDetails: () -> String = {
    [weak self] in
    return "Name: " + self?.name ?? "" + " Engine: " + self?.engine.type ?? ""
}

At the top of our closure, we have an array. This array is called a capture list. You can “weakify” your pointer to self here. It’s an array, so you can add more pointers to it if you had to. Now self will be weak within the block and as a result is an optional type. We got rid of our retain cycle.

Conclusion

Memory management is important across all platforms, but it’s a bit more important on iOS than over other platforms just because we don’t have a garbage collector. With ARC our lives became a bit easier, and the new generations of developers are not exposed as much to memory management. I believe that knowing how memory management on iOS works will make you a better developer, I just hope I’ve managed to explain the basics in this post clearly.

Have a nice day πŸ™‚

Dejan.

More resources

10 thoughts on “Memory Management in Swift: The Strong, The Weak and The Unowned

  1. derp

    Hello Dejan,
    Thanks for the blog read, its interesting, but you missed one thing that is often used

    Lets take your last code example:

    suppose i add a function inside class Car with a blockClosure parameter.

    func somecarfunc()
    {
    let blockThatDoesNotRetain = {
    print(self.engine.type)
    }
    }

    then

    myCar.somecarfunc()

    then this code does not produces strong reference cycle and also it does not need to assign weak to self inside closure.

    So is this correct?

    Reply
    1. Dejan Agostini Post author

      Hi derp,

      Thanks for reading the article and thank you for pointing this out.

      Yes, your code is correct, you won’t retain self. Technically ‘blockThatDoesNotRetain’ would be on the stack so you couldn’t leak memory. This is a bit more advanced topic. I wanted to cover the basics of memory management in this article.

      I can’t say that I often use this pattern (that must be why it slipped my mind), I’ve seen it used and I’ve found the code to be a bit difficult to read, maybe it’s just me.

      Have a nice day πŸ™‚
      Dejan.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.