Ответ
Completion handlers (замыкания) и async/await — это два способа работы с асинхронным кодом. async/await представляет собой синтаксический сахар над completion handlers, делающий асинхронный код линейным и читаемым.
Сравнение на примере сетевого запроса:
1. Подход с Completion Handler:
func fetchUserData(withId id: String,
completion: @escaping (Result<User, Error>) -> Void) {
let url = URL(string: "https://api.example.com/user/(id)")!
URLSession.shared.dataTask(with: url) { data, response, error in
// 1. Ручная проверка ошибок
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(NetworkError.noData))
return
}
do {
// 2. Декодирование в основном потоке (может блокировать UI)
let user = try JSONDecoder().decode(User.self, from: data)
DispatchQueue.main.async {
// 3. Обязательный переход на main queue для UI-обновлений
completion(.success(user))
}
} catch {
completion(.failure(error))
}
}.resume()
}
// Использование (ведет к "Pyramid of Doom" или "Callback Hell"):
fetchUserData(withId: "123") { result in
switch result {
case .success(let user):
self.updateUI(with: user)
fetchUserAvatar(for: user) { avatarResult in // Вложенный асинхронный вызов
// ...
}
case .failure(let error):
self.showError(error)
}
}
2. Подход с async/await (Swift 5.5+):
func fetchUserData(withId id: String) async throws -> User {
let url = URL(string: "https://api.example.com/user/(id)")!
// 1. `await` приостанавливает функцию, не блокируя поток
let (data, _) = try await URLSession.shared.data(from: url)
// 2. Декодирование. Функция `decode` также может быть async
let user = try JSONDecoder().decode(User.self, from: data)
// 3. Автоматический возврат в actor (например, MainActor) не требуется здесь,
// но для UI-обновлений нужно вызвать `await MainActor.run`
return user
}
// Использование (линейный код, похожий на синхронный):
Task { // Создает новый асинхронный контекст
do {
let user = try await fetchUserData(withId: "123")
let avatar = try await fetchUserAvatar(for: user) // Последовательный вызов
// Обновление UI должно происходить на главном потоке
await MainActor.run {
self.imageView.image = avatar
self.nameLabel.text = user.name
}
} catch {
await MainActor.run {
self.showError(error)
}
}
}
Ключевые преимущества async/await:
- Читаемость: Код выполняется сверху вниз, устраняя вложенность.
- Упрощенная обработка ошибок: Используется знакомый
try/catchвместо проверкиResultв completion. - Безопасность потоков: Компилятор помогает избежать распространенных ошибок (например, вызов completion более одного раза).
- Интеграция с акторами (Actors): Упрощает написание потокобезопасного кода.
Важно: async/await не заменяет полностью completion handlers. Они все еще нужны для взаимодействия с API, основанными на замыканиях, и для обратной совместимости.