Что такое escaping и non-escaping замыкания и в чем их отличие в Swift
Что проверяют
Понимаете ли вы, что различие про время жизни замыкания, и знаете ли, почему у экранирующего требуется явный self.
Как отвечать
Неэкранирующее замыкание (по умолчанию) гарантированно выполняется до возврата из функции и не сохраняется. Экранирующее (@escaping) может пережить вызов: его сохранили в свойстве, положили в массив обработчиков или передали в асинхронную операцию.
func load(completion: @escaping (Data) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, _ in
completion(data ?? Data()) // вызовется много позже возврата из load
}.resume()
}Почему это важно компилятору: неэкранирующее замыкание нельзя сохранить, значит его можно не удерживать в куче и можно оптимизировать. Плюс оно не создаёт циклов ссылок.
Практические следствия:
- в экранирующем нужен явный
self. Это не формальность, а напоминание: замыкание удержит объект сильной ссылкой, и если объект хранит само замыкание, получится цикл. Отсюда[weak self]; - неэкранирующее не требует
self, потому что удержания за пределами вызова не будет; - функции вроде
map,filter,forEachпринимают неэкранирующие замыкания, поэтому в нихselfписать не надо.
С появлением async/await значительная часть экранирующих колбэков заменяется на await, и вопрос циклов ссылок в этих местах отпадает.
Где спотыкаются
Не понимают, зачем в одних местах требуется self, а в других нет.
Ставят @escaping везде на всякий случай.
Забывают [weak self] в сохранённом замыкании и получают утечку.
Считают, что @escaping делает замыкание асинхронным: он лишь разрешает пережить вызов.