День 16 — async/await и отмена
Эта статья идёт прямо перед Cupcake Corner. Её цель узкая: понять один сетевой запрос, не заморозить интерфейс и корректно обработать ошибку.
Почему обычной функции недостаточно
Сеть отвечает не сразу. Функция с async может приостановиться, пока ждёт результат, а await отмечает это место.
func loadMessage() async throws -> String {
let url = URL(string: "https://example.com/message.txt")!
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse,
200..<300 ~= http.statusCode else {
throw URLError(.badServerResponse)
}
return String(decoding: data, as: UTF8.self)
}async означает «функция может приостановиться», а не «она всегда запускается в другом потоке». throws отдельно сообщает, что операция может завершиться ошибкой.
Запуск из SwiftUI
.task связывает асинхронную работу с жизненным циклом View. Когда экран исчезает, SwiftUI запрашивает отмену task.
struct MessageView: View {
enum LoadState {
case idle
case loading
case loaded(String)
case failed(String)
}
@State private var state = LoadState.idle
var body: some View {
Group {
switch state {
case .idle, .loading:
ProgressView("Загрузка…")
case .loaded(let message):
Text(message)
case .failed(let message):
ContentUnavailableView("Не удалось загрузить", systemImage: "wifi.exclamationmark", description: Text(message))
}
}
.task {
state = .loading
do {
state = .loaded(try await loadMessage())
} catch is CancellationError {
return
} catch {
state = .failed(error.localizedDescription)
}
}
}
}Отмена не всегда ошибка для пользователя: экран мог просто исчезнуть. Остальные ошибки показывайте понятным состоянием и, когда уместно, кнопкой повтора.
Что нужно для Cupcake Corner
- Сделать функцию
async. - Вызвать
URLSessionчерезtry await. - Проверить HTTP status, а потом декодировать данные.
- Различать loading, success и failure.
- Не очищать введённый заказ при ошибке — пользователь должен суметь повторить запрос.
Checkpoint
Условие
Измените MessageView: добавьте кнопку повтора после ошибки. Кнопка меняет reloadID, а .task(id:) запускает новую загрузку.
Что нужно знать
async,await,throws;.task(id:);- enum для состояния экрана;
- отдельная обработка отмены.
Показать подсказку
Храните @State private var reloadID = 0 и увеличивайте его в Button.
Показать решение
Button("Повторить") { reloadID += 1 }
// На контейнере экрана:
.task(id: reloadID) {
await load()
}Глубже: параллельные операции
Когда две загрузки независимы и обе нужны экрану, async let запускает их одновременно:
async let profile = loadProfile()
async let messages = loadMessages()
let screen = try await (profile, messages)Для первого сетевого проекта это не требуется. Actors и Sendable разобраны отдельно после практики с базовым async/await.
Дальше примените эти шаги в Project 10 — Cupcake Corner →.
Комментарии
Вопросы, находки и подсказки по этой странице.
Войди, чтобы задать вопрос или помочь другим.