Retain cycle — одна из самых коварных проблем в Swift. Даже опытные разработчики могут не заметить, как создают цикл удержания ссылок, который мешает освобождению памяти. Разберём пример, который выглядит безобидно, но содержит скрытую угрозу.
⚠️ Что такое Retain cycle?
Retain Cycle (цикл удержания сильных ссылок) в Swift — это ситуация, когда два или более объекта удерживают друг друга через сильные ссылки (strong), не позволяя ARC (Automatic Reference Counting) освободить память. Это приводит к утечкам памяти, так как объекты никогда не освобождаются.
❌ Проблемный код.
class MyClass {
var task: Task<Void, Never>?
init() {
task = Task { [weak self] in
guard let self else { return }
repeat {
self.performSomeWork() // <- Цикл удержания!
} while !Task.isCancelled
}
}
func performSomeWork() {
}
deinit {
print("deinit") // Не выполнится
task?.cancel()
}
}
🤔 Что не так?
🔸 guard let self создаёт сильную ссылку на весь блок Task.
🔸 Цикл repeat работает бесконечно, удерживая self → deinit никогда не вызовется.
✅ Как исправить?
1️⃣ Изменить область видимости сильной ссылки.
Перенесите guard внутрь цикла, чтобы ссылка освобождалась после каждой итерации:
task = Task { [weak self] in
repeat {
guard let self else { return }
self.performSomeWork() // Сильная ссылка только здесь
} while !Task.isCancelled
}
2️⃣ Использовать слабую ссылку везде.
Если метод не требует обязательного self:
task = Task { [weak self] in
repeat {
self?.performSomeWork() // слабая ссылка
} while !Task.isCancelled
}
3️⃣ Вручную разорвать цикл.
Вызовите cancel() вне deinit, например, при закрытии экрана:
task?.cancel()
task = nil
⚠️ Чего лучше избегать?
Явный захват метода performSomeWork:
task = Task { [performSomeWork] in
repeat {
performSomeWork() // Метод всё равно содержит неявный self
} while !Task.isCancelled
}
Методы экземпляра неявно захватывают self, даже если переданы как замыкание.
🤔 Как отлавливать такие проблемы?
🔸 Тесты: Добавьте проверку на deinit с задержкой.
🔸 Instruments: Используйте Leaks и Allocations для поиска утечки памяти и цикличных ссылок.
🔸 Логирование: Добавьте print в deinit для критичных объектов.
💡 Вывод:
🔹 [weak self] — не панацея. Всегда анализируйте область видимости сильных ссылок.
🔹 Для бесконечных задач (repeat, while) используйте локальный guard или явный cancel.
🔹 Пишите тесты на освобождение памяти — это сэкономит часы отладки.
➡️ Подписаться на канал
Мобильный трудоголик