Когда и зачем использовать weak при захвате self в замыкании в Swift?

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

Ответ

Ключевое слово weak используется в списке захвата замыкания для предотвращения цикла сильных ссылок (retain cycle).

Когда это необходимо? Используйте [weak self], когда:

  1. Замыкание хранится как свойство (например, колбэк, обработчик).
  2. self владеет этим замыканием (прямо или косвенно).
  3. Замыкание может пережить self (является @escaping).

Пример проблемы и решения:

class DataLoader {
    var onComplete: (() -> Void)? // Хранимое escaping-замыкание

    func loadData() {
        // БЕЗ weak: ЦИКЛ! DataLoader владеет onComplete, а замыкание сильно ссылается на DataLoader.
        // onComplete = { self.handleData() } // Память никогда не освободится.

        // С weak: Цикл разорван.
        onComplete = { [weak self] in
            // self стал опциональным из-за weak
            self?.handleData()
        }
    }
    func handleData() { print("Data processed") }
    deinit { print("DataLoader deallocated") }
}

Альтернатива unowned: Используйте [unowned self], только если вы абсолютно уверены, что замыкание не переживет self. В противном случае это приведет к аварийному завершению.

// Допустимо, если замыкание выполняется синхронно в рамках жизни self.
dispatchQueue.async { [unowned self] in
    self.updateUI() // Crash, если self к этому моменту освобожден!
}

Лучшая практика: По умолчанию используйте weak для @escaping замыканий и безопасно разворачивайте self с помощью guard:

onComplete = { [weak self] in
    guard let self = self else { return }
    self.handleData() // self теперь strong reference на время выполнения замыкания
}