SwiftUIMiddleTechnical
В чём разница между модификаторами .onAppear и .task?
.onAppear — синхронный колбэк без поддержки async/await и без автоотмены; .task запускает async-замыкание и автоматически отменяет его, когда вью исчезает. Для сетевых запросов и AsyncSequence используйте .task.
Разница между .onAppear и .task
Оба модификатора реагируют на появление представления на экране, но имеют принципиально разную природу: .onAppear — синхронный колбэк, .task — встроенный механизм запуска асинхронных задач с автоматической отменой.
.onAppear
- Вызывается синхронно в главном потоке в момент появления вью.
- Не поддерживает
async/awaitнапрямую — чтобы запустить асинхронный код, нужно явно создаватьTask { ... }. - Задача не отменяется автоматически при исчезновении вью.
- Подходит для синхронных побочных эффектов: аналитика, изменение состояния, запуск таймера.
struct ExampleView: View {
@State private var items: [Item] = []
var body: some View {
List(items) { item in Text(item.name) }
.onAppear {
// Синхронно
Analytics.log("screen_viewed", name: "ExampleView")
// Асинхронный код требует явного Task
Task {
items = try await ItemService.fetch()
}
}
}
}
.task
- Запускает
async-замыкание при появлении вью и автоматически отменяет (Task.cancel()) его при исчезновении. - Принимает опциональный
id:— при измененииidзадача перезапускается, старая отменяется. - Приоритет задачи настраивается:
.task(priority: .background) { ... }. - Подходит для сетевых запросов, подписок на
AsyncSequence, любых асинхронных операций.
struct ExampleView: View {
@State private var items: [Item] = []
let categoryID: UUID
var body: some View {
List(items) { item in Text(item.name) }
.task(id: categoryID) {
// Запускается при появлении и при смене categoryID
do {
items = try await ItemService.fetch(category: categoryID)
} catch is CancellationError {
// Вью исчезло — задача отменена, это нормально
} catch {
print("Ошибка: \(error)")
}
}
}
}
Подписка на AsyncSequence
.task {
for await notification in NotificationCenter.default
.notifications(named: .NSManagedObjectContextDidSave)
.map({ $0 }) {
await refreshData()
}
// Цикл автоматически завершится при отмене задачи
}
Сравнительная таблица
- async/await: .task — да, нативно; .onAppear — нет, нужен явный Task { }
- Автоотмена при disappear: .task — да; .onAppear — нет
- Перезапуск при смене id: .task(id:) — да; .onAppear — нет
- Синхронные побочные эффекты: оба подходят
Подводные камни
- Утечка задачи в .onAppear — если внутри
.onAppearсозданTask { }без хранения токена, задача продолжает выполняться после исчезновения вью и может обновлять уже не существующее состояние. - Игнорирование CancellationError в .task — не обрабатывать отмену как ошибку: повторные попытки (retry) при отмене создают неотменяемые циклы.
- id: Equatable — часто меняющиеся значения — если
idв.task(id:)меняется очень часто (например, текст поискового запроса без debounce), задача будет постоянно перезапускаться и отменяться, создавая лишнюю нагрузку. - Двойной вызов в preview — в SwiftUI Preview и при hot reload
.onAppear/.taskмогут вызываться дважды; код должен быть идемпотентным. - .task не является заменой .onAppear везде — для чисто синхронных операций (установка фокуса, трекинг аналитики) использование
.taskизбыточно и добавляет ненужный overhead планировщика. - Приоритет задачи — по умолчанию
.taskиспользует.userInitiated; для фоновых операций явно передавайтеpriority: .background, иначе тяжёлые вычисления блокируют UI.
Common mistakes
- Сводить «модификаторами
.onAppearи.task» к синтаксису и не объяснять идентичность view. - Игнорировать жизненный цикл, основной поток или момент освобождения ресурсов в сценарии swiftui-15.
- Выбирать API по привычке, не проверяя состояние, ошибки, доступность и платформенные ограничения.
What the interviewer is testing
- Формулирует точную модель для «модификаторами
.onAppearи.task» и подтверждает ее корректным примером. - Умеет связать ответ с инвалидация body, тестированием и отладкой на устройстве.
- Называет ограничения подхода swiftui-15, включая производительность, память и сопровождение.