Swift: передать тип из свойства в общую функцию

Для моего сетевого модуля у меня есть этот протокол, который я использую для доступа к различным частям API:

protocol Router: URLRequestConvertible {
    var baseUrl: URL { get }        
    var route: Route { get }        
    var method: HTTPMethod { get }        
    var headers: [String: String]? { get }        
    var encoding: ParameterEncoding? { get }        
    var responseResultType: Decodable.Type? { get }
}

Я использую это с перечислениями, которые выглядят так:

enum TestRouter: Router {
    case getTestData(byId: Int)
    case updateTestData(byId: Int)

    var route: Route {
        switch self {
        case .getTestData(let id): return Route(path: "/testData/\(id)")
        case .updateTestData(let id): return Route(path: "/testDataOtherPath/\(id)")
        }
    }

    var method: HTTPMethod {
        switch self {
        case .getTestData: return .get
        case .updateTestData: return .put
        }
    }

    var headers: [String : String]? {
        return [:]
    }

    var encoding: ParameterEncoding? {
        return URLEncoding.default
    }

    var responseResultType: Decodable.Type? {
        switch self {
        case .getTestData: return TestData.self
        case .updateTestData: return ValidationResponse.self
        }
    }
}

Я хочу использовать Codable для декодирования вложенных ответов API. Каждый ответ состоит из токена и результата, содержимое которого зависит от маршрута запроса.

Для выполнения запроса я хочу использовать тип, указанный в свойстве responseResultType в enum выше.

struct ApiResponse<Result: Decodable>: Decodable {
    let token: String
    let result: Result
}

extension Router {
    func asURLRequest() throws -> URLRequest {
        // Construct URL
        var completeUrl = baseUrl.appendingPathComponent(route.path, isDirectory: false)
        completeUrl = URL(string: completeUrl.absoluteString.removingPercentEncoding ?? "")!
        // Create URL Request...
        var urlRequest = URLRequest(url: completeUrl)
        // ... with Method
        urlRequest.httpMethod = method.rawValue
        // Add headers
        headers?.forEach { urlRequest.addValue($0.value, forHTTPHeaderField: $0.key) }
        // Encode URL Request with the parameters
        if encoding != nil {
            return try encoding!.encode(urlRequest, with: route.parameters)
        } else {
            return urlRequest
        }
    }

    func requestAndDecode(completion: @escaping (Result?) -> Void) {
        NetworkAdapter.sessionManager.request(urlRequest).validate().responseData { response in
            let responseObject = try? JSONDecoder().decode(ApiResponse<self.responseResultType!>, from: response.data!)
            completion(responseObject.result)
        }
    }
}

Но в моем методе requestAndDecode выдает ошибку компилятора (Cannot invoke 'decode' with an argument list of type '(Any.Type, from: Data)'). Я не могу так использовать ApiResponse<self.responseResultType!>.

Я мог бы сделать эту функцию универсальной и назвать ее так:

TestRouter.getTestData(byId: 123).requestAndDecode(TestData.self, completion:)

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

Чего я хочу добиться, так это того, чтобы функция расширения requestAndDecode брала информацию о типе ответа сама от себя, свойство responseResultType.

Это возможно?


person heyfrank    schedule 05.07.2018    source источник


Ответы (2)


Игнорируя фактическое сообщение об ошибке, у вас есть фундаментальная проблема с requestAndDecode: это общая функция, параметры типа которой определяются на сайте вызова, которая объявлена ​​​​для возврата значения типа Result, но пытается вернуть значение типа self.responseResultType, значение которого неизвестный тип.

Если бы система типов Swift поддерживала это, потребовалась бы проверка типов во время выполнения, возможная сбой, и ваш код должен был бы справиться с этим. Например. вы можете передать TestData requestAndDecode, а responseResultType может быть ValidationResponse...

Измените вызов JSON на:

JSONDecoder().decode(ApiResponse<Result>.self ...

и типы статически совпадают (хотя фактический тип Result неизвестен).

Вам нужно переосмыслить свой дизайн. ХТН

person CRD    schedule 05.07.2018
comment
Вы правы, я перепутал две версии функции (первая с дженериками, а другая без). Я обновил код. - person heyfrank; 06.07.2018
comment
Спасибо за Ваш ответ! Я понял, почему это невозможно. Этот вопрос аналогичен и также имеет в нем хорошие объяснения (stackoverflow.com/questions/30905060/) - person heyfrank; 06.07.2018

Создайте общую функцию с помощью Combine и AlomFire. Вы можете использовать его для всех методов (получить, опубликовать, поставить, удалить)

 func fatchData<T: Codable>(requestType: String, url: String, params: [String : Any]?, myType: T.Type, completion: @escaping (Result<T, Error>) -> Void) {

    var method = HTTPMethod.get
    switch requestType {
    case "Get":
        method = HTTPMethod.get
    case "Post":
        method = HTTPMethod.post
        print("requestType  \(requestType) \(method) ")
    case "Put":
        method = HTTPMethod.put
    default:
        method = HTTPMethod.delete
    }

    print("url  \(url) \(method)  \(AppConstant.headers)  ")
    task =  AF.request(url, method: method, parameters: params, encoding: JSONEncoding.default, headers: AppConstant.headers)
        .publishDecodable(type: myType.self)
        .sink(receiveCompletion: { (completion) in
            switch completion{
            case .finished:
                ()
            case .failure(let error):
               // completion(.failure(error))
                print("error  \(error)")
            }
        }, receiveValue: {
            [weak self ](response) in
            
            print("response \(response)")

            switch response.result{
            
            case .success(let model):
                completion(.success(model))
                print("error success")

            case .failure(let error):
                completion(.failure(error))

                print("error failure \(error.localizedDescription)")

            }
        }
    )
}
person Tariqul    schedule 20.03.2021