What is ARC?
Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. In most cases, this means that memory management “just works” in Swift, and you don’t need to think about memory management yourself. ARC automatically frees up the memory used by class instances when those instances are no longer needed. - Apple
ARC In Action
In this example, we assign an instance to the reference1 property.
- number of references equals 1.
class Storage {
let data: Data
init(data: Data) {
self.data = data
print("\(Self.self) is being initialized")
}
deinit {
print("\(Self.self) is being deinitialized")
}
}
var reference1: Storage?
var reference2: Storage?
var reference3: Storage?
reference1 = Storage(data: Data())
Now we can assign a reference to another two properties, reference2 and reference3.
Whenever you assign a reference, you increase the counter.
- number of references equals 3.
reference2 = reference1
reference3 = reference1
When you set reference2 and reference3 to nil, the number of references equals 1.
reference2 = nil
reference3 = nil
Only when you set reference1 to nil object will be deinitialized.
reference1 = nil
Memory leaks
Memory leaks appear when you have strong references between two instances that point to each other.
In this example, class Employee has optional department property, and class Department has optional employee property.
class Department {
let name: String
init(name: String) {
self.name = name
print("\(Self.self) is being initialized")
}
var employee: Employee?
deinit {
print("\(Self.self) is being deinitialized")
}
}
class Employee {
let name: String
init(name: String) {
self.name = name
print("\(Self.self) is being initialized")
}
var department: Department?
deinit {
print("\(Self.self) is being deinitialized")
}
}
var employee: Employee?
var department: Department?
employee = Employee(name: "John Doe")
department = Department(name: "Research and development")
If we try to assign Department reference to employee property and Employee reference to department property, it creates a memory leak by strong references that point to each other.
employee!.department = department
department!.employee = employee
If you try to set employee and department properties to nil, then these two objects can’t be deallocated because of the existing strong reference relationship between both objects.
employee = nil
department = nil
To avoid this unpleasant situation, we can use weak, unowned references.
Weak reference
If you use a weak keyword before a property, you say that this property should not keep a strong reference. Weak property should always be mutable and optional because ARC set the property to nil after the instance was deallocated.
In this example, Employee instance has department property with weak keyword. It means when we set employee property to nil ARC sets department property to nil and deallocates Department instance.
class Department {
let name: String
init(name: String) {
self.name = name
print("\(Self.self) is being initialized")
}
var employee: Employee?
deinit {
print("\(Self.self) is being deinitialized")
}
}
class Employee {
let name: String
init(name: String) {
self.name = name
print("\(Self.self) is being initialized")
}
weak var department: Department?
deinit {
print("\(Self.self) is being deinitialized")
}
}
var employee: Employee?
var department: Department?
employee = Employee(name: "John Doe")
department = Department(name: "Research and development")
employee!.department = department
department!.employee = employee
department = nil
employee = nil
Unowned reference
Unowned reference can’t be optional, and it should always have value. If you try to access a deallocated property value, you will face a runtime error.
In this example, we have two instances: User and DiscountCard.
DiscountCard has a relationship with the User that is marked as unowned to avoid a strong reference cycle.
class User {
let name: String
var discountCard: DiscountCard?
init(name: String) {
self.name = name
}
deinit { print("\(Self.self) is being deinitialized") }
}
class DiscountCard {
let number: UInt64
unowned let user: User
init(number: UInt64, user: User) {
self.number = number
self.user = user
}
deinit { print("\(Self.self) is being deinitialized") }
}
var user: User?
user = User(name: "John Doe")
When you create DiscountCard instance and assign it as reference to user property, it no longer holds strong reference.
user!.discountCard = DiscountCard(number: 1234_5678_9012_3456, user: user!)
After we set user property to nil, it will deallocate User and DiscountCard instances.
user = nil
Strong Reference Cycles for Closures
Strong reference cycle for closure can occur if you assign closure to property of instance. In this case, you assign reference to that closure. The strong reference cycle appears because closures are reference types.
class MemoryStorage {
let text: String
let additionalText: String?
lazy var copy: () -> String = {
if let additionalText = self.additionalText {
self.text + "\n" + additionalText
} else {
self.text
}
}
init(text: String, additionalText: String? = nil) {
self.text = text
self.additionalText = additionalText
}
deinit {
print("\(Self.self) is being deinitialized")
}
}
var memoryStorage: MemoryStorage? = MemoryStorage(text: "Thank you for registration", additionalText: "John Doe")
print(memoryStorage!.copy())
memoryStorage = nil
In this example, closure captures self.text property and create strong reference cycle by referencing to self.
Breaking Strong Reference Cycle in Closure
To break strong reference cycle, we need to add capture list with unowned keyword to copy closure.
class MemoryStorage {
let text: String
let additionalText: String?
lazy var copy: () -> String = { [unowned self] in
if let additionalText = self.additionalText {
self.text + "\n" + additionalText
} else {
self.text
}
}
init(text: String, additionalText: String? = nil) {
self.text = text
self.additionalText = additionalText
}
deinit {
print("\(Self.self) is being deinitialized")
}
}
var memoryStorage: MemoryStorage? = MemoryStorage(text: "Thank you for registration", additionalText: "John Doe")
print(memoryStorage!.copy())
memoryStorage = nil