使用通用参数调用函数需要类型的无参数初始化
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 ...
我无法通过 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 ...