Swift Unowned References
Memory management is a critical aspect of Swift programming, and understanding how to properly handle object references can help you write more efficient and bug-free code. In this tutorial, we'll explore unowned references - a powerful tool in Swift's memory management system.
Introduction to Unowned References
In Swift, memory management is handled automatically through Automatic Reference Counting (ARC). When two objects reference each other strongly, they can create a strong reference cycle (also known as a memory leak), which prevents ARC from deallocating them even when they're no longer needed.
To solve this problem, Swift provides two special reference types:
- Weak references (which we covered in a previous tutorial)
- Unowned references (the focus of this tutorial)
An unowned reference is similar to a weak reference in that it doesn't create a strong hold on the object it refers to. However, there's a key difference: unowned references are assumed to always have a value.
When to Use Unowned References
You should use unowned references when:
- You need to break a strong reference cycle
- The referenced object will never become nil during the lifetime of the referencing object
- The referenced object has the same or longer lifetime than the referencing object
Think of unowned as saying: "This reference will definitely have a value for as long as I need it."
Unowned vs Weak References
Before diving deeper, let's clarify the difference:
Feature | Unowned References | Weak References |
---|---|---|
Declaration | unowned | weak |
Type | Non-optional | Optional |
Safety | Must always have a value | Can be nil |
Access | Direct | Requires unwrapping |
Using Unowned References in Swift
Let's see how to declare and use an unowned reference:
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
print("Customer \(name) is being initialized")
}
deinit {
print("Customer \(name) is being deinitialized")
}
}
class CreditCard {
let number: String
unowned let customer: Customer
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
print("Credit card \(number) is being initialized")
}
deinit {
print("Credit card \(number) is being deinitialized")
}
}
In this example, a Customer
may or may not have a CreditCard
(it's optional), but a CreditCard
must always have a Customer
. The relationship makes logical sense - a credit card can't exist without its owner.
Let's see how to use these classes:
var john: Customer? = Customer(name: "John Appleseed")
john?.card = CreditCard(number: "1234-5678-9101-1121", customer: john!)
// Output:
// Customer John Appleseed is being initialized
// Credit card 1234-5678-9101-1121 is being initialized
When we set john
to nil, both objects are properly deallocated:
john = nil
// Output:
// Customer John Appleseed is being deinitialized
// Credit card 1234-5678-9101-1121 is being deinitialized
Without the unowned reference, we would have created a memory leak.
Unowned References and Implicitly Unwrapped Optional Properties
Sometimes you need to handle situations where two properties both need to reference each other, but one must be initialized before the other. Here's how you can combine unowned with implicitly unwrapped optionals:
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
In this example:
Country
needs to initialize itscapitalCity
property- But
City
requires aCountry
reference during initialization - We use an implicitly unwrapped optional (
City!
) and unowned to resolve this circular reference
Usage:
let canada = Country(name: "Canada", capitalName: "Ottawa")
print("The capital of \(canada.name) is \(canada.capitalCity.name)")
// Output: The capital of Canada is Ottawa
Unowned Optional References
Starting from Swift 5, you can also declare unowned optional references:
class Department {
var name: String
unowned var head: Employee?
init(name: String) {
self.name = name
}
}
class Employee {
var name: String
var department: Department?
init(name: String) {
self.name = name
}
}
This allows you to have an optional unowned reference when you're certain the reference won't be used after being deallocated.
Safety Considerations with Unowned References
Important Warning: Using unowned references incorrectly can lead to crashes. If you access an unowned reference after the referenced instance has been deallocated, you'll get a runtime error.
func riskyUnownedUse() {
let customer = Customer(name: "Jane")
let card = CreditCard(number: "9876-5432-1098", customer: customer)
// Store unowned reference in a property or variable
let unownedCustomer = card.customer
// This is dangerous! The customer could be deallocated
// by this point if it's not referenced elsewhere
print(unownedCustomer.name) // Potential crash!
}
As a rule of thumb:
- Use
weak
when the referenced object might becomenil
- Use
unowned
when you're certain the referenced object will outlive the reference or have the same lifetime
Real-World Application: Delegation Pattern
Unowned references are commonly used in iOS development with the delegation pattern:
protocol DataProviderDelegate: AnyObject {
func didProvideData(_ data: Data)
}
class DataProvider {
unowned let delegate: DataProviderDelegate
init(delegate: DataProviderDelegate) {
self.delegate = delegate
}
func fetchData() {
// Simulate getting data
let data = Data()
delegate.didProvideData(data)
}
}
class ViewController: UIViewController, DataProviderDelegate {
var dataProvider: DataProvider?
override func viewDidLoad() {
super.viewDidLoad()
dataProvider = DataProvider(delegate: self)
}
func didProvideData(_ data: Data) {
// Handle the data
print("Data received")
}
}
In this example, the delegate (ViewController) will outlive the DataProvider, making an unowned reference appropriate.
Summary
Unowned references are a powerful tool in Swift's memory management system to prevent reference cycles without needing to handle nil values. Here are the key takeaways:
- Use unowned references when you're sure the referenced object will never become nil during your reference's lifetime
- Unowned references don't increment the reference count
- Unlike weak references, unowned references are non-optional by default
- Unowned references provide more direct access since you don't need to unwrap them
- Incorrect use of unowned references can lead to crashes, so use them carefully
Exercises
- Create a Person class with an unowned reference to a Passport object, where a Passport must always have an owner.
- Implement a Book and Author class relationship using unowned references appropriately.
- Identify potential strong reference cycles in an existing project and resolve them using unowned references where appropriate.
Additional Resources
- Swift Documentation on Automatic Reference Counting
- WWDC Session: Modernizing Grand Central Dispatch Usage
- Swift Memory Management and ARC
Understanding unowned references is essential for writing memory-efficient Swift code. With practice, you'll develop an intuition for when to use unowned versus weak references in your applications.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)