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 trialKevin Gutowski
4,082 PointsClass 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)
}
}
9 Answers
Ben Shockley
6,094 PointsOkay, 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
4,082 PointsHaha of course! That makes sense. Thanks a lot mate!
dexter foo
8,233 PointsHi, 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
4,082 PointsOh 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
8,233 Pointsyep! 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
8,233 PointsHi, 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
4,082 PointsAh 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
8,233 PointsOh 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
4,082 PointsYeah 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
6,270 PointsI'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
6,094 PointsNo, 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
6,094 PointsAlso, 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
6,270 PointsBen 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
6,094 PointsIn 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
6,270 PointsBen 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
6,094 PointsIt 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
Courses Plus Student 7,020 PointsI'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
Courses Plus Student 7,020 PointsFIXED: 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!
Kevin Gutowski
4,082 PointsKevin Gutowski
4,082 PointsSeems like I'm not conforming to fetch properly:
Xcode insists that I have something like
Which I thought we satisfied through this
Perhaps someone could explain how we are conforming to fetch with the function fetchCurrentWeather?