Swift:将类型从属性传递给泛型函数

时间:2018-07-05 09:01:06

标签: swift generics swift4 swift-protocols codable

对于我的网络模块,我具有用于访问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的{​​{1}}属性中指定的类型。

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属性中获取响应类型信息。

这可能吗?

2 个答案:

答案 0 :(得分:2)

忽略实际的错误报告,您在requestAndDecode上存在一个基本问题:这是一个泛型函数,其类型参数在调用站点处确定,并声明为返回Result类型的值尝试返回类型为self.responseResultType的值,其值为未知类型。

如果Swift的类型系统支持此功能,则将需要运行时类型检查,潜在的失败,并且您的代码必须处理该问题。例如。您可以将TestData传递给requestAndDecode,而responseResultType可能是ValidationResponse ...

将JSON调用更改为:

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

并且类型静态匹配(即使Result的实际类型未知)。

您需要重新考虑您的设计。 HTH

答案 1 :(得分:0)

使用Combine 和AlomFire 创建通用函数。您可以将它用于所有方法(get、post、put、delete)

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

            }
        }
    )
}