Ответ
Атрибут @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. Во всех остальных случаях — расслабься и не парись.