Ответ
Да, опциональное замыкание неявно является @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 поведение, следует:
- Избегать хранения замыкания.
- Вызывать его синхронно и гарантированно внутри функции.
- Рассмотреть возможность использования не-опционального замыкания со значением по умолчанию (например,
{}).
Ответ 18+ 🔞
А, ну это, блядь, классика! Сидишь такой, пишешь код, всё пучком, а тут бац — опциональное замыкание. И думаешь: «Ну, опциональное, и чё?». А оно, сука, оказывается, по умолчанию — @escaping! Прям как хитрая жопа, которая сама решает, когда ей сбежать из функции.
Почему так, нахуй? Да потому что компилятор — не телепат, ёпта! Он смотрит на твой опциональный completion: (() -> Void)? и такой: «А хули мне думать? Ты можешь его вызвать сейчас, а можешь — никогда, потому что он nil. Или сохранить куда-то, или запулить в асинхронную очередь. Так что, мудила, считай, что оно уже сбежало!». И всё, приехали.
Вот, смотри, как это выглядит в жизни, когда ты пытаешься его удержать, а оно уже в закате:
var storedClosure: (() -> Void)? // Этому свойству похуй на твои non-escaping амбиции
func performAsyncTask(completion: (() -> Void)?) {
// Пытаешься сохранить? Ага, щас, держи @escaping!
storedClosure = completion
// Или вот, классика жанра — отложенный вызов. Функция уже сдохла, а замыкание ещё живое.
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
completion?() // Пиздец, оно уже за пределами тела функции! Явный побег.
}
}
// Компилятор тут просто смотрит на это и тихо матерится: «Ну конечно, блядь, @escaping».
А как сделать, чтобы оно не сбегало, как последний подлец? Да нихуя не сделать, если оно опциональное! Swift тебе такого счастья не даст. Если тебе прям позарез нужно @nonescaping, то:
- Не храни его нигде, блядь! Вызови сразу и на месте, пока функция не кончилась.
- Гарантируй, что вызов будет синхронным и внутри функции. Но с опционалом это как танцевать с бубном вокруг
nil— можно промахнуться. - Вообще, забей на опционал! Сделай замыкание неопциональным, но с дефолтным пустым значением, типа
completion: () -> Void = {}. И будет тебе счастье, и компилятор успокоится.
Вот так вот, ебать мои старые костыли. Казалось бы, мелочь — опциональность, а сколько подводных ебучков вылезает. Swift — он такой, любит сюрпризы, сука.