使用通用参数调用函数需要类型的无参数初始化

Calling a function with a Generic parameter requires a no-arg init for the type

我无法通过 URLSession DataTaskPublisher...

使用泛型类型调用函数

我调用的 API 总是以 HTTP 200 响应,并通过带有指示符的 code 字符串在 JSON 中指示它是否成功。

我创建了一个所有响应对象都符合的 protocol,例如:

protocol APIResponse: Decodable {
    var code: String { get }
}

我的实际回复是这样的:

struct LoginResponse :  APIResponse {
    let code: String
    let name: String
    enum CodingKeys: String, CodingKey {
        case code = "code"
        case name = "name"
    }
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        code = try values.decode(String.self, forKey: .code)
        name = try values.decode(String.self, forKey: .name)
    }
}

现在我想要一个可以使用的函数:

    private func call<T: APIResponse>(_ endpoint: String, using data: Encodable, providing response: T)
                    -> AnyPublisher<T, Error> {
        let request = createRequest(for: endpoint, using: data)
        return session
                .dataTaskPublisher(for: request)
                .map {
                    [=12=].data
                }
                .decode(type: T.self, decoder: decoder)
                .tryMap {
                    response in
                    if response.code.suffix(1) != "I" {
                        throw MyError(message: "Error \(code)")
                    }
                    return response
                }
                .eraseToAnyPublisher()
    }

到目前为止,一切顺利!

但是问题来了……我想这样使用它:

    func login() -> AnyPublisher<String, Error> {
        call("login", using: LoginRequest(), providing: LoginResponse)
                .map {
                    [=13=].name
                }
                .eraseToAnyPublisher()
    }

编译器抱怨 Type 'LoginResponse.Type' cannot conform to 'APIResponse'; only struct/enum/class types can conform to protocols

我的修复(有效,但很笨拙),它为 LoginResponse 提供无参数 init() 并这样称呼它:call("login", using: LoginRequest(), providing: LoginResponse())

有什么方法可以让泛型在没有无参数的情况下工作init()

我应该采取完全不同的方法吗?

call 的 header 中,将 response 参数更改为

...providing response: T.Type)

并使用 .self

调用它
call("login", using: "request", providing: LoginResponse.self)

您真的不需要将响应类型传递给此函数:

private func call<T: APIResponse>(_ endpoint: String,
                                  using data: Encodable) -> AnyPublisher<T, Error> {
        // no change here
}

但是您可能必须在此处通过指定 map 中使用的闭包类型来帮助编译器,因为它无法猜测从该通用函数返回哪种类型:

func login() -> AnyPublisher<String, Error> {
    call("login", using: LoginRequest())
            .map { (response: LoginResponse) -> String in
                response.name
            }
            .eraseToAnyPublisher()
}

您还可以简化您的 LoginResponse:

struct LoginResponse: APIResponse {
    let code: String
    let name: String
}

作为替代方案,在我看来,最干净的方法是创建一个知道自己响应的 Endpoint 协议,以便从 call 函数中删除这种冗余。

看起来像这样:

protocol Endpoint {
   associatedtype Response: Decodable
   var request: URLRequest { get }
}

func call<E: Endpoint>(endpoint: E) -> AnyPublisher<E.Response, Error> {
   //...
}

像这样定义端点:

struct LoginResponse: Decodable {
  // ...
}

struct LoginEndpoint: Endpoint {
   typealias Response = LoginResponse
   let request: URLRequest
}

并使用它:

let login = LoginEndpoint(request: URLRequest(...))
call(endpoint: login)
   .sink ...