Как избежать цикла сильных ссылок в Swift
Что проверяют
Понимаете ли вы, как работает подсчёт ссылок, и знаете ли разницу между weak и unowned.
Как отвечать
Память в Swift освобождается автоматическим подсчётом ссылок: объект живёт, пока на него есть хотя бы одна сильная ссылка. Цикл возникает, когда два объекта держат друг друга: счётчики никогда не доходят до нуля, и память не освобождается.
Два классических случая:
1. Делегат. Родитель владеет дочерним объектом, а тот держит родителя как делегата. Лечение: свойство делегата объявляется слабым, и потому обязательно var и опциональным.
weak var delegate: SomeDelegate?2. Замыкание, захватывающее self, которое при этом хранится в свойстве этого же объекта. Компилятор специально требует явный self в убегающих замыканиях, чтобы вы заметили захват. Лечение это список захвата:
loader.onFinish = { [weak self] result in
guard let self else { return }
self.render(result)
}Разница между модификаторами:
weak: ссылка обнуляется при освобождении объекта, поэтому тип обязательно опциональный. Чуть дороже из-за учёта, зато безопасно;unowned: не обнуляется и не опционален. Быстрее, но обращение после освобождения объекта роняет приложение. Использовать только когда точно известно, что объект живёт дольше ссылки.
Важная оговорка: не всякое замыкание создаёт цикл. Не убегающие замыкания вроде map и forEach выполняются сразу и ничего не удерживают, добавлять туда [weak self] бессмысленно. Разовый колбэк, который вызовется один раз и освободится, тоже обычно безопасен. Цикл появляется, когда замыкание хранится объектом, на который ссылается.
Обнаружение: инструмент Leaks и граф памяти в отладчике Xcode, который показывает, кто кого держит.
Где спотыкаются
Объявляют делегата сильной ссылкой.
Ставят [weak self] везде подряд, включая не убегающие замыкания.
Берут unowned там, где объект может быть освобождён раньше.
Забывают развернуть слабую ссылку и молча пропускают выполнение.