Generi c параметр 'T' не может быть выведен: в методе Common Webservice - PullRequest
1 голос
/ 07 мая 2020

Я пытаюсь создать общий метод c post для вызова API. В моем методе loadNew я хочу добавить обычный словарь внутри объекта ресурса. Ресурс содержит обычные данные, которые будут передаваться из класса контроллера. И словарь передается как тело запрос. но при кодировании « Generi c параметр 'T' не может быть выведен» отображается. Как мне использовать в нем словарь?

    struct Resource<T> {
            let url: URL
            let request: URLRequest
            let dictionary : [String:Any]
            let parse: (Data) -> T?

    }

   final class Webservice {

            //    MARK:- Generic

            func load<T>(resource: Resource<T>, completion: @escaping (T?) -> ()) {

                URLSession.shared.dataTask(with: resource.url) { data, response, error in

                    if let data = data {
                        //completion call should happen in main thread
                        DispatchQueue.main.async {
                            completion(resource.parse(data))
                        }
                    } else {
                        completion(nil)
                    }

                    }.resume()

            }


            func loadNew<T>(resource: Resource<T>, completion: @escaping (T?) -> ()) {

                var request = resource.request
                request.addValue("application/json", forHTTPHeaderField: "Content-Type")

                do {
                    //FIXIT: error is getting here
                    let jsonBody = try JSONEncoder().encode(resource.dictionary)
                    request.httpBody = jsonBody
                }catch{}

                let session = URLSession.shared

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

                    if let data = data {
                        //completion call should happen in main thread
                        DispatchQueue.main.async {
                            completion(resource.parse(data))
                        }
                    } else {
                        completion(nil)
                    }

                    }.resume()

            }

        }

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

        func APICall(){

            guard let url = URL(string: Constants.HostName.local + Constants.API.User_Login) else {
                return
            }

            var request = URLRequest(url: url)
            request.httpMethod = "POST"


            let resources = Resource<LoginReponse>(url: url, request: request, dictionary: dict){
                data in
                let loginModel = try? JSONDecoder().decode(LoginReponse.self, from: data)

                return loginModel

            }
            //        var response = LoginReponse()

            Webservice().loadNew(resource: resources) {
                result in

                if let model = result {

                    print(model)
                }

            }
   }

1 Ответ

2 голосов
/ 07 мая 2020

Ошибка немного вводит в заблуждение и может указывать на то, что вы используете старую версию Xcode. В 11.4.1 ошибка более явная:

ошибка: значение типа протокола «Любой» не может соответствовать «Кодируемый»; только типы struct / enum / class могут соответствовать протоколам

Проблема в том, что [String: Any] не кодируется, потому что нет способа закодировать «Any» (что должно произойти, если вы передали UIViewController здесь ? Или CBPeripheral?)

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

struct Resource<Value: Decodable, Parameters: Encodable> {
    let url: URL
    let request: URLRequest
    let parameters : Parameters?
    let parse: (Data) -> Value?
}

final class Webservice {

         func loadNew<Value, Parameters>(resource: Resource<Value, Parameters>, completion: @escaping (Value?) -> ()) {

             var request = resource.request
             request.addValue("application/json", forHTTPHeaderField: "Content-Type")

            if let parameters = resource.parameters {
                request.httpBody = try? JSONEncoder().encode(parameters)
            }

            // ...

     }

Тем не менее, я, вероятно, немного переверну эту систему. Если вы хотите иметь Request<T> (параметризованное для того, что он возвращает, а не для параметров, необходимых для его генерации), это нормально. Вы можете упаковать немного больше в структуру. Например:

let baseURL = URL(string: "https://example.com/api/")!

struct Resource<Value> {
    let urlRequest: URLRequest
    let parse: (Data) -> Result<Value, Error>

    // Things you want as default for every request
    static func makeStandardURLRequest(url: URL) -> URLRequest {
        var request = URLRequest(url: url)
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        return request
    }
}

// It would be nice to have a default parser when you can, but you don't have to put that
// into Webservice. The Resource can handle it.
extension Resource where Value: Decodable {
    init(urlRequest: URLRequest) {
        self.init(urlRequest: urlRequest, parse: { data in
            Result { try JSONDecoder().decode(Value.self, from: data) }
        })
    }
}

И тогда Ресурсы умны сами по себе:

struct LoginParameters: Encodable {
    let username: String
    let password: String
}

struct LoginResult: Decodable {
    let authToken: String
}

extension Resource where Value == LoginResult {
    static func login(parameters: LoginParameters) -> Resource {
        var urlRequest = makeStandardURLRequest(url: baseURL.appendingPathComponent("login"))
        urlRequest.httpBody = try? JSONEncoder().encode(parameters)
        return Resource(urlRequest: urlRequest)
    }
}

Конечно, это может повторяться много раз, поэтому вы можете поднять его:

extension Resource where Value: Decodable {
    static func makeStandardURLRequest<Parameters>(endpoint: String, parameters: Parameters) -> URLRequest
        where Parameters: Encodable {
            var urlRequest = makeStandardURLRequest(url: baseURL.appendingPathComponent(endpoint))
            urlRequest.httpBody = try? JSONEncoder().encode(parameters)
            return Resource(urlRequest: urlRequest)
    }
}

И тогда Логин выглядит так:

extension Resource where Value == LoginResult {
    static func login(parameters: LoginParameters) -> Resource {
        return makeStandardURLRequest(endpoint: "login", parameters: parameters)
    }
}

Дело в том, что вы можете перенести дублированный код в расширения; вам не нужно вставлять его в Webservice или добавлять дополнительные c.

Благодаря этому ваш load становится немного проще и намного гибче. Он фокусируется только на сетевой части. Это означает, что легче заменить что-то другое (например, что-то для модульных тестов) без необходимости имитировать кучу функций.

func load<Value>(request: Resource<Value>, completion: @escaping (Result<Value, Error>) -> ()) {
    let session = URLSession.shared
    session.dataTask(with: request.urlRequest) { data, response, error in

        DispatchQueue.main.async {
            if let data = data {
                //completion call should happen in main thread
                completion(request.parse(data))
            } else if let error = error {
                completion(.failure(error))
            } else {
                fatalError("This really should be impossible, but you can construct an 'unexpected error' here.")
            }
        }
    }.resume()
}

Есть много способов сделать это; для другого см. это AltConf talk .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...