Ответ
Разница определяется временем жизни замыкания относительно функции, в которую оно передано.
@escaping замыкание — может быть вызвано после возврата из функции. Требует явного указания модификатора @escaping. Используется для асинхронных операций, хранения замыкания в свойствах или передачи в другую escaping-функцию.
Non-escaping замыкание (поведение по умолчанию) — гарантированно выполняется в пределах работы функции и не может быть сохранено или вызвано позже. Это позволяет компилятору проводить оптимизации.
Ключевые различия:
| Аспект | Non-escaping (по умолчанию) | @escaping |
|---|---|---|
| Время жизни | В пределах функции | Может пережить функцию |
| Захват self | Неявный, без риска цикла ссылок | Требует явного захвата ([weak self]) |
| Оптимизация | Компилятор может инлайнить | Меньше оптимизаций |
| Использование | Синхронные операции (map, filter) | Асинхронные колбэки, таймеры, уведомления |
Пример non-escaping замыкания:
// Замыкание выполняется синхронно внутри функции
func calculate(values: [Int], using transform: (Int) -> Int) -> [Int] {
var result: [Int] = []
for value in values {
result.append(transform(value)) // Выполняется ДО возврата
}
return result
}
// self захватывается неявно, без риска
class Calculator {
var multiplier = 2
func process(numbers: [Int]) -> [Int] {
// non-escaping, можно использовать self без weak
return calculate(values: numbers) { number in
return number * self.multiplier
}
}
}
Пример @escaping замыкания:
// Замыкание сохраняется и вызывается после задержки
func fetchData(from url: URL, completion: @escaping (Result<Data, Error>) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, _, error in
// Вызывается ПОСЛЕ возврата из fetchData
if let error = error {
completion(.failure(error))
} else if let data = data {
completion(.success(data))
}
}
task.resume()
// Функция завершается здесь, но completion ещё не вызван
}
// Обязателен явный захват self
class DataLoader {
var cachedData: Data?
func load() {
fetchData(from: someURL) { [weak self] result in
// weak self для избежания цикла ссылок
guard let self = self else { return }
switch result {
case .success(let data):
self.cachedData = data
self.updateUI()
case .failure(let error):
self.handleError(error)
}
}
}
}
Правила и best practices:
- Всегда используйте non-escaping по умолчанию, если возможно.
- Для
@escapingзамыканий обязательно рассматривайте захват[weak self]или[unowned self]для избежания retain cycles. - С Swift 5+ non-escaping является поведением по умолчанию, а
@escapingтребуется указывать явно. - Escaping-замыкания могут изменить состояние объекта после его деинициализации, поэтому нужна осторожность.
- Некоторые API (например,
DispatchQueue.async) всегда требуют escaping-замыканий.