Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

iOS Network Programming with Swift 2 Implementing a Forecast Client From JSON to CurrentWeather

Kevin Gutowski
Kevin Gutowski
4,082 Points

Class ForecastAPIClient isn't conforming to APIClient

Can't seem to get conformance to APIClient. Could perhaps someone explain to me how exactly we are able to conform to the two functions JSONTaskWithRequest & fetch within ForecastAPIClient? I have a hunch that the culprit might be with those two functions. Any guidance would be greatly appreciated.

Note: I'm using Swift 3.0 with Xcode 8 Beta 6

Here's my code

For APIClient:

protocol APIClient {
    var configuration: URLSessionConfiguration { get }
    var session: URLSession { get }

    func JSONTaskWithRequest(request: URLRequest, completion: JSONTaskCompletion) -> JSONTask
    func fetch<T: JSONDecodable>(request: URLRequest, parse: (JSON) -> T?, completion: (APIResult<T>) -> Void)
}

extension APIClient {
    func JSONTaskWithRequest(request: URLRequest, completion: JSONTaskCompletion) -> JSONTask {

        let task = session.dataTask(with: request) { data, response, error in

            guard let HTTPResponse = response as? HTTPURLResponse else {
                let userInfo = [
                    NSLocalizedDescriptionKey: NSLocalizedString("Missing HTTP Response", comment: "")
                ]

                let error = NSError(domain: TRENetworkingErrorDomain, code: MissingHTTPResponseError, userInfo: userInfo)
                completion(nil, nil, error)
                return
            }

            if data == nil {
                if let error = error {
                    completion(nil, HTTPResponse, error)
                }
            } else {
                switch HTTPResponse.statusCode {
                case 200:
                    do {
                        let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [String : AnyObject]
                        completion(json, HTTPResponse, nil)
                    } catch let error as NSError {
                        completion(nil, HTTPResponse, error)
                    }
                default: print("Received HTTP Response: \(HTTPResponse.statusCode) - not handled")
                }
            }
        }

        return task
    }

    func fetch<T>(request: NSURLRequest, parse: @escaping (JSON) -> T?, completion: @escaping (APIResult<T>) -> Void) {

        let task = JSONTaskWithRequest(request: request as URLRequest) { json, response, error in

        DispatchQueue.main.async {
            guard let json = json else {
                    if let error = error {
                        completion(.Failure(error))
                    } else {
                        // TODO: Implement Error Handling
                    }
                    return
                }

                if let value = parse(json) {
                    completion(.Success(value))
                } else {
                    let error = NSError(domain: TRENetworkingErrorDomain, code: UnexpectedResponseError, userInfo: nil)
                    completion(.Failure(error))
                }
            }
        }

        task.resume()
    }
}

For ForecastAPI:

final class ForecastAPIClient: APIClient {

    let configuration: URLSessionConfiguration
    lazy var session: URLSession = {
        return URLSession(configuration: self.configuration)
    }()

    private let token: String

    init(config: URLSessionConfiguration, APIKey: String) {
        self.configuration = config
        self.token = APIKey
    }

    convenience init(APIKey: String) {
        self.init(config: URLSessionConfiguration.default, APIKey: APIKey)
    }

    func fetchCurrentWeather(coordinate: Coordinate, completion: @escaping (APIResult<CurrentWeather>) -> Void) {
        let request = Forecast.Current(token: self.token, coordinate: coordinate).request

        fetch(request: request as NSURLRequest, parse: { json -> CurrentWeather? in
            // Parse from JSON response to CurrentWeather

            if let currentWeatherDictionary = json["currently"] as? [String : AnyObject] {
                return CurrentWeather(JSON: currentWeatherDictionary)
            } else {
                return nil
            }

            }, completion: completion)
    }
}
Kevin Gutowski
Kevin Gutowski
4,082 Points

Seems like I'm not conforming to fetch properly:

Xcode insists that I have something like

func fetch<T : JSONDecodable>(request: URLRequest, parse: (JSON) -> T?, completion: (APIResult<T>) -> Void) {

    }

Which I thought we satisfied through this

    func fetchCurrentWeather(coordinate: Coordinate, completion: @escaping (APIResult<CurrentWeather>) -> Void) {
        let request = Forecast.Current(token: self.token, coordinate: coordinate).request

        fetch(request: request as NSURLRequest, parse: { json -> CurrentWeather? in
            // Parse from JSON response to CurrentWeather

            if let currentWeatherDictionary = json["currently"] as? [String : AnyObject] {
                return CurrentWeather(JSON: currentWeatherDictionary)
            } else {
                return nil
            }

            }, completion: completion)
    }

Perhaps someone could explain how we are conforming to fetch with the function fetchCurrentWeather?

9 Answers

Ben Shockley
Ben Shockley
6,094 Points

Okay, so I was going crazy trying to figure this out as well, and I finally figured out that the closures that are marked as

@escaping

in the extension, they need to marked the same way in the protocol declaration.

Hope this helps!

Kevin Gutowski
Kevin Gutowski
4,082 Points

Haha of course! That makes sense. Thanks a lot mate!

dexter foo
dexter foo
8,233 Points

Hi, i think the problem is that you declared request as an URLRequest instead of NSURLRequest in the fetch method under the APIClient protocol and also in the extension fetch method. It should work once you've fixed it i hope! :)

Kevin Gutowski
Kevin Gutowski
4,082 Points

Oh whats the difference between URLRequest and NSURLRequest? (I guess for that matter whats the difference between all the NS-somethings and the non NS-somthings?)

dexter foo
dexter foo
8,233 Points

yep! i mean it is a new language and the first few versions are bound to have some issues that will get deprecated and resolved as more programmers starts using it and giving feedback. all in all, i think apple really did and is doing a great job with this new language

dexter foo
dexter foo
8,233 Points

Hi, i'm still pretty new to iOS dev so i'm not very sure about URLRequest. Tho i did some googling and i don't think its a class. Basically, to my knowledge, NS classes are part of the foundation framework in that they are objective c classes that are bridged over to swift. NS stands for NeXTSTEP. hope this helps! :)

Kevin Gutowski
Kevin Gutowski
4,082 Points

Ah I see! Yeah, I think that is explained in some of the earlier series. I'll try switching over to NSURL etc and see if that changes things. Lots of NS-things got depreciated with swift 3.0 so its kinda confusing what the difference between them are.

dexter foo
dexter foo
8,233 Points

Oh yeah i'm still using Swift 2 cos the changes that was applied to Swift 3 might greatly affect the outcome of the code that we're taught to write for this course. :)

Kevin Gutowski
Kevin Gutowski
4,082 Points

Yeah definitely! I considered doing that but found that Xcode is pretty good about alerting you of what the changes are (particularly with the beginner videos). As the videos get a bit more complex I'm finding some changes that are much different. Its actually pretty cool to see how the codebase has evolved.

Keenan Turner
Keenan Turner
6,270 Points

I'm still getting the error, and what the compiler is telling me is that the ForecastAPIClient is not conforming to the APIClient because I do not have an implementation of this method:

func JSONTaskWithRequest(request: URLRequest, completion: JSONTaskCompletion) -> JSONTask

Do I just declare it? I thought this was taken care of in the fetch method.

Help! Pasan Premaratne

Ben Shockley
Ben Shockley
6,094 Points

No, it should be taken care of there, in the extension. That's the same thing I was getting when I was trying to figure it out, so what's happening is the extension isn't satisfying the requirements of the protocol for one reason or another. So you should go through the implementation of the functions in the extension definition, and compare it to the declaration in the protocol and make sure it matches exactly.

Do you have a github of your code that I can look at, or can you paste i here?

-- Ben

Ben Shockley
Ben Shockley
6,094 Points

Also, if try to run you app and get the build failed, go to the Report Navigator (under View>Navigators) it might give you a little more detail of whats going on with the compiler when it sees the error.

Keenan Turner
Keenan Turner
6,270 Points

Ben Shockley Right, I know that this function is supposed to be taken care of, but I guess I'm not able to see the issue. Below is my APIClient (Swift 3):

import Foundation

public let TRENetworkingErrorDomain = "com.treehouse.Stormy.NetworkingError"
public let MissingHTTPResponseError: Int = 10
public let UnexpectedResponseError: Int = 20

typealias JSON = [String : AnyObject]
typealias JSONTaskCompletion = (JSON?, HTTPURLResponse?, NSError?) -> Void
typealias JSONTask = URLSessionDataTask

enum APIResult<T> {
    case Success(T)
    case Failure(Error)
}

protocol JSONDecodable {
    init?(JSON: [String : AnyObject])
}

protocol Endpoint {
    var baseURL: URL { get }
    var path: String { get }
    var request: URLRequest { get }
}

protocol APIClient {
    var configuration: URLSessionConfiguration { get }
    var session: URLSession { get }

    init(config: URLSessionConfiguration, APIKey: String)

    func JSONTaskWithRequest(request: URLRequest, completion: JSONTaskCompletion) -> JSONTask

    func fetch<T: JSONDecodable>(request: URLRequest, parse: (JSON) -> T?, completion: (APIResult<T>) -> Void)
}

extension APIClient {
    func JSONTaskWithRequest(request: URLRequest, completion: @escaping JSONTaskCompletion) -> JSONTask {

        let task = session.dataTask(with: request) {data, response, error in

            guard let HTTPResponse = response as? HTTPURLResponse else {
                let userInfo = [
                    NSLocalizedDescriptionKey: NSLocalizedString("Missing HTTP Response", comment: "")]

                let error = NSError(domain: TRENetworkingErrorDomain, code: MissingHTTPResponseError, userInfo: userInfo)
                completion(nil, nil, error)
                return
        }

        if data == nil {
            if let error = error as NSError? {
                completion(nil, HTTPResponse, error)
            }
        } else {
            switch HTTPResponse.statusCode {
            case 200:
                do {
                    let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [String: AnyObject]
                    completion(json, HTTPResponse, nil)
                } catch let error as NSError {
                    completion(nil, HTTPResponse, error)
                }
            default: print("Recieved HTTP Response: \(HTTPResponse.statusCode) - not handled")
            }
        }
    }
        return task
    }

    func fetch<T>(request: URLRequest, parse: (JSON) -> T?, completion: (APIResult<T>) -> Void) {

        let task = JSONTaskWithRequest(request: request) {json, response, error in


            guard json != nil else {
                if let error = error {
                    completion(.Failure(error))
                } else {
                    // TODO: Implement Error Handling
                }
                return
            }

            if let value = parse(json!) {
                completion(.Success(value))
            } else {
                let error = NSError(domain: TRENetworkingErrorDomain, code: UnexpectedResponseError, userInfo: nil)
                completion(.Failure(error))
            }

        }
        task.resume()
    }
}

And below is my ForecastClien that is showing the conforming error. When I run the app the compiler wants me to insert the JSONTaskWithRequest func, but I knew it was taken care of, so as you stated there must be an issue with its implementation:

import Foundation

struct Coordinate {
    let latitude: Double
    let longitude: Double
}

enum Forecast: Endpoint {
    case Current(token: String, coordinate: Coordinate)

    var baseURL: URL {
        return URL(string: "https://api.darksky.net")!
    }

    var path: String {
        switch self {
        case .Current(let token, let coordinate):
            return "/forcast/\(token)/\(coordinate.latitude),\(coordinate.longitude)"
        }
    }

    var request: URLRequest {
        let url = URL(string: path, relativeTo: baseURL)!
        return URLRequest(url: url)
    }
}

final class ForecastAPIClient: APIClient {

    var configuration: URLSessionConfiguration
    lazy var session: URLSession = {
        return URLSession(configuration: self.configuration)
    }()

    private let token: String

    init(config: URLSessionConfiguration, APIKey: String) {
        self.configuration = config
        self.token = APIKey
    }

    convenience init(APIKey: String) {
        self.init(config: URLSessionConfiguration.default, APIKey: APIKey)
    }

    func fetchCurrentWeather(coordinate: Coordinate, completion: @escaping (APIResult<CurrentWeather>) -> Void) {
        let request = Forecast.Current(token: self.token, coordinate: coordinate).request

        fetch(request: request, parse: { (json) -> CurrentWeather? in
            // Parse from JSON response to CurrentWeather

            if let currentWeatherDictionary = json["currently"] as? [String : AnyObject] {
                return CurrentWeather(JSON: currentWeatherDictionary)
            } else {
                return nil
            }

            }, completion: completion)
    }
}
Ben Shockley
Ben Shockley
6,094 Points

In the extension definition, where you implement

 func JSONTaskWithRequest(request: URLRequest, completion: @escaping JSONTaskCompletion) -> JSONTask {

You have @escaping labeled before the completion handler. You need to add that to the Protocol declaration as well, and that should fix it. Xcode 8 with Swift 3 warns when you have @escaping closures when your writing the implementation, but in the declaration of the function in the Protocol, since there's no implementation there, it doesn't know that you could have @excaping closures, so it doesn't know to warn you of it there. Does that make sense?

Keenan Turner
Keenan Turner
6,270 Points

Ben Shockley This is starting to make sense now. And I checked out on stackoverflow - http://stackoverflow.com/questions/39063499/updating-closures-to-swift-3-escaping

Explaining that closures are now defaulted as noescaping and we have to specify when it's to be escaping. I'm still a bit lost though on where I should implement it in the declaration.

Ben Shockley
Ben Shockley
6,094 Points

It just goes in the same place that you have it in the extension. So the header of the function should look exactly the same in both the protocol, and the extension.

Alistair Cooper
PLUS
Alistair Cooper
Courses Plus Student 7,020 Points

I'm running into a similar issue. Have tried adding @escaping to protocol declaration and extension but I'm still getting the error that ForecastAPIClient doesn't conform to APIClient. Any help much appreciated!! I'm in Swift 3.01

protocol APIClient {
    var configuration: URLSessionConfiguration { get }
    var session: URLSession { get }

    init(config: URLSessionConfiguration)

    func JSONTaskWithRequest(request: URLRequest, completion: @escaping JSONTaskCompletion) -> JSONTask

    func fetch<T: JSONDecodable>(request: URLRequest, parse: @escaping (JSON) -> T?, completion: @escaping (APIResult<T>) -> Void)
}

extension APIClient {

func JSONTaskWithRequest(request: URLRequest, completion: @escaping JSONTaskCompletion) -> JSONTask {

    let task = session.dataTask(with: request) { (data, response, error) in

        guard let HTTPResponse = response as? HTTPURLResponse else {
            let userInfo = [
                NSLocalizedDescriptionKey: NSLocalizedString("Missing HTTP Response", comment: "")
            ]

            let error = NSError(domain: ASCNetworkingErrorDomain, code: MissingHTTPResponseError, userInfo: userInfo)
            completion(nil, nil, error)
            return
        }

        if data == nil {
            if let error = error {
                completion(nil, HTTPResponse, error)
            }
        } else {
            switch HTTPResponse.statusCode {
            case 200:
                do {
                    let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [String : Any]
                    completion(json, HTTPResponse, nil)
                } catch let error as NSError {
                    completion(nil, HTTPResponse, error)
                }
            default: print("Received HTTP Response Code \(HTTPResponse.statusCode) - not handled")
            }

        }
    }

    return task
}

func fetch<T>(request: URLRequest, parse: @escaping (JSON) -> T?, completion: @escaping (APIResult<T>) -> Void) {

    let task = JSONTaskWithRequest(request: request) { json, response, error in

        guard let json = json else {
            if let error = error {
                completion(.failure(error))
            } else {
                // TODO: Implement error handling
            }
            return
        }

        if let value = parse(json) {
            completion(.success(value))
        } else {
            let error = NSError(domain: ASCNetworkingErrorDomain, code: UnexpectedResponseError, userInfo: nil)
            completion(.failure(error))
        }
    }

    task.resume()
}
}

and the forecast client:

final class ForecastAPIClient: APIClient {

let configuration: URLSessionConfiguration
lazy var session: URLSession = {
    return URLSession(configuration: self.configuration)
}()

private let token: String

init(config: URLSessionConfiguration, APIKey: String) {
    self.configuration = config
    self.token = APIKey
}

convenience init(APIKey: String) {
    self.init(config: URLSessionConfiguration.default, APIKey: APIKey)
}

func fetchCurrentWeather(coordinate: Coordinate, completion: @escaping (APIResult<CurrentWeather>) -> Void) {

    let request = Forecast.current(token: self.token, coordinate: coordinate).request

    fetch(request: request, parse: { json -> CurrentWeather? in
        // Parse from JSON response to CurrentWeather

        if let currentWeatherDict = json["currently"] as? [String : Any] {

            return CurrentWeather(JSON: currentWeatherDict)
        } else {
            return nil
        }

    }, completion: completion)

}
}
Alistair Cooper
Alistair Cooper
Courses Plus Student 7,020 Points

FIXED: Thanks to the other thread for this problem. Thanks to Ginny Pennekamp for discovering that removing:

init(config: URLSessionConfiguration)

from the APIClient protocol solved the issue!

Crazy coincidence I actually met Ginny two days ago at a Swift meetup haha!