В Swift, когда замыкание в параметре функции нужно помечать как @escaping?

«В Swift, когда замыкание в параметре функции нужно помечать как @escaping?» — вопрос из категории Swift Core, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Пометка @escaping требуется, когда замыкание должно "пережить" время выполнения функции. Это происходит в двух основных сценариях:

  1. Сохранение: Когда замыкание сохраняется в свойстве, глобальной переменной или любой другой структуре данных вне области видимости функции.
  2. Асинхронность: Когда замыкание передается в асинхронный вызов (например, в очередь DispatchQueue), где оно будет выполнено позже.

Пример non-escaping замыкания:

func processItems(_ items: [Int], using handler: (Int) -> Void) {
    for item in items {
        handler(item) // Выполняется немедленно, внутри функции
    }
    // К этому моменту замыкание `handler` больше не существует
}

Пример escaping замыкания:

class DataManager {
    var completionHandlers: [() -> Void] = []

    func fetchData(completion: @escaping () -> Void) {
        // 1. Сохраняем замыкание для вызова позже
        completionHandlers.append(completion)

        // 2. Или передаем в асинхронную задачу
        DispatchQueue.global().async {
            // Долгая операция...
            DispatchQueue.main.async {
                completion() // Вызывается после завершения `fetchData`
            }
        }
    }
}

Ключевое отличие: Использование @escaping требует от разработчика явного управления захватом переменных (например, с помощью [weak self]), чтобы избежать цикла сильных ссылок, так как время жизни замыкания теперь неизвестно.