Reputation: 133
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
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
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