Отмените текущий поиск при смене запроса — двумя способами
Экран поиска перезапускает запрос на каждое нажатие клавиши. Отмените предыдущий запрос при смене запроса — реализуйте через хранимую задачу и укажите аналог в SwiftUI.
Требования:
- Отмените текущий поиск до запуска нового.
- Результат применяется, только если этот поиск не был отменён.
@MainActor final class SearchModel: ObservableObject {
@Published var results: [Item] = []
private var searchTask: Task<Void, Never>?
func search(_ query: String) {
// ваш код здесь
}
}
Допишите реализацию.
Храните поиск в Task?; при новом запросе — cancel() перед следующим, чтобы старый запрос остановился. В SwiftUI то же — .task(id: query) при смене query. Fetch должен проверять отмену, чтобы остановиться.
- ✗Считают, что сброс ссылки на
Taskотменяет её - ✗Думают, что
cancel()обрывает сетевой вызов без проверки - ✗Полагают, что
.task(id:)не реагирует на смену значенияid
- →Почему сама функция поиска должна проверять
Task.isCancelled? - →Как
.task(id:)решает, когда перезапустить своё async-тело?
Хранение задачи позволяет её отменить; cancel() лишь выставляет флаг, поэтому применение результата защищено проверкой Task.isCancelled:
func search(_ query: String) {
searchTask?.cancel() // остановить устаревший запрос
searchTask = Task {
let items = try? await api.search(query) // api.search сам смотрит Task.isCancelled
guard !Task.isCancelled else { return }
results = items ?? []
}
}
Декларативный аналог в view не требует ручного учёта:
List(model.results) { item in Text(item.title) }
.task(id: query) { await model.load(query) } // отмена + перезапуск при смене query
Оба варианта опираются на то, что fetch уважает отмену: .cancel() только выставляет флаг, поэтому api.search (или его вызов URLSession) должен его увидеть, чтобы реально прерваться.