Group Group Group Group Group Group Group Group Group

Using Generic parameter in closure

Hello,

I have a function that makes a service call to fetch data, uses JSONDecoder to decode the JSON response into a model object and then return that object as part of a parameter in the completion block provided to the function. In the object, I am actually using the Result object.
Here is the code -

static func fetchData(_ completion: @escaping (Result<ExampleModelObject, Error>) -> Void)

I am trying to make this function generic, where it can accept the url it needs to call as one parameter and the Model object as another parameter and so I modified it to this -

static func fetchData <T: Decodable>(urlString: String, _ completion: @escaping (Result<T, Error>) -> Void)

This is the code where I use jsondecoder -

let parsedJSON = try jsonDecoder.decode(T.self, from: data)
            completion(.success(parsedJSON))

Here is how I am trying to call this function from another class -

DataRetriever. fetchData(urlString: dataURLString) { (result: ExampleModelObject) in

However, I am getting 2 errors during compilation -

  1. Cannot convert value of type ‘(ExampleModelObject) → Void’ to expected argument type ‘(Result<_, Error>) → Void’
  2. Generic parameter ‘T’ could not be inferred

Would anyone please be able to help me with how I can fix these errors?

Swift’s generics do not allow you to leave off placeholder types. You’ll need to fully qualify the parameter as Result<ExampleModelObject, Error>.

However, there’s no reason to rely on Result if your error isn’t typed. You can just use a throwing closure as a parameter. (Which is the same as Swift 5.5’s async throws, but uglier.)

static func fetchData<T: Decodable>(
  urlString: String,
  _ completion: @escaping (() throws -> T) -> Void
) {
DataRetriever.fetchData(urlString: dataURLString) { (getData: () throws -> ExampleModelObject) in
  do {
    let data = try getData()
1 Like

Thanks for the above @jcatterwaul ! That was helpful.

I am still learning swift and so I am struggling to understand what you meant by the above. I thought we usually would use throws only for throwing errors and exceptions.

2 Likes

The Result type’s main purpose is to preserve the specific type of errors. If your failure type is Error itself, then Result doesn’t add any functionality to your code.

For example, given this code…

struct ExampleModelObject {
  struct Error: Swift.Error { }
}

Result would be very useful if your failure type was going to be that one in particular:

var result: Result<ExampleModelObject, ExampleModelObject.Error>

But otherwise, these two types convey the same information. That’s because Swift’s throwing closures, get accessors, and functions, do not preserve error type information—only that some type of error might be thrown.

var getExampleModelObject: () throws -> ExampleModelObject
var result: Result<ExampleModelObject, Error>

Successes:

getExampleModelObject = { .init() }
result = .success(.init())

…or failures:

getExampleModelObject = { throw ExampleModelObject.Error() }
result = .failure(ExampleModelObject.Error())
result = .init { throw ExampleModelObject.Error() }

(The last line uses this initializer, which only works with untyped failures.)

@jcatterwaul Thanks for the detailed explanation!

1 Like