2

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.

ryankuck
  • 320
  • 3
  • 15
  • 2
    I see that in the `enum` definition you use `Either` but everywhere else you use `Either<[List], APIError>`. Are these meant to be the same? – Michael Fourre Sep 11 '19 at 18:45
  • When I change the enum definition to Either it gives an error: "Expected an identifier to name generic parameter." When I change everything to List with no brackets I get an error when decoding the JSON: "Expected to decode Dictionary but found an array instead." This error makes sense because the JSON is an array of dictionaries. – ryankuck Sep 11 '19 at 21:01
  • I think it would be helpful if you included these errors in your question, and particularly so if you specified exactly on which lines the errors are shown. That way it will be easier to see them in context. – Michael Fourre Sep 11 '19 at 21:05
  • My comment was supposed say there was an error for changing the enum definition to ```Either<[List], APIError>``` ( then it gives an error: "Expected an identifier to name generic parameter.") I've added comments to the code showing where both errors are. – ryankuck Sep 11 '19 at 21:47

1 Answers1

0

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.

ryankuck
  • 320
  • 3
  • 15