Что такое escaping и nonescaping? Для чего они используются?ios-18

Основные понятия

Non-Escaping Closure

По умолчанию все closure параметры в Swift являются non-escaping. Это означает:

  • Замыкание должно быть выполнено до возврата из функции
  • Не может быть сохранено для последующего использования
  • Не требует специального управления памятью
func performOperation(with completion: () -> Void) {
    // Замыкание выполняется в рамках функции
    completion()
    // После выполнения функция завершается
}

Escaping Closure

Помечается с @escaping и может:

  • Быть вызвано после возврата из функции
  • Сохраняться в свойствах или глобальных переменных
  • Требует явного захвата self (при использовании в классах)
class DataManager {
    var completionHandlers: [() -> Void] = []

    func storeCompletion(_ completion: @escaping () -> Void) {
        // Сохраняем для последующего использования
        completionHandlers.append(completion)
    }
}

Когда использовать каждый тип

Non-Escaping применяется когда:

  1. Замыкание используется синхронно внутри функции
  2. Не требуется сохранение для будущего использования
  3. Работа с высокоуровневыми функциями (map, filter и т.д.)

Escaping необходимо когда:

  1. Асинхронные операции (сетевые запросы, анимации)
  2. Сохранение замыкания в свойства класса
  3. Работа с GCD (DispatchQueue)
  4. Использование в коллекциях замыканий

Практические примеры

1. Асинхронный сетевой запрос

func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, _, error in
        // Выполняется после возврата из fetchData
        if let error = error {
            completion(.failure(error))
        } else if let data = data {
            completion(.success(data))
        }
    }.resume()
}

2. Анимации UIKit

func animateView(_ view: UIView, completion: @escaping () -> Void) {
    UIView.animate(withDuration: 0.3, animations: {
        view.alpha = 0
    }, completion: { _ in
        // Вызывается после завершения анимации
        completion()
    })
}

3. Опасность циклических ссылок

class MyClass {
    var value = 10

    func doWork() {
        storeCompletion { [weak self] in
            // Обязательно weak/unowned!
            self?.value = 20
        }
    }
}

Производительность и оптимизация

  1. Non-escaping:

    • Может быть оптимизировано компилятором
    • Не увеличивает счетчик ссылок
    • Хранится в стеке
  2. Escaping:

    • Требует выделения в куче
    • Увеличивает счетчик ссылок
    • Может вызывать retain cycles

Автоматическое преобразование

Начиная с Swift 5, closure в не-escaping позициях могут иногда автоматически становиться escaping при необходимости:

func execute(_ block: () -> Void,
           concurrentlyWith other: () -> Void) {
    // block может вести себя как escaping внутри
    DispatchQueue.global().async(execute: block)
    other()
}

Резюмируем:

понимание различий между escaping и non-escaping closure критически важно для безопасной работы с памятью и асинхронными операциями в Swift. Non-escaping - выбор по умолчанию для большинства операций, тогда как escaping необходимо явно указывать для асинхронных задач и сохранения замыканий. Всегда учитывайте риски циклических ссылок при работе с escaping closure.