Как работает механизм подсчета ссылок в Swift?
Что проверяют
Понимаете ли вы, что ARC это не сборщик мусора, а вызовы retain/release, расставленные компилятором на этапе сборки. Дальше проверяют, умеете ли объяснить, почему счётчик иногда не доходит до нуля, и чем weak отличается от unowned на практике.
Как отвечать
Что считается. Счётчик есть только у ссылочных типов: экземпляров классов, замыканий, акторов. Структуры и перечисления копируются по значению и счётчика не имеют, хотя ссылочные поля внутри них считаются как обычно.
Механика. Компилятор анализирует время жизни каждой ссылки и вставляет retain там, где ссылка появляется, и release там, где исчезает. Как только счётчик стал нулём, немедленно вызывается deinit и память возвращается. Освобождение детерминированное: пауз на обход кучи, как у трассирующего сборщика, здесь нет.
Типы ссылок:
strong(по умолчанию) увеличивает счётчик и удерживает объект живым.weakне удерживает, всегда объявляется опционалом иvar, потому что рантайм автоматически обнуляет её при освобождении объекта.unownedне удерживает и не является опционалом. Обращение после освобождения даёт краш, поэтому её берут только при гарантии, что объект переживёт ссылающегося.
Цикл удержания. Два объекта держат друг друга сильными ссылками, счётчики никогда не падают до нуля, память не возвращается. Разрывается тем, что одна из сторон, обычно «подчинённая» или обратная ссылка, объявляется weak или unowned.
class Person {
let name: String
var apartment: Apartment? // strong: человек владеет квартирой
init(name: String) { self.name = name }
deinit { print("\(name) освобождён") }
}
class Apartment {
let number: Int
weak var tenant: Person? // weak: обратная ссылка не удерживает
init(number: Int) { self.number = number }
}Замыкания. Замыкание захватывает self сильно, поэтому self.onUpdate = { self.reload() } создаёт цикл. Лечится списком захвата: [weak self] in guard let self else { return }. В Task { } и в цепочках Combine та же история.
Как выбирать. weak, когда объект может исчезнуть раньше меня: делегат, родитель во вью-иерархии, кэшируемая сущность. unowned, когда время жизни гарантированно не короче моего, например ссылка на владельца, который меня и создал. weak чуть дороже: под неё рантайм ведёт side table для зануления.
Где спотыкаются
Называют ARC сборщиком мусора и обещают, что он «сам разберёт циклы». Второй частый провал в примере кода: weak-поле объявляют неопциональной константой или ставят weak с обеих сторон цикла, хотя достаточно одной.