I'm setting up an api client with Openweathermap for a Swift based iOS app. I followed a tutorial for how to set it up, but had to change weather providers because their service no longer provides free api keys. I have a bug with the type of the completion of my client but don't fully understand what's going on. The tutorial used a template type but I changed that to try to solve another bug.
I have been experimenting with using template vs a non templated completion format, but I don't understand the purpose or how it works fully.
class WeatherAPIClient: APIClient {
var session: URLSession
init(session: URLSession = URLSession.shared) {
self.session = session
}
func weather(with endpoint: WeatherEndpoint, completion: @escaping (Either<List, APIError>) -> Void){
let request = endpoint.request
self.fetch(with: request) { (either: Either<[List], APIError>) in
switch either {
case .value(let weather):
completion(.value(weather)) // <--- Original error line here
case .error(let error):
completion(.error(error))
}
}
}
}
enum Either<List, APIError> { // <-- Error changing this to [List] (comment error 1)
case value(List)
case error(APIError)
}
enum APIError: Error {
case apiError
case badResponse
case jsonDecoder
case unknown(String)
}
protocol APIClient {
var session : URLSession {get}
func fetch<List: Codable> (with request: URLRequest, completion: @escaping (Either<[List], APIError>) -> Void)
}
extension APIClient {
func fetch<List: Codable> (with request: URLRequest, completion: @escaping (Either<[List], APIError>) -> Void){
let task = session.dataTask(with: request) { (data, response, error) in
guard error == nil else {
completion(.error(.apiError))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
print(response)
return
}
guard httpResponse.statusCode == 200 else {
print(httpResponse.statusCode)
print(response)
completion(.error(.badResponse))
return
}
do {
let dictionaryFromJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String:Any]
let jsonItem = dictionaryFromJSON["list"] as? NSArray
let jsonData = try JSONSerialization.data(withJSONObject: jsonItem!, options: [])
let responseModel = try JSONDecoder().decode([List].self, from: jsonData as! Data)
// Error changing line the above [List] to List (comment error 2)
print(responseModel)
completion(.value(responseModel))
}
catch {
print("darn")
print(error)
completion(.error(.jsonDecoder))
}
}
task.resume()
}
}
When I print the response model I have a valid json with the data I want. The problem is passing it to the completion correctly. I get a compile time error that: "Member 'value' in 'Either' produces result of type 'Either', but context expects 'Either'"
Originally the error made sense because the result type was different than the context expected, but I changed the api client code so that they matched, yet I am still getting an error.
I removed the brackets and used an array object in the decode call to fix the code:
let responseModel = try JSONDecoder().decode([List].self, from: jsonData as! Data)
to
let responseModel = try JSONDecoder().decode(Array<List>.self, from: jsonData)
I then removed brackets from List everywhere and changed the enum definition of Either to use the new array format: case value(Array<List>)
I solved an intermediate error ("Expected to decode Dictionary but found an array instead.") when removing brackets from the JSON decoding line (shown above) using: Swift4 JSONDecoderExpected to decode Dictionary<String, Any> but found an array instead.