Класс не соответствует протоколу с расширением, содержащим реализацию по умолчанию

#swift #xcode #protocols #swift3 #protocol-extension

#swift #xcode #протоколы #swift3 #протокол-расширение

Вопрос:

В настоящее время я работаю над курсом Treehouse IOS Swift, и мы создаем приложение для погоды. Я дошел до того, что постоянно получаю сообщение об ошибке, что мой класс не соответствует моему протоколу, но я не могу понять, почему.

Вот мое объявление протокола:

 public 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)

}
  

И затем у меня есть расширение протокола, в котором у меня есть реализации по умолчанию моих двух методов, объявленных в протоколе, и один из методов вызывает другой метод, как вы можете видеть ниже.

 public 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: BPSnetworkingErrorDomain, code: MissingHTTPReponseError, userInfo: userInfo)
                completion(nil, response as! HTTPURLResponse, error)
                return
            }

            if data == nil  {
                if let error = error {
                    completion(nil, response as! HTTPURLResponse, error as NSError?)
                }

            } 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
    }

    public func fetch<T>(request: URLRequest, parse: @escaping (JSON) -> T?, completion: @escaping (APIResult<T>) -> Void) {
        let task = JSONTaskWithRequest(request: request) { json, response, error in
            DispatchQueue.main.async {
            guard let json = json else {
                if let error = error {
                    completion(.Failure(error))
                } else {
                    let error = "Something is really wrong.  There was no JSON object created, but there was no error either."
                    completion(.Failure(error as! Error))
                }
                return
            }

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

        task.resume()
    }
}
  

Затем у меня есть объявление моего класса, в котором я получаю ошибку несоответствия.

 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

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

    }



}
  

Я много читал в течение нескольких часов, пытаясь понять, что здесь происходит. Насколько я понимаю, мне не нужно определять эти два метода в моем классе, поскольку они имеют реализации по умолчанию в расширении протокола. Я столкнулся с проблемой общедоступных / внутренних типов и тому подобного, что кто-то еще имел здесь, на StackExchange, со своими расширениями (как вы видите, по моему обозначению общедоступных, а что нет), но, похоже, это не помогло в моем случае. Единственный способ, которым я смог устранить ошибку, — это прокомментировать эти объявления методов в исходном объявлении протокола. Мне кажется, это указывает на то, что либо класс, либо протокол, либо что-то еще по какой-то причине не видит расширение, однако, если я нажимаю на fetch вызов метода в объявлении класса, это приводит меня к его определению в расширении. Я не смог найти решение или даже кого-то, кто делает это подобное, в Treehouse есть несколько человек, у которых, похоже, тоже такая проблема.

Кроме того, я скачал код teachers и преобразовал его в Swift 3, и он также получал ту же ошибку, так что, может быть, это проблемы с наличием другой версии Xcode, которую он использовал при создании видео?

Я чувствую, что немного хватаюсь за соломинку, но мне действительно не терпится разобраться в этом, поэтому любая возможная помощь будет очень признательна.

Спасибо!

Комментарии:

1. «Навигатор отчетов» в Xcode содержит полный журнал компилятора, и он должен содержать более подробную информацию о том, почему компилятор считает, что ваш класс не соответствует протоколу.

2. Спасибо, это помогло мне понять, что происходит. По-видимому, если вы пометите замыкание как @escaping в расширении, оно также должно быть помечено таким образом в объявлении протокола, имеет смысл подумать об этом, но я все еще новичок в этой концепции.

Ответ №1:

Я использовал игровую площадку Xcode для тестирования и игры с вашим кодом. Я взял ваш код (объявление протокола, расширение протокола и объявление класса) и сильно упростил функции JSONTaskWithRequest() and fetch() . Код скомпилирован без «ошибки несоответствия». Вот код, который я использовал:

 //: Playground :    
import UIKit

// protocol declaration
public protocol APIClient {

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

    func JSONTaskWithRequest()
    func fetch()
}

// protocol extension
public extension APIClient {
    func JSONTaskWithRequest() {
        print("JSONTaskWithRequest here")
    }
    func fetch() {
        print("fetch here")
    }
}

// class declaration
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
    }
}
  

Я подозреваю, что в JSONTaskWithRequest и / или fetch есть ошибка. Я предлагаю вам изолировать любую функцию, чтобы выяснить, какая из них выдает ошибку. Затем отлаживайте оттуда.

Кроме того, просто еще одно подозрение. В реализации функции JSONTaskWithRequest расширения у вас есть:

 let task = session.dataTask(with: request) {...}
return task
  

JSONTaskWithRequest требуется для возврата JSONTask. Возможно, вам нужно понизить task :

 return task as! JSONTask
  

Я не мог использовать предоставленный вами код, потому что такие вещи, как JSONTaskCompletion и JSONDecodable, не распознаются Swift. Используете ли вы стороннюю библиотеку JSON swift?

Комментарии:

1. Хорошо, я немного поиграю с этим и посмотрю, смогу ли я что-нибудь там найти. Есть некоторый код, который я пропустил, чтобы вопрос не был длинным. JSONTaskCompletion , JSONTask и JSON — это все типы (тип JSONTask на самом деле является URLSessionDataTask , поэтому возвращаемая задача такая же, как и возвращаемая JSONTask). JSONDecodable — это другой протокол, определяющий дополнительный словарь для данных JSON. Спасибо за ответ!

2. Хорошо! Я понял это! Так что, очевидно, если вы отметите закрытие как @escaping в расширении, оно также должно быть помечено таким образом в объявлении протокола. Не могу поверить, что что-то настолько тривиальное заставило меня потратить на это так много времени, ха. Я думаю, это имеет смысл, но я все еще новичок в экранировании / неэкранировании.