Что такое @escaping замыкание в Swift?

Ответ

@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 тебе такую ошибку влепит, что волосы дыбом встанут. Удачи, не проёбывайся!