Добавить объявление

Retain Cycle в Swift: как не попасть в тонкую ловушку

Retain cycle - одна из самых коварных проблем в Swift. Даже опытные разработчики могут не заметить, как создают цикл удержания ссылок, который мешает освобождению памяти. Разберем пример, который выглядит безобидно, но содержит скрытую угрозу.

Что такое Retain cycle:


Retain Cycle (цикл удержания сильных ссылок) в Swift - это ситуация, когда два или более объекта удерживают друг друга через сильные ссылки (strong), не позволяя ARC (Automatic Reference Counting) освободить память. Это приводит к утечкам памяти, так как объекты никогда не освобождаются.

Проблемный код:


class MyClass {
var task: Task?

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 никогда не вызовется.

Как исправить:



Изменить область видимости сильной ссылки:

Перенесите guard внутрь цикла, чтобы ссылка освобождалась после каждой итерации:

task = Task { [weak self] in
repeat {
guard let self else { return }
self.performSomeWork() // Сильная ссылка только здесь
} while !Task.isCancelled
}


Использовать слабую ссылку везде:

Если метод не требует обязательного self:

task = Task { [weak self] in
repeat {
self?.performSomeWork() // слабая ссылка
} while !Task.isCancelled
}


Вручную разорвать цикл:

Вызовите 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.

  • Пишите тесты на освобождение памяти - это сэкономит часы отладки.
03.08.2025 23 414