Memory Management in Swift: ARC, Weak, and Unowned References
- arda doğantemur
- Jun 5, 2024
- 3 min read

Memory management is a crucial aspect of any programming language, and Swift handles it gracefully through a mechanism called Automatic Reference Counting (ARC). Understanding how ARC works and when to use weak and unowned references can help you write more efficient and safer Swift code.
What is Automatic Reference Counting (ARC)?
Automatic Reference Counting (ARC) is a memory management feature of Swift that automatically keeps track of the memory used by your app. ARC ensures that instances of classes are only kept in memory as long as they are needed, and it automatically frees up memory when those instances are no longer needed.
How ARC Works
Every time you create a new instance of a class, ARC allocates a chunk of memory to store information about that instance. This includes properties, methods, and any other data. ARC then keeps a count of the number of references to each class instance. When the reference count drops to zero, ARC deallocates the instance and frees up the memory.
Here's a simple example:
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var person1: Person? = Person(name: "John")
var person2 = person1
person1 = nil
person2 = nil
In this example, the Person instance is created and assigned to person1. When person1 is set to nil, the reference count decreases but does not reach zero because person2 still holds a reference. When person2 is also set to nil, the reference count drops to zero, and ARC deallocates the Person instance.
Strong References and Retain Cycles
By default, every reference in Swift is a strong reference, meaning that it increases the reference count of the instance it points to. However, this can lead to retain cycles, where two or more instances hold strong references to each other, preventing ARC from deallocating them.
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
}
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john
john = nil
unit4A = nil
In this scenario, john and unit4A both hold strong references to each other, resulting in a retain cycle. Neither instance is deallocated because their reference counts never reach zero.
Weak References
To break retain cycles, you can use weak references. A weak reference does not increase the reference count of the instance it refers to. Weak references are declared using the weak keyword and must always be optional (nil when the referenced instance is deallocated).
Here's how you can break the retain cycle using weak references:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let unit: String
weak var tenant: Person?
init(unit: String) {
self.unit = unit
}
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john
john = nil
unit4A = nil
Now, when john is set to nil, the reference count of Person drops to zero, and the instance is deallocated. Consequently, the weak reference tenant in Apartment is set to nil, breaking the cycle.
Unowned References
Unowned references are similar to weak references but are used when the referenced instance is expected to always have a value. Unlike weak references, unowned references are not optional and do not become nil when the referenced instance is deallocated. Declaring an unowned reference uses the unowned keyword.
Use unowned references when the referenced instance is expected to outlive the reference. If the referenced instance is deallocated and the unowned reference is accessed, a runtime crash will occur.
Here's an example using unowned references:
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class CreditCard {
let number: String
unowned let customer: Customer
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
}
deinit {
print("CreditCard \(number) is being deinitialized")
}
}
var john: Customer? = Customer(name: "John")
john?.card = CreditCard(number: "1234-5678-9012-3456", customer: john!)
john = nil
in this case, CreditCard holds an unowned reference to Customer, and when john is set to nil, both Customer and CreditCard instances are deallocated correctly.
Feature | Weak Reference | Strong Reference | Unowned Reference |
Reference Count | Does not increase reference count | Increases reference count | Does not increase reference count |
Deallocation | Automatically set to nil when the referenced instance is deallocated | Keeps the referenced instance alive | Can cause a runtime crash if accessed after the referenced instance is deallocated |
Use Case | Used for properties that can become nil during the lifecycle (e.g., delegates, parent-child relationships) | Default type of reference, used for most references | Used when the referenced instance is expected to outlive the referencing instance |
Syntax | weak var reference: Type | var reference: Type | unowned var reference: Type |
Example Scenario | Breaking retain cycles where the referenced instance may become ni | Default references for most class properties | References in a closure capturing a property where the captured property will outlive the closure |
Conclusion
Understanding ARC, strong, weak, and unowned references is essential for effective memory management in Swift. Properly using these concepts helps prevent memory leaks and ensures that your applications run efficiently. By mastering these techniques, you can write safer and more reliable Swift code.
Comments