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