Как работает сборщик мусора (garbage collector) в iOS?
Sobes Copilot
Получайте ответы в реальном времени
Ответ от нейросети
sobes.tech AI
В iOS нет классического сборщика мусора (garbage collector) в привычном смысле, как, например, в Java или C#. Вместо него используется механизм автоматического управления памятью, основанный на подсчете ссылок.
Исторически в Objective-C была ручная модель управления памятью (Manual Reference Counting - MRC), затем появился Automatic Reference Counting (ARC). В Swift используется только ARC.
ARC (Automatic Reference Counting)
ARC - это механизм компилятора, который автоматически вставляет вызовы методов управления памятью, таких как retain, release и autorelease (в Objective-C) или аналогичные операции в Swift.
Ключевые принципы работы ARC:
- Счетчик ссылок: У каждого объекта есть счетчик ссылок, который указывает, сколько других объектов ссылаются на него.
- Увеличение счетчика: Когда на объект создается сильная ссылка (
strongв Swift,retainпо умолчанию в Objective-C), счетчик ссылок увеличивается на единицу. - Уменьшение счетчика: Когда сильная ссылка удаляется (например, переменная выходит из области видимости, или ей присваивается
nil), счетчик ссылок уменьшается на единицу. - Освобождение памяти: Когда счетчик ссылок объекта достигает нуля, ARC автоматически освобождает память, занимаемую этим объектом, путем вызова его метода
deinit(в Swift) илиdealloc(в Objective-C).
Типы ссылок в Swift:
strong: Делает объект владельцем ссылки, увеличивает счетчик ссылок. Является типом ссылки по умолчанию.weak: Не делает объект владельцем ссылки, не увеличивает счетчик ссылок. Используется для предотвращения циклов сильных ссылок. При освобождении объекта, на который ссылается слабая ссылка, она automatically устанавливается вnil. Используется с опциональными типами.unowned: Подобнаweak, но не обнуляется при освобождении объекта. Требует, чтобы объект существовал на протяжении всего времени жизни ссылки. Не является опциональным типом. Используется, когда гарантируется, что ссылка всегда будет указывать на действительный объект.
Циклы сильных ссылок:
Основная проблема ARC - это циклы сильных ссылок (retain cycles). Они возникают, когда два или более объекта имеют сильные ссылки друг на друга, и ни один из них не может быть освобожден, потому что их счетчики ссылок никогда не достигнут нуля.
Пример цикла сильных ссылок:
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
unit4A = nil // Ни один из deinit не будет вызван
Решение циклов сильных ссылок:
Для предотвращения циклов сильных ссылок используются weak или unowned ссылки.
В данном примере Person должен иметь сильную ссылку на Apartment, а Apartment должен иметь слабую ссылку на Person (так как человек может съехать, но апартамент останется).
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? // Слабая ссылка
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 deinitialized
unit4A = nil // Apartment deinitialized
Замыкания и циклы сильных ссылок:
Циклы сильных ссылок также могут возникать между экземплярами класса и замыканиями, если замыкание захватывает сильную ссылку на экземпляр, а экземпляр содержит сильную ссылку на это замыкание.
Решение: использовать списки захвата (capture lists) с weak или unowned внутри замыкания.
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
[unowned self] in // Список захвата с 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
В заключение, iOS управляет памятью объектов с помощью ARC, основанного на подсчете ссылок, а не на типичном сборщике мусора. Разработчик должен осознавать концепцию циклов сильных ссылок и использовать weak или unowned ссылки для их предотвращения.