Является ли опциональное замыкание (optional closure) в Swift escaping по умолчанию?

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

Ответ

Да, опциональное замыкание неявно является @escaping.

Причина: Компилятор Swift не может гарантировать, что опциональное замыкание будет вызвано синхронно в пределах тела функции. Поскольку замыкание может быть nil, его вызов часто откладывается или выполняется условно, что подразумевает необходимость его сохранения (escaping).

Пример, требующий escaping:

var storedClosure: (() -> Void)? // Для хранения замыкания оно должно быть escaping

func performAsyncTask(completion: (() -> Void)?) {
    // Сохранение в свойство или асинхронный вызов требуют escaping
    storedClosure = completion

    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        completion?() // Вызов после завершения функции — явный признак escaping
    }
}
// Компилятор автоматически трактует `completion` как @escaping в этом случае.

Как сделать его non-escaping? Фактически, нет способа явно объявить опциональное замыкание как @nonescaping. Если нужно non-escaping поведение, следует:

  1. Избегать хранения замыкания.
  2. Вызывать его синхронно и гарантированно внутри функции.
  3. Рассмотреть возможность использования не-опционального замыкания со значением по умолчанию (например, {}).