Oliver H
Oliver H

Reputation: 133

Returning generic type from a URLRequest dataTask

I'm trying to build a generic function to return an array of a given type once a URLSession's dataTask has completed.

I have this Decodable:

struct Todo: Hashable, Codable, Identifiable {
    var id: Int
    var title: String
    var completed: Bool
}

And this function:

func loadFrom<T: Decodable>(url: String, memberType: T, completionHandler: (T?) -> Void) {
    guard let url = URL(string: url) else {
        completionHandler(nil)
    }

    URLSession.shared.dataTask(with: url) {data, response, error in
        guard let data = data else {
            fatalError("No data returned")
        }

        do {
            let decoder = JSONDecoder()
            let results = try decoder.decode(T.self, from: data)

            completionHandler(results)
        }catch {
            fatalError("Couldn't parse data")
        }
    }.resume()
}

loadFrom(url: "https://jsonplaceholder.typicode.com/todos?completed=true", memberType: Todo) {response in
    ...
}

error: Swift Scratchpad.playground:61:88: error: argument type 'Todo.Type' does not conform to expected type 'Decodable'

I've seen similar issues which point to the compiler being unable to synthesise the methods that the Decodable protocol requires for conformance, but building a similar method which is assigned to the Decodable type rather than having it specified as a parameter works:

func loadFile<T: Decodable>(file: String) -> T {
...
}

var todos: [Todo] = loadFile(file: "todos.json")
print(todos[0].title) => "The todo title"

I assume the fact that my loadFrom doesn't have its return type specified is the cause, but I don't understand why. Would it be possible to provide enough context for this code to compile?

Upvotes: 0

Views: 954

Answers (2)

vadian
vadian

Reputation: 285059

You have to declare

func loadFrom<T: Decodable>(url: String, memberType: T.Type, completionHandler: @escaping (T?) -> Void) {

and call it

loadFrom(url: "https://jsonplaceholder.typicode.com/todos?completed=true", memberType: Todo.self) {response in
    ...
}

And rather than the unhandy fatal errors add a better result type to be able to return also the errors and catch the bad URL before calling the method

func loadFrom<T: Decodable>(url: URL, memberType: T.Type, completionHandler: @escaping (Result<T,Error>) -> Void) {
    URLSession.shared.dataTask(with: url) {data, _, error in
        if let error = error {
            completionHandler(.failure(error))
        } else {
           completionHandler( Result{ try JSONDecoder().decode(T.self, from: data!)})
        }
    }.resume()
} 

Upvotes: 0

PGDev
PGDev

Reputation: 24341

In loadFrom(url:memberType:completionHandler:) method,

1. Use T.Type instead of T as data type for memberType

2. Add @escaping to the completionHandler

func loadFrom<T: Decodable>(url: String, memberType: T.Type, completionHandler: @escaping ((T?)->())) 

Call the method like,

loadFrom(url: "https://jsonplaceholder.typicode.com/todos?completed=true", memberType: Todo.self) {response in
     ...
}

Also, add return in the guard statement,

guard let url = URL(string: url) else {
    completionHandler(nil)
    return //here...
}

Upvotes: 1

Related Questions