Swift uses Automatic Reference Counting (ARC) to manage the memory of instances of classes. ARC automatically keeps track of references to class instances and deallocates them when they are no longer needed, freeing up memory.
class Person {
var name: String
init(name: String) {
self.name = name
print("\(name) is initialized.")
}
deinit {
print("\(name) is deinitialized.")
}
}
var person1: Person? = Person(name: "Bishal") // Reference count: 1
var person2 = person1 // Reference count: 2
person1 = nil // Reference count: 1
person2 = nil // Reference count: 0
// Output: Bishal is deinitialized.
Retail Cycle and Memory Leak
A retain cycle occurs when two or more class instances hold strong references to each other, preventing ARC from deallocating them. This leads to a memory leak.
class Person {
var name: String
var apartment: Apartment?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is deinitialized.")
}
}
class Apartment {
var unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
}
deinit {
print("Apartment \(unit) is deinitialized.")
}
}
var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john
// Breaking references
john = nil // Retain cycle: Apartment is not deallocated.
unit4A = nil // Retain cycle: Person is not deallocated.
Solving Retail Cycle
- Weak References
- A weak reference does not increase the reference count of an instance.
- Declared using the
weak
keyword. - Must always be an optional (
nil
if the instance is deallocated).
class Apartment {
var unit: String
weak var tenant: Person? // Weak reference
init(unit: String) {
self.unit = unit
}
deinit {
print("Apartment \(unit) is deinitialized.")
}
}
2.Unowned References
An unowned reference does not increase the reference count, similar to a weak reference.Unlike weak references, it assumes the instance will always exist during its lifetime.Declared using the unowned
keyword.
class Person {
var name: String
unowned var apartment: Apartment // Unowned reference
init(name: String, apartment: Apartment) {
self.name = name
self.apartment = apartment
}
}
ARC and Closures
Closures can also cause retain cycles if they capture references to self
or other objects.
class ViewController {
var title: String = "MyViewController"
lazy var printTitle: () -> Void = {
print(self.title)
}
deinit {
print("ViewController is deinitialized.")
}
}
var vc: ViewController? = ViewController()
vc?.printTitle()
vc = nil // Retain cycle: ViewController is not deallocated.
Using Capture Lists to Solve the Retain Cycle
Capture lists specify how references are captured within the closure.
class ViewController {
var title: String = "MyViewController"
lazy var printTitle: () -> Void = { [weak self] in
guard let self = self else { return }
print(self.title)
}
deinit {
print("ViewController is deinitialized.")
}
}
var vc: ViewController? = ViewController()
vc?.printTitle()
vc = nil // Output: ViewController is deinitialized.
Weak vs Unowned in Swift
Weak Reference
- Always optional, so you must handle cases where the reference is
nil
. - When the reference might become
nil
. - For optional references like delegates or relationships where one object might outlive the other.
Unowned Reference
- Unsafe if the object is deallocated. Accessing it after the object is gone will cause a runtime crash.
- When the reference will never become
nil
. - For non-optional references where the lifetime of the referred object is guaranteed to be longer than the reference.