How does automatic memory management (ARC) work in Swift?
sobes.tech AI
Answer from AI
ARC (Automatic Reference Counting) in Swift tracks and manages your application's memory usage. It automatically frees memory used by class instances when they are no longer needed, preventing memory leaks.
When a new class instance is created, ARC allocates a block of memory to store information about that instance. When the instance is no longer needed, ARC releases that memory so it can be used for other purposes.
ARC works by counting the number of strong references to each class instance. A strong reference is a reference that keeps the instance in memory. When the number of strong references to an instance drops to zero, ARC deallocates the associated memory.
There are three types of references in Swift that affect ARC:
- Strong References: Increase the reference count to an instance.
- Weak References: Do not increase the reference count. Used to avoid strong reference cycles when two class instances hold strong references to each other. A weak reference automatically becomes
nilwhen the object it references is deallocated. - Unowned References: Also do not increase the reference count. Used when you are sure that the referenced instance will always exist as long as the current instance exists. They cannot be
nil. Accessing an unowned reference after the instance has been deallocated will cause a runtime error.
Example of a strong reference cycle:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
print("Apartment \(unit) is being initialized")
}
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil // Person is not deallocated
unit4A = nil // Apartment is not deallocated
To resolve the cycle, use weak or unowned references. In this case, tenant in Apartment should be a weak reference:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let unit: String
weak var tenant: Person? // Using weak reference
init(unit: String) {
self.unit = unit
print("Apartment \(unit) is being initialized")
}
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john
john = nil // Person is deallocated
unit4A = nil // Apartment is deallocated
Choosing between weak and unowned depends on the relationship between objects:
- Weak: If one object can become
nilbefore the other is deallocated (optional value). - Unowned: If one object always exists as long as the other does (non-optional).
ARC also manages memory used by closures that capture class instances. Strong reference cycles can occur when a closure captures an instance of a class, and that instance also has a strong reference to the closure. Capture lists in closures are used to prevent this.
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
// No capture list, potential strong cycle
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name)>"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
paragraph = nil // HTMLElement is not deallocated
Solution using capture list [weak self]:
class AnHTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = { [weak self] in // Using weak self
guard let self = self else { return "" } // Check if self exists
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name)>"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
var paragraph: AnHTMLElement? = AnHTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
paragraph = nil // AnHTMLElement is deallocated
Using [unowned self] is possible if you are sure that the class instance (self) will not become nil before the closure executes.
ARC greatly simplifies memory management in Swift compared to manual management, but understanding reference types is essential to prevent memory leaks.