Ошибка немного вводит в заблуждение и может указывать на то, что вы используете старую версию 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 .