Cancel the in-flight search when the query changes, two ways
A search screen refires a request on every keystroke. Cancel the previous request when the query changes — implement it with a stored task, and note the SwiftUI equivalent.
Requirements:
- Cancel the in-flight search before starting a new one.
- The result must only apply if this search was not cancelled.
@MainActor final class SearchModel: ObservableObject {
@Published var results: [Item] = []
private var searchTask: Task<Void, Never>?
func search(_ query: String) {
// your code here
}
}
Write the implementation.
Store the search in a Task?; on each new query, cancel() it before the next so the stale request stops. In SwiftUI .task(id: query) does the same on a query change. The fetch must still check cancellation to stop.
- ✗Assuming dropping a
Taskreference cancels it - ✗Thinking
cancel()aborts the network call without a check - ✗Believing
.task(id:)ignores changes to theidvalue
- →Why must the search function itself check
Task.isCancelled? - →How does
.task(id:)decide when to restart its async body?
Storing the task lets you cancel it; cancel() only sets a flag, so applying the result is guarded by a Task.isCancelled check:
func search(_ query: String) {
searchTask?.cancel() // stop the stale request
searchTask = Task {
let items = try? await api.search(query) // api.search checks Task.isCancelled
guard !Task.isCancelled else { return }
results = items ?? []
}
}
The declarative equivalent in the view needs no manual bookkeeping:
List(model.results) { item in Text(item.title) }
.task(id: query) { await model.load(query) } // cancels + restarts on every query change
Both rely on the fetch honoring cancellation: .cancel() only sets the flag, so api.search (or its URLSession call) must observe it to actually abort.