简体   繁体   中英

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.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM