Узнайте, как Swift автоматически уничтожает классы через ARC и deinit. Практический разбор с примерами кода для iOS-разработчиков.
Каждый экземпляр класса в Swift когда-нибудь уничтожается. Но когда именно это происходит? Если вы работаете с классами, рано или поздно столкнётесь с управлением памятью. В этой статье разберём Automatic Reference Counting (ARC) и деинициализатор — механизмы, которые Swift использует, чтобы решить, когда пора «убить» объект. Вы узнаете, как отслеживать создание и уничтожение объектов, и почему let для класса ведёт себя не так, как для структуры.
За каждым экземпляром класса в Swift стоит скрытый счётчик. Каждый раз, когда вы создаёте новую ссылку на класс (присваиваете переменной, передаёте в функцию, кладёте в массив), Swift увеличивает этот счётчик на 1. Когда одна из ссылок исчезает, счётчик уменьшается на 1. Когда счётчик достигает нуля, Swift понимает: на этот объект больше никто не смотрит. Можно безопасно его уничтожить.
Эта система называется Automatic Reference Counting (ARC). Вам не нужно управлять ею вручную — Swift делает это невидимо и непрерывно для каждого экземпляра класса в вашем приложении.
Структурам ARC не нужен, потому что каждая копия структуры — это независимый набор данных. Когда переменная-структура исчезает, данные исчезают вместе с ней. Просто, предсказуемо, без подсчёта.
Классам ARC нужен именно из-за системы «указателей»: несколько переменных могут указывать на одни и те же данные. Без подсчёта ссылок Swift не смог бы узнать, когда последняя ссылка исчезла.
Когда счётчик ссылок достигает нуля и экземпляр класса вот-вот будет уничтожен, Swift вызывает специальный метод — деинициализатор, который записывается как deinit. Это противоположность init: если init выполняется при создании экземпляра, то deinit — при уничтожении последней ссылки.
Посмотрим на примере тренировочного лагеря из мира аниме:
class TrainingCamp {
let name: String
init(name: String) {
self.name = name
print("\(name) открылся! 🏕️")
}
deinit {
print("\(name) заброшен. Тренировка окончена.")
}
}Теперь создадим несколько лагерей и посмотрим, как они уничтожаются:
var camp1: TrainingCamp? = TrainingCamp(name: "Тренировочная база Конохи")
var camp2: TrainingCamp? = TrainingCamp(name: "Лагерь деревни Песка")
var camp3: TrainingCamp? = camp1 // camp1 и camp3 теперь указывают на один лагерь
camp1 = nil // счётчик ссылок для Конохи падает до 1 (camp3 всё ещё держит)
camp2 = nil // счётчик для Песка падает до 0 — deinit срабатывает!
camp3 = nil // счётчик для Конохи падает до 0 — deinit срабатывает!Вывод:
Тренировочная база Конохи открылся! 🏕️
Лагерь деревни Песка открылся! 🏕️
Лагерь деревни Песка заброшен. Тренировка окончена.
Тренировочная база Конохи заброшен. Тренировка окончена.Обратите внимание: camp2 запускает деинициализатор сразу после camp2 = nil, потому что это была единственная ссылка. Деинициализатор Конохи не срабатывает до camp3 = nil, так как даже после camp1 = nil счётчик оставался равным 1 благодаря camp3.
deinit вызывается Swift автоматически, когда счётчик ссылок достигает нуля. Вы можете его определить, но не можете запустить вручную.init, deinit не принимает аргументов. Он просто получает сигнал «ты будешь уничтожен, сделай что нужно».deinit в конец тела класса. Код должен читаться как глава книги, а deinit — это финальная сцена.Пока мы говорим о различиях классов и структур, стоит упомянуть ещё один нюанс. Если вы создаёте константную структуру через let, то ничего в ней изменить нельзя — ни свойства, ничего. С классами всё иначе. let для класса означает «эта переменная всегда будет указывать на тот же экземпляр». Но это не значит, что сам экземпляр нельзя менять.
class Ninja {
var powerLevel: Int
init(powerLevel: Int) {
self.powerLevel = powerLevel
}
}
let naruto = Ninja(powerLevel: 9000)
naruto.powerLevel = 9001 // ✅ Это можно!
naruto = Ninja(powerLevel: 1) // ❌ А это нельзя — нельзя переназначить указательlet naruto означает, что указатель naruto зафиксирован — он всегда будет указывать на тот же экземпляр Ninja. Но данные, на которые указывает указатель, можно менять свободно. Это становится понятным, если вспомнить аналогию с указателем: let фиксирует указатель на месте, но ничего не говорит о том, что находится на другом конце.
ARC и деинициализаторы имеют значение, когда:
В повседневной SwiftUI-разработке вы не будете писать deinit постоянно. Но понимание того, что он существует и почему он существует (потому что ARC нужен хук для очистки), помогает разобраться в модели памяти вашего приложения, а не просто надеяться, что всё работает.
Swift подсчитывает, сколько сущностей указывает на каждый экземпляр класса. Когда счётчик достигает нуля, вызывается deinit и объект уничтожается. Вот и вся суть ARC. А let для класса означает постоянство указателя, а не данных — это тот нюанс, который ставит в тупик, пока аналогия с указателем не становится очевидной. 🌸
Попробуйте прямо сейчас добавить deinit в один из ваших классов и понаблюдайте, когда он вызывается. Это лучший способ закрепить понимание ARC.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →