Episode Links

Modeling the API Response

We’ll start by defining the response JSON structure as Decodable structs.

extension TopPodcastsAPI {
    struct Response: Decodable {
        let feed: Feed
    }

    struct Feed: Decodable {
        let results: [PodcastResult]
    }
}

Note that Response is Decodable. This will allow us to decode the JSON that comes back from the server. Every type used in a Decodable must implement Decodable.

Create struct PodcastResult with properties matching to JSON names obtained from the feed.

struct PodcastResult : Decodable {
    let id: String
    let artistName: String
    let name: String
    let artworkUrl100: String
    let genres: [Genre]
} 
struct Genre: Decodable {
    let name: String
    let genreId: String
}

Extracting a function to parse the response

Here we create a generic function that takes any Decodable type T and handles the parsing logic for us. Note that we are returning a closure this method (a function returning a function). Doing this allows us to compose the result of this function with the perform() function.

private func parseDecodable<T : Decodable>(
        completion: @escaping (Result<T, APIError>) -> Void
    ) -> (Result<Data, APIError>)

Change the completionHandler to handle success and failure. Note the changes in completionHandler, it takes Result<Data, APIError>and processes callback on the main queue.

switch result {
    case .success(let data):
        do {
        let jsonDecoder = JSONDecoder()
        let object = try jsonDecoder.decode(T.self, from: data)
        DispatchQueue.main.async {
            completion(.success(object))
        }
    }   
        catch let decodingError as DecodingError {
         DispatchQueue.main.async {
        completion(.failure(.decodingError(decodingError)))
         }
    }
        catch {
            fatalError("Unhandled error raised.")
    }
    case .failure(let error):
        DispatchQueue.main.async {
            completion(.failure(error))
        }   
}

In case of success, use JSONDecoder to decode the data. In case of failure, catch DecodingError and any unhandled error.

Fetching Parsed JSON Response

To fetch the response, switch Result<Data, APIError> to Result<Response, APIError>. We call parseDecodable through the completionBlock of func perform, this reduces nesting of closures.

func fetchTopPodcasts(limit: Int = 50, allowExplicit: Bool = false, completion: @escaping (Result<Response, APIError>) -> Void ) {
    //... fetching parsed response
    perform(request: request, completion: parseDecodable(completion: completion)) {
        //...
    }
}
blog comments powered by Disqus