I am the author of iOS 17 Fundamentals, Building iOS User Interfaces with SwiftUI, and eight other courses on Pluralsight.
Deepen your understanding by watching!
Strong, Weak, and Unowned – Sorting out ARC and Swift
I’m willing to bet that a good number of Swift developers struggle with the particulars of how strong
, weak
, and unowned
affect the run-time behavior of their code. I, myself, wouldn’t want to have to give an explanation of the finer points of Automatic Reference Counting (ARC) if my life depended on it.
I wanted to stop being unsure about the implications of typing one of those three words before variable / constant declarations, so I finally pulled up the Apple Documentation on ARC and began trying to digest the semantics. This article is an attempt to share what got sorted out in my brain as it regards ARC and Swift.
This article is long enough that I thought, “Why don’t I summarize my conclusions up front and then let folks read about how I got there if they so-desire”. So here you go: Conclusions first!
Conclusions
- To determine if you even need to worry about
strong
,weak
, orunowned
, ask, “Am I dealing with reference types”. If you’re working with Structs or Enums, ARC isn’t managing the memory for those Types and you don’t even need to worry about specifyingweak
orunowned
for those constants or variables. Strong
references are fine in hierarchical relationships where the parent references the child, but not vice-versa. In fact,strong
references are the most appropraite kind of reference most of the time.- When two instances are optionally related to one another, make sure that one of those instances holds a
weak
reference to the other. - When two instances are related in such a way that one of the instances can’t exist without the other, the instance with the mandatory dependency needs to hold an
unowned
reference to the other instance.
To see how I arrived at this set of conclusions, read on or jump around!
ARC and memory management
For many, much of “memory management” feels like a huge black box.
The mystical nature of memory management is exaggerated by the fact that many of the languages we’re familiar with abstract it away. For the most part, we can simply write code and not think too hard about the number of bytes our object instances are using or how they get cleaned up when they’re done being used. It just works.
Automatic Reference Counting (ARC) is one of those abstractions over managing memory. It’s the methodology Apple employs to free up memory when class instances are finished using it.
ARC only applies to classes (so not structs or enums), because Automatic Reference Counting applies only to reference Types. Structs & enums are value Types, so ARC does not manage the memory associated with instances of those Types.
The question that begins the discussion of our featured keywords is, “How does ARC know when an instance is “finished using” the memory it borrowed?”
Strong references
Strong
, weak
, and unowned
are keywords that describe the nature of a reference in the ARC paradigm.
All references are strong
references by default unless otherwise specified. Most of the time, this is the right thing to do. Strong
is always implied when you declare a variable or constant. You don’t need to type strong
in Swift.
So what does the “strength” of a reference have to do with how ARC manages memory?
ARC doesn’t free up the memory being used by a class instance until all strong
references to that instance are broken. How are strong
references broken?
Strong
references can be broken…
- When a variable that references an instance of something is set to
nil
- When a parent variable that holds a reference to a child class instance is to
nil
it will break the reference to the parent and the child - When a variable or constant goes out of scope – for example, if something gets initialized inside a control-flow code segment like an
if
/else
or inside afor
loop, when execution moves past that code segment, the reference is broken and the memory is freed by ARC
Everything in ARC’s memory management model hinges on the number of strong
references are connected to an instance. The moment that the number of strong
references counts down to zero, the memory where that instance was stored is freed.
So where does weak
and unowned
come in?
Weak and unowned references
Weak
and unowned
references come into the picture when we start talking about the relationships that emerge between class instances.
Relationships between instances
Relationships between classes are core to the Object-Oriented paradigm. Whether you plan them or not, these relationships exist, and they affect the ARC memory management model.
So which kinds of relationships exist? Which ones warrant the use of weak
and unowned
?
Hierarchical relationships
Much of our programming in Swift involves hierarchical relationships. Things are often modeled in such a way that one class instance holds strong
references to one or more child class instances, and those child class instances hold one or more strong
references to other child class instances, and so on. The strong
reference relationship in this type of situation flows in one direction: from parent to child.
In this arrangement, strong
references are normal and fine. But what happens if, say, a child class were to hold a reference back to its parent? This is where we can get into trouble with ARC.
Optional, mutually dependent relationships
By the end of this section, my goal is to demonstrate the role of weak
references.
When class instances depend on each other, that is, they both hold a reference to one another, it can be said that the instances are mutually dependent. Sometimes, the dependence isn’t required – in these cases, it makes sense for one instance to exist without a reference to the other, and vice-versa.
If we were talking database cardinality, we might say that the relationship is 0:1 both ways.
An example is always nice to have before us – This past week, a tornado hit a zoo here in Oklahoma, and a few exotic animals escaped, so the theme is on my mind.
In a zoo, an Animal
lives in an Exhibit
. Sometimes Exhibits
may be vacant. They could be cleaning it, or the Animal
that lived there is sick or being moved, or a tornado hits it, etc.
Likewise, an Animal
may not live in an Exhibit
. It could be held in a temporary location while its Exhibit
is being cleaned, or be in the process of being treated or transferred to another location, or it could have escaped the zoo due to a tornado, etc.
We could model this example as follows:
1class Animal {
2 let name: String
3 let species: String
4 init(name: String, species: String) {
5 self.name = name
6 self.species = species
7 }
8
9 var exhibit: Exhibit? // notice the optional nature of exhibit
10}
11
12class Exhibit {
13 let title: String
14 init(title: String) { self.title = title }
15
16 var animal: Animal? // notice the optional nature of animal
17}
Let’s consider the situation where an Animal
is living in an Exhibit
. It might be nice to traverse a relationship between them to get information about one or the other. So as we instantiate an Animal
and an Exhibit
, the next immediate step would be to assign the instances to each other’s corresponding property.
Now consider the situation that happened this week – a tornado hits the zoo and the Animal
escapes from its Exhibit
. Say that unlike this week, the Animal
isn’t recovered (they actually got the animals that escaped back). Suppose that it roams the plains of Oklahoma in freedom. The zoo, in turn, has to close the Exhibit
. To model this, we may simply set the Animal
instance to nil and the Exhibit
instance to nil.
Memory leak warning
The example just given is one that shows how it is possible to create a memory leak in your application. How?
It all has to do with ARC – remember that it only frees up memory for a class instance when there are zero references to it.
In our Animal
– Exhibit
example, both instances referenced each other. When we set the Animal
instance to nil, the Exhibit
instance still held a reference to it through its animal
property.
In turn, since that Animal
instance is still around, it holds a reference to the Exhibit
instance through its exhibit
property. So when we set the Exhibit
instance to nil, the Animal
still holds on to it.
Now we have a dilemma. While the Animal
and the Exhibit
continue to reference each other (so neither instance’s memory can be freed), nothing else does – there’s no way to access either instance any more. Thus, a memory leak is created.
But there’s hope! In this “Optional, mutually dependent relationship” scenario, this is where the keyword weak
comes into play.
Breaking strong reference cycle
The situation just described is what’s known as a “strong reference cycle”.
Thankfully, there’s a way to break those cycles and avoid memory leaks.
In the situation where that optional mutually dependent relationship exists, such as between an Animal
and an Exhibit
, changing one of those instances reference to the other from strong
to weak
will break the cycle.
It doesn’t really matter which class holds the weak reference, just as long as one of them does.
1class Animal {
2 let name: String
3 let species: String
4 init(name: String, species: String) {
5 self.name = name
6 self.species = species
7 }
8
9 var exhibit: Exhibit? // we'll hold a strong reference to the exhibit
10}
11
12class Exhibit {
13 let title: String
14 init(title: String) { self.title = title }
15
16 weak var animal: Animal? // but notice - we're holding a _weak_ reference to the animal
17}
Making the Exhibit's
animal
property hold a weak
reference to an Animal
instance eliminates the possibility of a strong reference cycle.
So what about unowned
?
Mandatory, one-way dependent relationships
By the end of this section, my goal is to demonstrate the role of unowned
references.
There is one final category of relationship that pertains most immediately to the topic of ARC. There are times when two class instances are related to one another, but one of those instance cannot exist without the other.
Let’s continue with the zoo theme. Suppose that our zoo issues AnnualPasses
to Visitors
. A Visitor
can come to the zoo without an AnnualPass
, of course, and can thus exist on its own and happily enjoy the exhibits. An AnnualPass
, however, is always issued to a Visitor
and cannot exist without being associated with one.
To model this kind of relationship, we may define some classes as follows:
1class Visitor {
2 let name: String
3 var annualPass: AnnualPass?
4 init(name: String) {
5 self.name = name
6 }
7}
8
9class AnnualPass {
10 let number: UInt64
11 unowned let passholder: Visitor // notice the use of an _unowned_ reference
12 init(number: UInt64, passholder: Visitor) {
13 self.number = number
14 self.passholder = passholder
15 }
16}
Notice a couple of things:
- The
Visitor
holds an optionalstrong
reference to anAnnualPass
instance - The
AnnualPass
holds a non-optionalunowned
reference to aVisitor
instance
Modeling things this way also eliminates the possibility of a strong reference cycle by allowing the strong
reference count to reach zero when a Visitor
variable is set to nil.
Conclusions
I know I stated these at the beginning, but I’ll restate them here just for completeness…
- To determine if you even need to worry about
strong
,weak
, orunowned
, ask, “Am I dealing with reference types”. If you’re working with Structs or Enums, ARC isn’t managing the memory for those Types and you don’t even need to worry about specifyingweak
orunowned
for those constants or variables. Strong
references are fine in hierarchical relationships where the parent references the child, but not vice-versa. In fact,strong
references are the most appropraite kind of reference most of the time.- When two instances are optionally related to one another, make sure that one of those instances holds a
weak
reference to the other. - When two instances are related in such a way that one of the instances can’t exist without the other, the instance with the mandatory dependency needs to hold an
unowned
reference to the other instance.