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.
- What’s a Strong Reference?
- Strong References in Action
- Weak vs. Strong References
- Strong vs. Weak vs. Unowned in Closures
- Further Reading
This tutorial is part of a series on memory management on iOS:
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.
- The local
bob
variable strongly references thePerson
instance named Bob - The
friend
property strongly references thePerson
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.
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 theweak
keyword. A weak property must be an optional, as well. Otherwise it cannot benil
. - 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?
- 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.
-
Delegates. A delegate property is
weak
by design, because it would cause a strong reference cycle otherwise. A table view controller’sdataSource
property is madeweak
. If it would be strong, the table view controller and data source would strongly hold onto each other and cause a memory leak. - 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:
- 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!
-
Weak: You use
weak
to create a weak reference to an object. This will automatically make that instance optional inside the closure. -
Unowned: Same as
weak
, except that the referenced instance is not an optional. You use this when you’re certain the instance cannot becomenil
.
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.
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
orunowned
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:
- Automatic Reference Counting (ARC) in Swift
- The Ultimate Guide to Closures in Swift
- Value Types vs. Reference Types In Swift
- Optionals in Swift: The Ultimate Guide
- How To: Escaping Closures In Swift With @escaping
- View Controllers for iOS & Swift Explained
- How To: Pass Data Between View Controllers In Swift
- Why App Architecture is Important