Weak vs. Strong References in Swift

Written by Reinder de Vries on October 21 2020 in App Development, iOS, Swift

Weak vs. Strong References in Swift

Creating a strong reference to an instance in Swift, means that the instance is kept in the iPhone’s memory until you’re done with it. You can also create weak references. Both are part of the memory management mechanism called ARC. In this tutorial, we’ll discuss how weak and strong references work on iOS with Swift.

Here’s what we’ll get into:

  • What’s a strong reference, and why do we need it?
  • Weak vs. strong references and how they affect your code
  • How to avoid strong reference cycles between objects and closures
  • Strong vs. weak vs. unowned in closures
  • When to use [weak self] and [unowned self]

Ready? Let’s go.

  1. What’s a Strong Reference?
  2. Strong References in Action
  3. Weak vs. Strong References
  4. Strong vs. Weak vs. Unowned in Closures
  5. Further Reading

What’s a Strong Reference?

Strong references increase the retain count of instances they reference (by 1). This prevents the Automatic Reference Counting (ARC) from removing the object from memory, as long as the object is in use.

Let’s take a quick step back. In the previous tutorial in this series we’ve discussed why memory needs to be managed, and how that works with ARC.

Memory needs to be managed because iOS needs to know which data it can free from memory, to make space for new data. The most effective way to manage memory is to keep track of which objects are being used, and if no one is using an object, to remove it from memory.

This “keeping track” happens via an object’s retain count property. It’s increased by 1 for strong references, kept the same for weak references, and decreased by 1 when a reference is removed. This happens almost automatically.

Note: ARC only affects reference types, like classes. It does not affect value types, like structs. Value types are copied when they’re passed/referenced, so beyond the local scope there’s nothing to retain in memory.

Strong References in Action

Let’s take a look at an example of a strong reference. Check this out:

class Person {
    var friend:Person?
    let name:String

    init(name: String) {
        self.name = name
        print("\(name) is initialized")
    }

    deinit {
        print("\(name) is deinitialized")
    }
}

In the above code we’ve created a class called Person. It’s got an initializer that takes a name of type String. Both the init and deinit will tell us when an instance of class Person is initialized and deinitialized, and subsequently deallocated and removed from memory.

The Person class also has a property friend of type Person. This property will create a strong reference to any object that’s assigned to it.

var bob = Person(name: "Bob")
bob.friend = Person(name: "Alice")

What’s going on here? It’s simple: we’re creating an instance of Person and assign it to the variable bob. Then, we create another instance of Person, and assign it to bob.friend, i.e. the friend property of bob.

That gives us the following output:

Bob is initialized
Alice is initialized

We’ve got 2 strong references in this example so far.

  1. The local bob variable strongly references the Person instance named Bob
  2. The friend property strongly references the Person instance named Alice

That means that Alice will only get deallocated when Bob is deallocated, because Bob is strongly holding onto Alice. So far so good, and by the end of the code, both the Person instances assigned to bob and bob.friend get deallocated because the code ends and quits.

At any point, we can explicitly deallocate Alice like this:

bob.friend = nil
// Output: Alice is deinitialized

If bob would have been an optional, i.e. Person?, we can see how setting bob to nil deallocates both Bob and Alice. Check this out:

var bob:Person? = Person(name: "Bob")
bob?.friend = Person(name: "Alice")
bob = nil
// Outputs: ··· Bob is deinitialized, Alice is deinitialized

When you know how ARC works, you can assert the following truths about strong references:

  • In Swift, a strong reference is the default. When you create a variable, constant or property for a reference type, a strong reference is created by default. This is also true for passing references into functions or closures.
  • An instance is only deallocated when its retain count is zero; when no other objects hold onto it. A strong reference increases the retain count by one, so the strong reference must be broken before the instance it references can be deallocated.

You’ve seen both principles in the last code example. Bob held strongly onto Alice, so Alice was only deallocated after Bob was.

Semantics are important in communication, but jargon occasionally gets in the way of a good example. You can see a reference as a “link” between an object and a variable name, like var name = object This reference can be strong (or weak). It’s called a reference because multiple variables, constants, properties can keep a “link” to the same object in memory. The core of knowing when to remove that object, is keeping track of who’s holding on (i.e., ARC). When no one is holding on, the object is removed to free up space.

Get hired as an iOS developer

Learn to build iOS 14 apps with Swift 5

Sign up for my iOS development course, and learn how to start your career as a professional iOS developer.

Weak vs. Strong References

The opposite of a strong reference is a weak reference. In Swift, strong references are the default, so to make a reference weak you can use the weak keyword. Unlike strong references, a weak reference does not affect an instance’s retain count. It does not hold onto the object.

Before we dive into an example, let’s briefly discuss why we have weak references at all. You already know we need ARC to manage memory, and we need to manage memory to know what data to remove to free up space. The only reason you need to use weak in Swift coding is to avoid strong reference cycles.

