Swift uses Automatic Reference Counting to keep track of how many instances of a class have been created.
For example say we have a class Person
.
class Person {
let name: String
init(name: String) {
self.name = name
}
}
When we create three Optional references to Person
these are initially all nil
.
var reference1: Person?
var reference2: Person?
var reference3: Person?
It's not until we actually instantiate an instance of person that ARC kicks in, tracks this instance, and ensures Person
is kept in memory and not deallocated.
reference1 = Person(name: "John Appleseed")
If we assign the same Person
instance to two more variables, two more strong references to that instance are created:
reference2 = reference1
reference3 = reference1
And we now have three strong references to this single Person
instance.
If we break two of these strong references (including the original), a single strong reference remains, and the Person
instance is not deallocated:
reference1 = nil
reference2 = nil
Until we break the final one
reference3 = nil
ARC tracks the number of references to instances of objects you create and then deallocates them when no longer needed.
It is possible however to write code such that a class never gets to the point where it has zero strong references. This can happen if two classes hold strong references to each other, such that each instances keep the other alive. This is known as a strong reference cycle and this is something we want to avoid at all costs.
For example, consider this Person
object with holds a reference to an Optional Apartment
and an Apartment
which in turn holds a strong reference to the Person
.
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
Both of these variables have an initial value of nil, by virtue of being optional.
var john: Person?
var unit4A: Apartment?
But as soon as we create them, and then assign them to each other, they now have strong references to each other.
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
At this point we are in trouble. Because even if we nil out both variables, the underlying object instances both point to each other. That means the reference count does not drop to zero, and the instances are not deallocated by ARC.
john = nil
unit4A = nil
Swift provides two ways to resolves these strong cycles between classes
- weak
- unowned
Both weak
and unowned
do not create strong holds on objects when used (i.e. they don't increment the retain count in order to prevent ARC from deallocating the referred object).
A weak
refence allows the possibility of it to become nil
(this happens automatically when the reference object is deallocated), therefore the type of your property must be Optional - so you, as a programmer, are obliged to check it before you use it (compiler will force you to).
An unowned
reference presumes that your reference will never become nil
during it's lifetime. An unowned reference must be set during initialization - this means that the reference will be defined as a non-optional type that cna be used safely without checks.
From the Apple docs
Use a weak reference whenever it is valid for that reference to become nil at some point during it's life time. Conversely, use an unowned reference when you know that the reference will never be nil once it has been set during initialization.
We can break the strong dependency on Apartment
to Person
by making it's reference to Person
weak
.
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
}
class Apartment {
let number: Int
init(number: Int) { self.number = number }
weak var tenant: Person?
}
In this example, a Customer
may or may not have a credit card, but a CreditCard
will always be associated with a Customer
.
class Customer {
let name: String
var card: CreditCard?
init(name: String) { self.name = name }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) { self.number = number; self.customer = customer }
}
Practically that means Customer
must work with an Optional CreditCard
(can be nil), but CreditCard
gets to work with an unwrapped Customer
(can't be nil, has to be there). By allowing one to be nil, but not the other, we break the cycle and both instances can be tracked by ARC.