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