Что означает ошибка компиляции при отсутствии атрибута escaping?
Что проверяют
Знаете ли вы, что замыкания в Swift по умолчанию неубегающие, и понимаете ли, зачем компилятор вообще следит за временем их жизни. За этим вопросом почти всегда идёт следующий: про захват self и циклы удержания.
Как отвечать
Сообщение звучит примерно как escaping closure captures non-escaping parameter или converting non-escaping value to a function type may allow it to escape. Смысл один: функция получила замыкание с обещанием выполнить его до возврата, а код пытается сохранить замыкание и позвать позже, то есть положить в свойство, в массив, отдать в асинхронную очередь или в сетевой клиент.
func load(_ done: () -> Void) {
DispatchQueue.global().async {
done() // ошибка: done не помечен @escaping
}
}
func load(_ done: @escaping () -> Void) { ... } // так компилируетсяДальше стоит объяснить, ради чего это ограничение. Неубегающее замыкание компилятор может не удерживать сильной ссылкой и разместить дешевле, а ещё разрешает внутри него обращаться к свойствам без явного self. Как только стоит @escaping, замыкание переживает вызов, self пишется явно, и появляется повод для [weak self].
Где спотыкаются
Считают, что без @escaping замыкание не может захватывать переменные. Захватывать может любое, ограничение касается только времени жизни: вызвать такое замыкание нужно до выхода из функции, а вот сохранить его нельзя.
Пробуют пометить @escaping опциональное замыкание и получают новую ошибку. Параметр типа ((Int) -> Void)? убегающий по умолчанию, потому что опционал оборачивает функцию в структуру, и атрибут к нему не применяется.
Ставят @escaping наугад, лишь бы собралось, и получают утечку. Объект держит сохранённое замыкание, замыкание сильно держит self, и экран не освобождается. Лечится списком захвата и обнулением сохранённого замыкания после вызова.