I'm working on an SDK, and have developed a nice concise Combine pipeline method that accepts a generic parameter that's used to decode json with. Effectively, it's a re-usable combine pipeline for JSON -> Decodable
. Works really well. Here's what that pipeline looks like:
func records<Record: Decodable>(forRequest request:RestRequest ) -> AnyPublisher<[Record], Never> {
return NetworkService.publisher(for: request)
.tryMap({ (response) -> Data in
response.asData()
})
.decode(type: Wrapper<Record>.self, decoder: JSONDecoder())
.map({ (record) -> [Record] in
record.records
})
.catch({ _ in
Just([Record]())
})
.eraseToAnyPublisher()
}
Usage:
contactsCancellable = NetworkService.records(forRequest: request)
.receive(on: RunLoop.main)
.assign(to: \.contacts, on: self)
It's my understanding that Swift+Combine is inferring the generic parameter type from the assign(to:, on:)
call.
But the powers that be want a non-Combine version, and I'm really struggling to figure out how to help Swift infer the type. I tried building a direct analog like this:
func fetchRecords<Record: Decodable>(forRequest request: RestRequest,
_ completionBlock: @escaping (Result<[Record], RestClientError>) -> Void) {
RestClient.shared.send(request: request) { result in
switch result {
case .success(let response):
do {
let decoder = JSONDecoder()
let wrapper = try decoder.decode(Wrapper<Record>.self, from: response.asData())
completionBlock(.success(wrapper.records))
} catch {
completionBlock(.success([Record]()))
}
case .failure(let err):
completionBlock(.failure(err))
}
}
}
This compiles however, executing that method like this:
NetworkService.fetchRecords(forRequest: request) { records in
print(records)
}
Results in a lovingly cryptic error Generic parameter 'Record' could not be inferred
How can I specify that generic Record 'type' - anything that conforms to Decodable, in this non-combine version?
Ps: Here's that Wrapper struct:
struct Wrapper<R: Decodable>: Decodable {
var totalSize: Int
var done: Bool
var records: [R]
}
You could specify the generic type in the closure parameter list:
NetworkService.fetchRecords(forRequest: request) { (result: Result<[ConcreteRecordType], RestClientError>) {
switch result {
case .success(let records):
// "records" is of type [ConcreteRecordType]
//...
case .failure(let error):
//...
}
}
But that can be cumbersome having to provide the full Result
type in the closure, so I recommend that you fill in the generic type information by accepting it as a parameter. (like the Decoder
functions do.)
func fetchRecords<Record: Decodable>(ofType type: Record.Type, forRequest request: RestRequest, _ completionBlock: @escaping (Result<[Record], RestClientError>) -> Void) {
//... same code...
}
Then, you'd call it like this:
NetworkService.fetchRecords(ofType: ConcreteRecordType.self, forRequest: request) { result in
// No need to specify closure argument type :)
switch result {
case .success(let records):
// "records" is of type [ConcreteRecordType]
//...
case .failure(let error):
//...
}
}
Voila! The explicit type provided to fetchRecords
cascades down to the closure argument type. No need to provide the type in the closure parameter list.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.