A memory leak occurs in an iOS app when memory that is no longer in use is not properly cleared and continues taking up space. This can hurt app performance, and eventually, when the app runs out of available memory, will cause a crash. In order to better understand how memory leaks happen, it's important to first know how iOS apps manage their memory.

Automatic Reference Counting

In iOS, shared memory is managed by having each object keep track of how many other objects have a reference to it. Once this reference count reaches 0, meaning there are no more references to the object, it can be safely cleared from memory. As the name would imply, this reference counting is only necessary for reference types, while value types require no memory management. Reference counting was done manually in the past by calling retain on an object to increase its reference count and then calling release on the object to decrease its reference count. This code was almost entirely boiler plate, boring to write and mistake prone. So like most menial tasks, it became automated through Automatic Reference Counting (ARC) which makes the necessary retain and release calls on your behalf at compile time.

Even though ARC mostly decreased the need to worry about memory management, it can still create Swift memory leaks whenever there are circular references. For example, say that we have a Person class that has a property for an apartment and the Apartment class has a Person property named tenant:

class Person {
    var name: String
    var age: Int
    init(name: String, age: Int) {…}
    var apartment: Apartment?
}


class Apartment {
    let unit: String
    init(unit: String) {…}
    var tenant: Person?
}

let person = Person(name: “Bob”, age: 30)
let unit4A = Apartment(unit: “4A”)
person.apartment = unit4A
unit4A.tenant = person

When we create a new Person and Apartment and assign them to each others' properties, they are now referencing each other in a circular fashion. Circular references can also happen with more than two objects. In this situation, both the Person and the Apartment are holding a reference to the other, so in this case neither will ever get down to a reference count of 0 and will hold each other in memory, even though no other objects have references to either of them. This is known as a retain cycle and causes a memory leak.

Related: Three features that'd be real Swifty

The way that ARC deals with retain cycles is to have different types of references: strong, weak and unowned. Strong references are the kind we have already talked about, and these references increase an object's reference count. Weak references, on the other hand, while still giving you reference to the object, don't increase its reference count. So if we were to take the tenant property on Apartment and make it a weak reference instead, it would break our retain cycle:

class Apartment {
    let unit: String
    init(unit: String) {…}
    weak var tenant: Person?
}

Now our Person object is still holding its Apartment in memory, but the reverse is no longer true. So when the last strong reference to the Person is gone, its reference count will drop to 0 and it will release its Apartment, whose reference count will also be dropped to 0, and they can both be correctly cleared from memory.

In Swift, weak references must be optional vars because, if you are not taking responsibility for keeping an object in memory, you can't guarantee that the object won't change or leave memory. This is where the third type of reference comes into play. unowned references are like weak references, except that they can be non-optional lets, but these should only be used when you are sure that the object should never be nil. Like force unwrapped optionals you are telling the compiler "Don't worry, I got this. Trust me." But like weak references, the unowned reference is doing nothing to keep the object in memory and if it leaves memory and you try to access it, your app will crash. Again, just like force unwrapped optionals.

While retain cycles are easy to see with two objects pointing at each other, they are harder to see when closures in Swift are involved, and this is where I've seen most retain cycles happen.

Avoiding Retain Cycles In Closures

It's important to remember that closures are reference types in Swift and can cause retain cycles just as easily, if not more easily, as classes. In order for a closure to execute later, it needs to retain any variables that it needs for it to run. Similarly to classes, a closure captures references as strong by default. A retain cycle with a closure would look something like this:

class SomeObject {
    var aClosure = {
        self.doSomething()
    }
    ...
}

Related: When should I use blocks and closures or delegates for callbacks?

In this case, the SomeObject class has a strong reference to aClosure and aClosure has captured self (the SomeObject instance) strongly as well. This is why Swift always makes you add self. explicitly while in a closure to help prevent programmers from accidentally capturing self without realizing it, and therefore most likely causing a retain cycle.

To have a closure capture variables as weak or unowned, you can give the closure instructions on how to capture certain variables:

class SomeObject {
    var aClosure = { [unowned self, weak delegate = self.delegate] in
        self.doSomething()
        delegate?.doSomethingElse()
    }
    ...
}

More on closure syntax here.

Most closure examples that I've seen in tutorials or examples seem to capture self as unowned and call it a day, since capturing self as weak makes it optional, which can be more inconvenient to work with. But as we learned before, this is inherently unsafe since there will be a crash if self is no longer in memory. This isn't much different than just force unwrapping all of your optional variables because you don't want to do the work to safely unwrap them. Unless you can be sure that self will be around as long as your closure is, you should try to capture it weak instead. If you need a non-optional self inside your closure, consider using an if let or guard let to get a strong, non-optional self inside the closure. Because you made this new, strong reference inside the Swift closure, you won't be creating a retain cycle since this reference will be released at the end of the closure:

var aClosure = { [weak self] in
    if let strongSelf = self {
        doSomethingWithNonOptional(strongSelf)
        doSomethingElseNonOptional(strongSelf)
    }
}

or even better:

var aClosure = { [weak self] in
    guard let strongSelf = self else { return }

    doSomethingWithNonOptional(strongSelf)
    doSomethingElseNonOptional(strongSelf)
}

 

Capturing Self Strongly

Although it is good practice to capture self weakly in closures, it is not always necessary. Closures that are not retained by the self  can capture it strongly without causing a retain cycle. A few common examples of this are:

Working with DispatchQueues in GCD

DispatchQueue.main.async {
    self.doSomething() // Not a retain cycle
}

Working with UIView.animate(withDuration:)

UIView.animate(withDuration: 1) {
    self.doSomething() // Not a retain cycle
}

The first is not a retain cycle since self does not retain the DispatchQueue.main singleton. In the second example, UIView.animate(withDuration:) is a class method, which self also has no part in retaining.

Capturing self strongly in these situations will not cause a retain cycle, but it also may not be what you want. For example, going back to GCD:

DispatchQueue.main.asyncAfter(deadline: .now() + 60) {
    self.doSomething()
}

This closure will not run for another 60 seconds and will retain self until it does. This may be the behavior you want, but if you wanted self to be able to leave memory during this time, it would be better to capture it weakly and only run if self is still around:

DispatchQueue.main.asyncAfter(deadline: .now() + 60) { [weak self] in
    self?.doSomething()
}

Another interesting place where self would not need to be captured strongly is in lazy variables, that are not closures, since they will be run once (or never) and then released afterwards:

lazy var fullName = {
  return self.firstName + " " + self.lastName  
}()

However, if a lazy variable is a closure, it would need to capture self weakly. A good example of this comes from The Swift Programming Language guide:

class HTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
}

This is also a good example of an instance that is reasonable to use an unowned reference, as the asHTML closure should exist as long as the HTMLElement does, but no longer.

TL;DR

When working with closures in Swift, be mindful of how you are capturing variables, particularly selfs. If self is retaining the closure in any way, make sure to capture it weakly. Only capture variables as unowned when you can be sure they will be in memory whenever the closure is run, not just because you don't want to work with an optional self. This will help you avoid memory leaks, which leads to poor performance and crashes.

 

More from Marcus on Swift: when to use value and reference types

CLICK HERE

stable-kernel-mobile-app-development

About The Author

Marcus Smith is a Myrtle Beach native who graduated from Furman University. Following college, he spent 3 years in Japan where he taught English and met his wife of 2 years. He came to stable|kernel after working for BMW as a software engineer and running his own software development company called Frozen Fire Studios, though he’s been programming since he was 11. Marcus enjoys traveling, learning new languages, and taking walks.