Какие проблемы памяти могут возникнуть, если замыкание внутри класса захватывает этот же класс?

«Какие проблемы памяти могут возникнуть, если замыкание внутри класса захватывает этот же класс?» — вопрос из категории Управление памятью, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Основная проблема — Retain Cycle (циклическая ссылка), ведущая к утечке памяти.

Как возникает: Класс (self) имеет сильную ссылку на замыкание (например, через свойство). Замыкание, захватывая self по умолчанию (strong), создает сильную ссылку обратно на класс. Они удерживают друг друга, и счетчик ссылок никогда не станет нулевым.

Пример утечки:

class DataManager {
    var onDataUpdated: (() -> Void)? // 1. Класс сильно владеет замыканием
    var data = ""

    func setupHandler() {
        onDataUpdated = {
            // 2. Замыкание по умолчанию сильно захватывает `self`
            self.processData() // Strong reference back to DataManager!
        }
    }
    func processData() { print(data) }
    deinit { print("DataManager deallocated") } // Не вызовется
}

Решение: Используйте weak или unowned capture lists.

// Решение 1: [weak self] (рекомендуется, если self может стать nil)
onDataUpdated = { [weak self] in
    guard let self = self else { return }
    self.processData()
}

// Решение 2: [unowned self] (только если self гарантированно существует на время выполнения замыкания)
onDataUpdated = { [unowned self] in
    self.processData() // Кратко, но вызовет краш, если self уже освобожден
}

Правило: Всегда анализируйте жизненный цикл. [weak self] — более безопасный выбор по умолчанию.