Differences between weak and unowned in Swift with examples

Yury Lebedev
4 min readJan 16, 2024

--

Introduction

In Swift, memory management is done through ARC (Automatic Reference Counting). Understanding the key concepts of weak and unowned references is critically important to prevent memory leaks and strong reference cycles. Let’s look at how they differ and how to use them properly.

weak References

Weak references are used when one object can exist without another. They do not increase the object’s reference count, which helps prevent strong reference cycles.

Features of weak references:

  • Always declared as optional variables (var) because the object they point to can be released, and in this case, the reference will become nil.
  • Especially useful in preventing memory leaks when working with delegates and closures.

Example with weak:

class Department {
var manager: Employee?

deinit {
print("Department is being deinitialized")
}
}

class Employee {
weak var department: Department?

deinit {
print("Employee is being deinitialized")
}
}

var department: Department? = Department()
var manager: Employee? = Employee()

department?.manager = manager
manager?.department = department

department = nil
manager = nil
// Prints: "Employee is being deinitialized" and then "Department is being deinitialized""Employee is being deinitialized" и затем "Department is being deinitialized"

In this example, by breaking the link between Department and Employee, we avoid memory leaks.

Using weak in Closures

Using weak in closures is often required to prevent strong reference cycles, especially when the closure captures self, i.e., the class instance.

Imagine we have a ViewController class that performs some asynchronous operation. We want some code to be executed after this operation is completed, but at the same time, we need to avoid memory leaks created by strong reference cycles.

Example with weak in closure:

class ViewController: UIViewController {
var dataLoader: DataLoader?

func fetchData() {
dataLoader?.loadData(completion: { [weak self] result in
guard let self = self else { return }

switch result {
case .success(let data):
self.updateUI(with: data)
case .failure(let error):
self.showErrorMessage(error)
}
})
}

private func updateUI(with data: Data) {
// Update user interface
}

private func showErrorMessage(_ error: Error) {
// Show error message
}
}

class DataLoader {
func loadData(completion: @escaping (Result<Data, Error>) -> Void) {
// Code to load data
}
}

In this example:

  • ViewController has a fetchData method that calls the loadData method of DataLoader.
  • In the closure passed to loadData, [weak self] is used to prevent a strong reference to self (which is an instance of ViewController). This is important because DataLoader might hold the closure for a long time, e.g., during an asynchronous operation.
  • guard let self = self else { return } is used for safe access to self inside the closure. If ViewController is released before the closure is executed, self will be nil, and the code inside the closure won’t execute, preventing potential errors or crashes.

unowned References

Unowned references are similar to weak, but have two key differences: they are not optional and do not become nil when the object they point to is released.

Features of unowned references:

  • Used when one object will not be released until another object is released.
  • Accessing an unowned reference if the object has been released will crash.
  • Only works with let (constants)

Example with unowned:

class Customer {
let name: String
var card: CreditCard?

init(name: String) {
self.name = name
}

deinit {
print("\(name) is being deinitialized")
}
}

class CreditCard {
let number: UInt64
unowned let customer: Customer

init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}

deinit {
print("Card #\(number) is being deinitialized")
}
}

var john: Customer? = Customer(name: "John")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

john = nil
// Prints: "John is being deinitialized" and then "Card #1234567890123456 is being deinitialized"

Here, after the Customer is released, the associated CreditCard object is also released, preventing memory leaks.

Comparison of weak and unowned

  • Declaration: Weak references are always optional, while unowned references cannot be and are constants — therefore not optionals.
  • Strong Reference Cycles: Both prevent strong reference cycles but are used in different scenarios.
  • Safety: Weak references are safer as they automatically become nil when the object is released. Unowned references can cause crashes if the object is destroyed. Need to know what you’re doing.

Conclusion

Understanding the differences between weak and unowned in Swift is crucial for safe and efficient memory management. The choice between them depends on the specific architecture of the application and the relationships between objects. Always test your code for memory leaks to ensure its reliability and performance.

--

--