Ответ
@escaping — атрибут параметра-замыкания, указывающий, что замыкание может быть вызвано после возврата из функции. Требуется для асинхронных операций.
Пример с сетевым запросом:
func downloadData(from url: URL,
completion: @escaping (Result<Data, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, error in
// Замыкание выполняется асинхронно, после возврата из downloadData
if let error = error {
completion(.failure(error))
} else if let data = data {
completion(.success(data))
}
}.resume()
// Функция завершается здесь, но completion ещё не вызван
}
Обязательное использование weak/unowned:
class DataLoader {
var cachedData: Data?
func loadData(completion: @escaping (Data) -> Void) {
downloadData(from: someURL) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let data):
self.cachedData = data
completion(data)
case .failure(let error):
print("Ошибка: (error)")
}
}
}
}
| Ключевые отличия: | Не-escaping (по умолчанию) |
Escaping (@escaping) |
|---|---|---|
| Вызывается до возврата функции | Может быть вызвано позже | |
| Не требует захвата self | Требует [weak self] для избежания retain cycle |
|
| Может быть mutating для value types | Не может изменять value types | |
| Оптимизируется компилятором | Меньше оптимизаций |
Ответ 18+ 🔞
А, блядь, @escaping! Ну это же классика, ёпта! Сидишь такой, пишешь функцию, а тебе компилятор начинает мозг ебать: «Ты че, мудила, забыл escaping поставить?».
Смотри, чувак, вот в чём суть. Когда ты пишешь замыкание как параметр, оно по умолчанию — не сбегающее. Это как будто ты говоришь: «Слушай, замыкание, ты тут посиди, я тебя прямо сейчас внутри функции вызову и отпущу. Никуда не денешься».
А @escaping — это как крик души: «Блядь, замыкание, ты можешь сбежать! Ты можешь жить своей жизнью, тебя вызовут потом, когда я уже давно сдохну и верну nil!».
Вот тебе живой пример, сетевая хуйня:
func downloadData(from url: URL,
completion: @escaping (Result<Data, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, error in
// Смотри-ка, замыкание! Оно выполнится через секунду, через пять, через хрен знает когда.
// Функция `downloadData` уже давно вернула управление, а этот кусок кода ещё только думает, как бы тебе данные подсунуть.
if let error = error {
completion(.failure(error))
} else if let data = data {
completion(.success(data))
}
}.resume()
// Функция тут же заканчивается! А completion ещё даже не нюхал.
}
И вот тут начинается самое интересное, ёбаный в рот! Если ты в этом сбежавшем замыкании используешь self, то ты создаёшь retain cycle — классическую утечку памяти, после которой твое приложение накрывается медным тазом.
Поэтому правило простое, как три рубля: если @escaping и используешь self — бейся головой об стену, пока не вспомнишь про [weak self].
Смотри, как правильно, чтобы не обосраться:
class DataLoader {
var cachedData: Data?
func loadData(completion: @escaping (Data) -> Void) {
downloadData(from: someURL) { [weak self] result in
// Слабая ссылка! Если `DataLoader` уже умер, self будет nil, и мы просто выйдем.
guard let self = self else {
print("Объект сдох, похуй")
return
}
switch result {
case .success(let data):
self.cachedData = data // Теперь безопасно обращаемся к self
completion(data)
case .failure(let error):
print("Ошибка, блядь: (error)")
}
}
}
}
А чтобы ты совсем не запутался, вот тебе табличка, в рот меня чих-пых:
| Не-escaping (по умолчанию) | Escaping (@escaping) |
|---|---|
| Вызовут ДО того, как функция вернёт управление. Сиди тут, не рыпайся. | Вызовут ПОСЛЕ. Может быть через час, может никогда. Сбежавшее, ёпта. |
С self можно общаться без церемоний, retain cycle не случится. |
Трогаешь self без [weak] — готовься к утечке памяти, пидорас. |
Может покоцать value types (изменять их), если они mutating. |
С value types так не поиграешь, они для него слишком непостоянные. |
| Компилятор его оптимизирует так, что мама не горюй. | Оптимизаций меньше, потому что жизнь у него сложная, непредсказуемая. |
Запомни, чувак: увидел асинхронщину, сетевой запрос, таймер, анимацию — сразу ищи, где бы тут @escaping впендюрить. А если забыл — Xcode тебе такую ошибку влепит, что волосы дыбом встанут. Удачи, не проёбывайся!