Что означает атрибут @escaping для замыкания в Swift?

Ответ

Атрибут @escaping указывает, что переданное в функцию замыкание может быть вызвано после завершения работы этой функции. Это необходимо, когда замыкание сохраняется для асинхронного или отложенного выполнения.

Типичные сценарии использования:

  • Асинхронные сетевые запросы.
  • Анимации с завершением.
  • Обработчики в GCD (DispatchQueue).
  • Сохранение замыкания в свойстве класса.

Пример с сетевым запросом:

func loadUserProfile(for userId: String, completion: @escaping (Result<User, Error>) -> Void) {
    let url = URL(string: "https://api.example.com/users/(userId)")!

    // dataTask запускает асинхронную операцию и возвращает управление сразу.
    URLSession.shared.dataTask(with: url) { data, response, error in
        // Это замыкание выполняется ПОСЛЕ завершения `loadUserProfile`.
        DispatchQueue.main.async {
            if let error = error {
                completion(.failure(error))
            } else if let data = data {
                // Декодирование и вызов completion
                completion(.success(decodedUser))
            }
        }
    }.resume()
    // Функция loadUserProfile завершается здесь, но completion еще не вызван.
}

Ключевые отличия и правила:

Не-escaping замыкание (по умолчанию) Escaping замыкание (@escaping)
Должно быть выполнено до выхода из функции. Может жить дольше, чем функция, которая его приняла.
Не требует явного захвата self. Требует явного захвата ([weak self] или [unowned self]) для избежания циклов сильных ссылок.
Может быть оптимизировано компилятором. Создает дополнительную нагрузку на память.

Используйте @escaping только тогда, когда замыкание должно пережить контекст функции.

Ответ 18+ 🔞

А, слушай, про этот @escaping аттрибут! Ну, блядь, классика, вечная путаница у всех новичков. Сейчас разжуем, как есть.

Представь себе: ты вызываешь функцию, кидаешь в неё замыкание, и обычно оно там внутри и помирает, выполнившись. Всё чинно, благородно. Но бывают случаи, когда это замыкание — хитрая жопа, которая норовит сбежать из функции и жить своей жизнью дальше. Вот как раз для таких беглецов и нужна метка @escaping. Она как кричит компилятору: «Э, бошка, думай! Этот клоужер может быть вызван потом, когда от нашей функции мокрого места не останется!»

Где эта мартышлюшка вылазит? Да везде, где асинхронщина или отложенный вызов:

  • Ты дернул что-то из сети и ждёшь ответа.
  • Запустил анимацию на 2 секунды и хочешь что-то сделать после.
  • Сказал системе «выполни это чёртово задание в другом потоке, а я пошёл».
  • Решил сохранить замыкание в свойство какого-нибудь объекта, чтобы потом ебнуть.

Смотри, классический пример — сетевой запрос:

func loadUserProfile(for userId: String, completion: @escaping (Result<User, Error>) -> Void) {
    let url = URL(string: "https://api.example.com/users/(userId)")!

    // dataTask — это асинхронный долбоёб. Он стартует и сразу говорит "пока!"
    URLSession.shared.dataTask(with: url) { data, response, error in
        // А это замыкание выполнится где-то потом, через секунду, через пять...
        DispatchQueue.main.async {
            if let error = error {
                completion(.failure(error)) // Опа! А функция-то loadUserProfile уже давно кончилась!
            } else if let data = data {
                // Декодирование и вызов completion
                completion(.success(decodedUser))
            }
        }
    }.resume()
    // Функция loadUserProfile тут благополучно сдохла. А completion ещё даже не нюхал.
}

Вот поэтому completion и помечаем как @escaping. Потому что он сбежал из контекста функции и живёт теперь в недрах URLSession.

А теперь, чтобы совсем не было мути, держи шпаргалку, чем они отличаются:

Обычное замыкание (не-escaping, дефолтное) Беглое замыкание (@escaping)
Должно отработать и умереть, пока функция не завершилась. Может пережить функцию, которая его приняла, и выстрелить потом.
На self можно ссылаться без затей, всё и так ясно. Тут уже надо головой думать! Обязательно явно указывай, как захватывать self[weak self] или [unowned self], а то наделаешь циклов сильных ссылок и память потечёт, как сука.
Компилятор его может по-хитрому оптимизировать. Создаёт дополнительную возню и нагрузку.

Короче, правило простое, блядь: если видишь, что замыкание куда-то сохраняется, передаётся в асинхронную задачу или вообще улетает в неизвестность — лепи @escaping. Во всех остальных случаях — расслабься и не парись.