首页 > 解决方案 > 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属性中获取响应类型信息。

这可能吗?

标签: swiftgenericsswift4swift-protocolscodable

解决方案


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

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

将 JSON 调用更改为:

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

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

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


推荐阅读