In short, a strong reference cycle or “retain cycle” is 2 instances holding onto each other. They cannot be removed from memory, which causes a memory leak, which could crash your app, which is a bad user experience. We’ll focus on resolving strong reference cycles in the next and last tutorial in this series.

Back to weak. Check this out:

class Person {
    weak var friend:Person?
    let name:String

    ···
}

We’ve got the same Person class as before, but now the friend property is explicitly marked with the weak keyword. This designates the friend property as a weak reference, i.e. anything you assign to .friend is not held on strongly.

var bob:Person? = Person(name: "Bob")
bob?.friend = Person(name: "Alice")

The above code results in the following output:

Bob is initialized
Alice is initialized
Alice is deinitialized

What’s going on here? It looks like Alice is immediately deinitialized after initialization. That makes sense! Bob doesn’t hold on to Alice – because of the weak reference – so Alice is removed from memory, because the instance’s retain count stays zero.

You wouldn’t do this in practical iOS development, though, just like you wouldn’t code a Bob and an Alice. When you run the above code in Xcode, you’ll even get a warning: Instance will be immediately deallocated because property friend is weak.

Based on the example, we can make 2 assertions about weak references:

  • You can mark a reference as weak with the weak keyword. The default is strong, hence usage of the weak keyword. A weak property must be an optional, as well. Otherwise it cannot be nil.
  • An instance’s retain count is not affected by a weak reference, as opposed to a strong reference. Beyond an instance’s existing retain count, a weak reference does not keep the instance in memory.

It’s worth it to note here that marking a property as weak will not exclude the referenced object from memory management. As we’ve discussed, each strong reference increases an instance’s retain count – and weak does not. That doesn’t mean the referenced instance isn’t strongly referenced– and subsequently retained in memory – elsewhere!

When is weak used in practical iOS development?

  1. Objects. This happens when you inadvertedly create a link between 2 instances, for example a view and its associated parent view controller. There’s a reference going “down” from the VC to the view, and “up” from the view to the VC. For example, so you can change or call the view controller from within the view. It’s a questionable design choice, but it happens.
  2. Delegates. A delegate property is weak by design, because it would cause a strong reference cycle otherwise. A table view controller’s dataSource property is made weak. If it would be strong, the table view controller and data source would strongly hold onto each other and cause a memory leak.
  3. Closures. A closure can capture variables, properties, etc. from its surrounding scope. When the closure outlives the scope it was declared in, it can strongly hold onto objects from that scope. This can cause a strong reference cycle.

The most common scenarios for weak in day-to-day iOS development are objects that reference each other, like a two-way link between objects or view controllers, and poor capturing in closures. Let’s discuss those next!

On Outlets: An outlet property can be made weak by design. Not doing so could cause a strong reference cycle between a view controller and a UI element, especially if the UI element accidentally outlives its view controller. This is a subject of debate, and what’s conventional around this has changed in the past. I’m personally in favor of weak, optional outlets as a precaution against accidental retain cycles down the line.

Strong vs. Weak vs. Unowned in Closures

Closures are self-contained blocks of code you can pass around. You can see them as functions you can assign to variables, and pass around in your code, to execute them at a later point. Modern Swift and SwiftUI rely heavily on the use of closures, because they’re super powerful.

A closure can capture values from their surrounding scope. You can use variables and properties from “outside” the closure, even though a closure may get executed at another point in your code.

Here’s an example:

let imageView:UIImageView = ···

let task = session.dataTask(with: "https://imgur.com/cats.png", completionHandler: { data, response, error in

    imageView.image = UIImage(data: data)
})

In the above code, we’ve got an image view, an async URLSession data task that downloads an image, and a closure that’ll load and display the image upon completion of the download task.

The closure is the stuff between the squiggly brackets. It’s a completion handler, so it’ll get called by the URLSession component when downloading the image is finished. The closure is passed into the URLSession component, stored there, and called at a later point. That’s how closures work!

What’s “captured” here? A reference to the UIImageView instance – assigned to imageView – is captured by the closure. It means that the UIImageView instance is available for use after the scope that surrounds the closure (i.e., a function) is gone.

And you’ve guessed it: when a closure captures a reference type from its surrounding scope, the closure creates a strong reference to the captured instance. This increases the instance’s retain count, which ensures that the instance is still stored in memory by the time the closure is called.

This works exactly the same as with strong or weak properties. Just as with a property, you can create a strong reference cycle between a closure and an instance when you manage memory poorly. Just as with properties, we can use weak to break this cycle.

Strong, Weak, Unowned and Capture Lists

Unlike properties, closures have their own specific syntax to designate a captured value as weak, called a capture list. Here’s an example:

let imageView:UIImageView = ···

let task = dataTask(with: ···, completionHandler: { [weak imageView] data, response, error in

    imageView?.image = UIImage(data: data)
})

In the above code, you we use the capture list [weak imageView] to indicate that the reference to imageView should be weak. Just as before, strong is the default here.

