Strong, weak and unowned reference types
A strong reference cycle, also known as a retain cycle, occurs in programming languages that use automatic memory management, such as Swift, Objective-C, and Java, when objects hold strong references to each other in a circular manner, causing them to become ineligible for garbage collection. This means that the objects will remain in memory indefinitely, even if they are no longer needed, resulting in a memory leak.
To understand this concept better, let’s consider an example in Swift:
class Person {
var name: String
var car: Car?
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deallocated")
}
}
class Car {
var model: String
var owner: Person?
init(model: String) {
self.model = model
print("\(model) is being initialized")
}
deinit {
print("\(model) is being deallocated")
}
}
var john: Person? = Person(name: "John")
var tesla: Car? = Car(model: "Tesla")
john?.car = tesla
tesla?.owner = john
john = nil
tesla = nil
In this example, we have two classes, Person
and Car
, each of which has a property that holds a strong reference to an instance of the other class. When we create instances of these classes and assign them to variables john
and tesla
, respectively, we also set their car
and owner
properties to each other, creating a circular strong reference.
Now, when we set both john
and tesla
to nil
, we might expect both instances to be deallocated and their deinit
methods to be called. However, since they hold strong references to each other, neither instance can be deallocated, resulting in a memory leak.
To avoid strong reference cycles, we can use weak or unowned references instead of strong references. A weak reference allows an object to hold a reference to another object without increasing its reference count, so if the referenced object is deallocated, the weak reference becomes nil. An unowned reference is similar to a weak reference, but it assumes that the referenced object will never be nil, so it does not need to be optional.
Here’s how we can modify the previous example to avoid the strong reference cycle using weak references:
class Person {
var name: String
weak var car: Car?
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deallocated")
}
}
class Car {
var model: String
unowned var owner: Person
init(model: String, owner: Person) {
self.model = model
self.owner = owner
print("\(model) is being initialized")
}
deinit {
print("\(model) is being deallocated")
}
}
var john: Person? = Person(name: "John")
var tesla: Car? = Car(model: "Tesla", owner: john!)
john?.car = tesla
john = nil
tesla = nil
In this modified example, we’ve changed the car
property of Person
to a weak reference, so it does not hold a strong reference to the Car
instance. Instead, we've added an owner
parameter to the Car
initializer, which is an unowned reference to the Person
instance that owns the car. This ensures that the Person
instance will always exist as long as the Car
Main difference between weak and unowned reference cycles:
In Swift, both weak
and unowned
are used to avoid strong reference cycles, but they have different use cases and behaviours.
A weak
reference allows an object to hold a reference to another object without increasing its reference count, and if the referenced object is deallocated, the weak reference becomes nil
. This means that a weak
reference is always optional, and you need to check if it's nil
before using it.
Here’s an example of using a weak
reference:
class Person {
var name: String
weak var car: Car?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deallocated")
}
}
class Car {
var model: String
init(model: String) {
self.model = model
}
deinit {
print("\(model) is being deallocated")
}
}
var john: Person? = Person(name: "John")
var tesla: Car? = Car(model: "Tesla")
john?.car = tesla
tesla = nil
print(john?.car) // prints "nil"
“weak” and “unowned” are used to avoid retain cycles, which occur when two or more objects hold strong references to each other, preventing them from being deallocated by the memory management system.
A “weak” reference is a reference to an object that does not prevent the object from being deallocated. If the object being referenced by the weak reference is deallocated, the weak reference will automatically be set to nil. Weak references must always be declared as optional.
An “unowned” reference is also a reference to an object that does not prevent the object from being deallocated. However, unlike weak references, unowned references are assumed to always have a value, and they are not optional. If an unowned reference is used after the object being referenced has been deallocated, the application will crash.
To summarize, “weak” references are optional and automatically set to nil when the referenced object is deallocated, while “unowned” references are non-optional and assume that the referenced object always exists, which can lead to a crash if that assumption is incorrect. Therefore, it’s important to use “weak” and “unowned” references appropriately based on the specific requirements of your code.