swift - Swift Type Inference with Generic method
问题描述
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.
推荐阅读
- sql-server - 在 SELECT 语句中编写完整路径会提高 SQL 的性能吗?
- html - 如何跨 JSON (AngularJS) 创建集合
- applescript - Applescript 处理程序,重复 i 从 1 到 this_list 的编号
- powerbi - Power BI 中的日期列分为 4 列
- node.js - 为什么我们需要 node 应用程序根目录的 package.json 中的 main 属性,如果它是为了告诉包和模块的入口点?
- python - 如何计算子目录中的文件数?
- kubernetes-ingress - GKE 上的一个 GCE 入口导致另一个 GCE 入口为默认后端提供服务
- sql - SQL Server 存储过程选择参数传递的特定列,并检查其他约束
- ruby - 为什么我在明显存在的对象上得到“nil:NilClass 的未定义方法”?
- perl - 如何替换 Perl 已弃用的 find.pl