I have a search for multiple types, so the general search method has a generic type T, and multiple simple methods call it specifying the type as needed. I'd like to delay search method's start (debounce
in Combine) and I'd like to hide this code as deep as I can.
I'm generally using async/await API in my project, only using Combine if I have to. I didn't find a built-in solution for this using async/await so I'm gonna use Combine's debounce
. (Please let me know if there is a cleaner solution without Combine)
So I have to go from async/await to Combine and back. This requires to use withCheckedContinuation
which loses the T and causes an error:
// simple generic specification using Event
func searchEvents(query: String) async -> [Event] {
await search(query: query)
}
// simple generic specification using Artist
func searchArtists(query: String) async -> [Artist] {
await search(query: query)
}
// debouncing layer between specifications and generic search
private func search<T>(query: String) async -> [T] where T: Codable, T: Identifiable {
await withCheckedContinuation { continuation in
Debouncer().debounce(query) { query in
Task {
// Generic parameter 'T' could not be inferred
let a = await self.performSearch(query: query) // <- error here
continuation.resume(returning: a)
}
}
}
}
// generic search function, details don't matter
private func performSearch<T>(query: String) async -> [T] where T: Codable, T: Identifiable {
...
}
// class performing simple debouncing
class Debouncer {
static var shared = Debouncer()
private var debounceSubscription: AnyCancellable?
func debounce(_ string: String, block: (String) -> ()) {
debounceSubscription?.cancel()
debounceSubscription = Just(string)
.debounce(for: .milliseconds(200), scheduler: DispatchQueue.global(qos: .background))
.sink { string in
block(string)
}
}
}
Is there an elegant way to make it auto-understand the type without passing it as a parameter (type: T.Type, query: String)?
EDIT: Yeah, this thing doesn't work anyway. In the end I had to use Combine on a TextField itself like this:
.onChange(query) { query in
queryPublisher.send(query)
}
.onReceive(queryPublisher.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)) { query in
Task {
options = await fetchOptions(query)
}
}
I wanted to avoid this because there are a lot of text fields using search in the app, but creating a base class for them is easier than hiding debounce deeper I guess. Thanks everyone for the wonderful suggestions, and for the fix with generics which I now know how to use!
Tell the compiler that performSearch
will return an array of T
let a: [T] = await performSearch(query: query)
Now T in performSearch
will be the same as T in search
which the compiler can conclude what type it is.