Когда в Swift необходимо использовать `[weak self]` в замыкании?

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

Ответ

Использовать [weak self] не всегда обязательно. Это требуется для предотвращения цикла сильных ссылок (retain cycle), который возникает, когда объект и захваченное им замыкание удерживают друг друга, что приводит к утечке памяти.

Когда [weak self] НЕ нужен?

  1. Синхронное выполнение: Замыкание выполняется немедленно и не сохраняется.
    let squares = numbers.map { $0 * $0 } // Нет цикла, замыкание живет только на время вызова `map`.
  2. Замыкание не захватывает self:
    DispatchQueue.main.async {
        print("Выполнено") // self не используется
    }
  3. Использование [unowned self]: В строго контролируемых сценариях, где время жизни объекта гарантированно превышает время жизни замыкания (рискованно).

Когда [weak self] ОБЯЗАТЕЛЕН?

  1. Асинхронные вызовы с сохранением замыкания: Когда замыкание хранится как свойство или передается в асинхронную операцию (сеть, таймеры, анимации с completion).
    class DataLoader {
        func loadData(completion: @escaping (Result) -> Void) {
            networkService.fetch { [weak self] result in // Обязательно weak!
                self?.handle(result) // self стал optional
                completion(result)
            }
        }
    }
  2. Захват self в замыкании, которое само является свойством этого self: Классический цикл.

    class MyClass {
        var closure: (() -> Void)?
    
        func setup() {
            // ЦИКЛ: self -> closure -> self
            closure = { self.doSomething() } // УТЕЧКА!
            // Решение:
            closure = { [weak self] in self?.doSomething() }
        }
    }

Практическое правило

Если замыкание является @escaping и внутри него используется self — в 99% случаев нужен [weak self]. Всегда анализируйте жизненные циклы объектов.