You’ve got 3 options in total:

  1. Strong: This is the default, and you don’t use a capture list. Just use the object in the closure, and you’re good. This works in most scenarios!
  2. Weak: You use weak to create a weak reference to an object. This will automatically make that instance optional inside the closure.
  3. Unowned: Same as weak, except that the referenced instance is not an optional. You use this when you’re certain the instance cannot become nil.

When do you use strong, weak or unowned references with closures? Let’s establish a baseline first, and check out a common use of capture lists.

class ImageVC: UIViewController 
{
    var imageView = ···

    func getImage()
    {
        ··· dataTask(with: ···, completionHandler: { data, response, error in
            imageView?.image = UIImage(data: data)
        })
    }
}

In the above code, we’ve created a view controller. At some point, its getImage() function is called. This function makes an async HTTP request to download an image, which is subsequently shown in the image view.

Right now, nothing is technically wrong with the above code. But what if, for example, the view controller is part of a navigation controller?

Note: Just as before, you technically only need weak to avoid strong reference cycles. Don’t use it from the get-go; only when you need to, because strong is the (sensible) default.

Reference Cycles in Closures

At some point, the HTTP request is pending, and the user navigates away from the view controller. The view controller would normally get removed from the view stack, and deallocated. Because we’ve created a strong reference to imageView however, the view controller cannot get deallocated until the strong reference is removed.

Due to a hypothetical bug somewhere in the dataTask(···) function, the closure isn’t deallocated, but remains assigned to some strong property. This creates a strong reference cycle between the closure and the view controller. Neither can get deallocated, because they both hold on strongly to each other.

At this point, you can either mark imageView as weak or unowned. Of course, you’d also have to inspect the code that calls and nulls the completion handler. Maybe it needs to be referenced strongly, so you can set it to nil after it’s used.

Weak vs. Unowned

What’s unowned for, then? There’s a lot of confusion around weak vs. unowned, especially with [weak self] and [unowned self].

Before we get into that, it’s important to point out that you generally avoid marking self as weak or unowned in its entirety. When you use imageView in a closure, for example, then only use imageView in a capture list. Even though with imageView you implicitly use self, using [weak imageView] instead of [weak self] makes what you’re capturing crystal clear.

The guiding principle in using unowned is the lifetime of the referenced object. Unlike weak, unowned does not make the referenced instance optional. With unowned, you can avoid some of the overhead that comes with dealing optionals.

The cost of unowned is that you need to be certain that the referenced instance does not become nil. The easiest way of doing this, is to make sure that the referenced instance outlives the closure. In other words, if you must use [unowned self] in a view controller, make sure that the view controller exists until or after the closure is deallocated.

A helpful way to remember the difference between weak and unowned lies within the meaning of those words. “Weak” implies a reference, i.e. objects with a link between one another that live independently. “Unowned” means that the closure does not own the instance it references. If the closure doesn’t own what it uses, it better finish its business before that object ceases to exist.

Get hired as an iOS developer

Learn to build iOS 14 apps with Swift 5

Sign up for my iOS development course, and learn how to start your career as a professional iOS developer.

Further Reading

In this tutorial, we’ve discussed how weak and strong references work. They’re part of memory management, and they’re required to properly free up memory in your iOS app. A strong reference can cause a retain cycle, which you can break with weak. As we’ve discussed, this typically happens between objects or closures.

Here’s a quick summary of the principles we discussed:

  • In Swift, a strong reference is the default, for variables, properties, constants, passing into functions (depending on what you do with it), and for closures.
  • With ARC, an instance is only deallocated when its retain count is zero. A strong reference increases the retain count by 1, a weak reference does not.
  • You can mark a reference as weak with the weak keyword. This will make the instance an optional. An instance’s retain count is not affected by a weak reference.
  • You typically encounter strong references between instances, ex. with delegate properties or two-way links between objects, or within closures. A closure captures values from its surrounding scope, and creates strong references to them.
  • You can manage these references with a capture list, like [weak imageView]. You can choose between a strong, weak or unowned reference. A weak reference becomes an optional, and an unowned reference does not. You use unowned when the referenced instance outlives the closure.

The next tutorial in this series is: How To Fix Strong Reference Cycles in Swift

Want to learn more? Check out these resources:

Reinder de Vries

Hi, I'm Reinder.
I help developers play with code.

Get the Weekly

Get iOS/Swift tutorials and insights in your inbox, every Monday.
  • This field is for validation purposes and should be left unchanged.

Most Popular

Browse Topics

Swift Sandbox

Code Swift right in your browser!
Go to the Swift Sandbox

Reinder de Vries

Reinder de Vries

Reinder de Vries is a professional iOS developer. He teaches app developers how to build their own apps at LearnAppMaking.com. Since 2009 he has developed a few dozen apps for iOS, worked for global brands and lead development at several startups. When he’s not coding, he enjoys strong espresso and traveling.

×

Start your iOS career
Learn how in my free 7-day course

  • This field is for validation purposes and should be left unchanged.

No spam, ever. Unsubscribe anytime. Privacy Policy