Skip to main content

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:

  1. You need to break a strong reference cycle
  2. The referenced object will never become nil during the lifetime of the referencing object
  3. 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:

FeatureUnowned ReferencesWeak References
Declarationunownedweak
TypeNon-optionalOptional
SafetyMust always have a valueCan be nil
AccessDirectRequires unwrapping

Using Unowned References in Swift

Let's see how to declare and use an unowned reference:

swift
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:

swift
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:

swift
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:

swift
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:

  1. Country needs to initialize its capitalCity property
  2. But City requires a Country reference during initialization
  3. We use an implicitly unwrapped optional (City!) and unowned to resolve this circular reference

Usage:

swift
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:

swift
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.

swift
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 become nil
  • 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:

swift
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

  1. Create a Person class with an unowned reference to a Passport object, where a Passport must always have an owner.
  2. Implement a Book and Author class relationship using unowned references appropriately.
  3. Identify potential strong reference cycles in an existing project and resolve them using unowned references where appropriate.

Additional Resources

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! :)