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

Posted

技术标签:

【中文标题】Swift:将类型从属性传递给泛型函数【英文标题】:Swift: Pass type from property to generic function 【发布时间】:2018-07-05 09:01:06 【问题描述】:

对于我的网络模块,我采用了这个协议来访问 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 响应。每个响应都包含一个令牌和一个结果,其内容取决于请求路由。

为了发出请求,我想使用上面enumresponseResultType 属性中指定的类型。

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&lt;self.responseResultType!&gt;

我可以使这个函数成为通用函数并像这样调用它:

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

但是每次我想使用这个端点时,我都必须传递响应类型。

我想要实现的是扩展函数requestAndDecode从自己的responseResultType属性中获取响应类型信息。

这可能吗?

【问题讨论】:

【参考方案1】:

忽略实际的错误报告,requestAndDecode 存在一个根本问题:它是一个泛型函数,其类型参数在调用站点确定,它被声明为返回类型为 Result 的值,但它试图返回一个self.responseResultType 类型的值,其值为 unknown 类型。

如果 Swift 的类型系统支持这一点,它将需要运行时类型检查,潜在的失败,而您的代码将不得不处理它。例如。你可以将TestData 传递给requestAndDecoderesponseResultType 可能是ValidationResponse...

将 JSON 调用更改为:

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

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

您需要重新考虑您的设计。高温

【讨论】:

你说得对,我混合了函数的两个版本(第一个带有泛型,另一个没有)。我更新了代码。 感谢您的回答!我明白为什么这是不可能的。这个问题很相似,也有很好的解释(***.com/questions/30905060/…)【参考方案2】:

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

            
        
    )

【讨论】:

以上是关于Swift:将类型从属性传递给泛型函数的主要内容,如果未能解决你的问题,请参考以下文章

为啥类型不能分配给泛型类型

如何将类类型分配给泛型类型<T>?

从头认识java-13.11 对照数组与泛型容器,观察类型擦除给泛型容器带来什么问题?

swift中泛型和 Any 类型

Type 应该采用啥协议来让泛型函数将任何数字类型作为 Swift 中的参数?

涉及泛型对象的泛型属性的赋值无法在泛型函数中正确进行类型检查