How does garbage collection work in iOS?
sobes.tech AI
Answer from AI
iOS does not have a traditional garbage collector as in Java or C#. Instead, it uses an automatic memory management mechanism based on reference counting.
Historically, Objective-C used manual memory management (Manual Reference Counting - MRC), then Automatic Reference Counting (ARC) was introduced. Swift uses only ARC.
ARC (Automatic Reference Counting)
ARC is a compiler mechanism that automatically inserts calls to memory management methods such as retain, release, and autorelease (in Objective-C) or similar operations in Swift.
Key principles of ARC:
- Reference count: Each object has a reference count indicating how many other objects refer to it.
- Incrementing the count: When a strong reference (
strongin Swift,retainby default in Objective-C) is created to an object, the reference count increases by one. - Decrementing the count: When a strong reference is removed (e.g., a variable goes out of scope or is assigned
nil), the reference count decreases by one. - Memory release: When the reference count of an object reaches zero, ARC automatically deallocates the memory occupied by that object by calling its
deinitmethod (in Swift) ordealloc(in Objective-C).
Types of references in Swift:
strong: Makes the object the owner of the reference, increasing the reference count. It is the default reference type.weak: Does not make the object the owner, does not increase the reference count. Used to prevent strong reference cycles. When the object is deallocated, the weak reference is automatically set tonil. Used with optional types.unowned: Similar toweak, but not set tonilupon deallocation. It requires that the object exists for the lifetime of the reference. Not an optional type. Used when it is guaranteed that the reference will always point to a valid object.
Strong reference cycles:
The main problem with ARC is strong reference cycles (retain cycles). They occur when two or more objects hold strong references to each other, preventing their deallocation because their reference counts never reach zero.
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")
// Strong references to each other
john!.apartment = unit4A
unit4A!.tenant = john
// Setting variables to nil does not deallocate memory
john = nil
unit4A = nil // Neither deinit will be called
Resolving strong reference cycles:
To prevent strong reference cycles, weak or unowned references are used.
In this example, Person should have a strong reference to Apartment, and Apartment should have a weak reference to Person (since a person can move out, but the apartment remains).
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? // 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
// Setting variables to nil successfully deallocates memory
john = nil // Person deinitialized
unit4A = nil // Apartment deinitialized
Closures and strong reference cycles:
Strong reference cycles can also occur between class instances and closures if the closure captures a strong reference to the instance, and the instance holds a strong reference to the closure.
Solution: use capture lists with weak or unowned inside closures.
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
[unowned self] in // Capture list with unowned
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 deinitialized
In conclusion, iOS manages object memory using ARC, based on reference counting, not a traditional garbage collector. Developers should be aware of strong reference cycles and use weak or unowned references to prevent them.