Ответ
Методы, помеченные async, можно вызывать только из асинхронного контекста с использованием ключевого слова await. Основные способы создания такого контекста:
1. Внутри другой async функции:
func fetchUserData() async throws -> User {
// Вызов асинхронного метода с await
let data = try await networkService.fetchData(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
2. В блоке Task:
Это основной способ запуска асинхронного кода из синхронного контекста (например, из метода viewDidLoad).
// Запуск асинхронной задачи
Task {
// Это асинхронный контекст
let user = await fetchUserData()
// Обновление UI должно быть на MainActor
await MainActor.run {
self.usernameLabel.text = user.name
}
}
3. В модификаторе .task в SwiftUI:
SwiftUI предоставляет специальный модификатор для безопасного запуска асинхронных задач, связанных с жизненным циклом View.
struct ContentView: View {
@State private var data: String = ""
var body: some View {
Text(data)
.task { // Асинхронный контекст, созданный SwiftUI
data = await loadData()
}
}
}
Важные правила:
awaitне блокирует поток: Он приостанавливает выполнение текущейasyncфункции, позволяя потоку выполнять другую работу, пока ожидается результат.- Вызов из синхронного контекста запрещен: Нельзя использовать
awaitпросто так. Нужно обернуть вызов вTaskили находиться внутриasyncфункции. - Обработка ошибок: Асинхронные функции, которые могут выбросить ошибку (
throws), требуют использованияtry await.
Ответ 18+ 🔞
О, смотри-ка, какой вопрос подъехал! Про эти ваши async да await. Ну что ж, давай разжую, как есть, чтобы даже у самого тупого мартышлюшки в голове щёлкнуло.
Вот представь, что async метод — это как сосед, который вечно одалживает у тебя дрель, но вернёт её только завтра. Ты не можешь просто так взять и крикнуть ему из окна: «Вася, дрель!» и тут же её получить. Нет, блядь. Ты должен либо подождать (это наш await), либо заняться чем-то другим, пока он её не принесёт.
Но вся фишка в том, что ждать-то ты можешь только в определённых местах, а не где попало. Вот основные три конуры, где это законно:
1. Внутри другой такой же async конуры.
Это как если ты сам такой же Вася, который кому-то свою болгарку обещал. Ты звонишь другому челу и говоришь: «Мужик, я тебе болгарку отдам, как только мне сосед дрель вернёт». И ждёшь. В коде это выглядит так:
func fetchUserData() async throws -> User {
// Тут можно ждать, потому что мы уже в async-функции
let data = try await networkService.fetchData(from: url) // Ждём, пока сеть не выдаст данные
return try JSONDecoder().decode(User.self, from: data)
}
2. В блоке Task.
А это что? Это твой личный раб, которого ты нанимаешь специально, чтобы он сходил к Васе за дрелью, пока ты на диване валяешься. Ты из своего обычного, синхронного мира (типа viewDidLoad) говоришь: «Эй, раб, иди сделай асинхронную хуйню».
// Ты в синхронном методе, просто так ждать нельзя.
// Но можно создать Task — это как отдельную пещеру для асинхронщины.
Task {
// А вот тут внутри уже можно! Это отдельная асинхронная берлога.
let user = await fetchUserData() // Ждём пользователя
// UI трогать надо на главном потоке, это святое
await MainActor.run {
self.usernameLabel.text = user.name // Обновляем интерфейс
}
}
// И пошёл дальше, не блокируя главный поток. Красота!
3. В модификаторе .task в SwiftUI.
SwiftUI — он умный, он сам знает, когда view появляется и исчезает. И даёт тебе специальную коробочку .task, куда можно положить асинхронный код. Удалили view — коробочку выкинули, задача отменилась. Удобно, ёпта!
struct ContentView: View {
@State private var data: String = ""
var body: some View {
Text(data)
.task { // Эта штука создаёт асинхронный контекст за тебя!
// И тут опять можно ждать
data = await loadData() // Ждём загрузки
}
}
}
А теперь, блядь, правила, которые надо выжечь на жопе:
await— это неsleep. Он не блокирует поток. Он говорит: «Я тут приостановлюсь, отпускаю поток погулять, пусть другие задачи поработают. Как только моя дрель (данные) будет готова — я проснусь и продолжу». Это овердохуища важное отличие от старого подхода.- Вызвать
awaitпросто так с улицы — нельзя. Это как зайти в чужую квартиру и начать ждать, пока тебе приготовят пельмени. Тебя вышвырнут в шею с ошибкой компиляции. Нужно быть либо в другойasyncфункции, либо вTask, либо в.task. - Ошибки. Если твой
asyncметод ещё иthrows(может выбросить ошибку), то готовься к двойному протоколу:try await. Сначала «попробуй» (try), потом «подожди» (await). Пиздец, да? Но логично.
Короче, суть в том, чтобы не пытаться ждать там, где система тебе этого не разрешает. Создавай правильный контекст — и будет тебе счастье. Всё остальное — от лукавого и ведёт к крашам и адовым багам.