Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
382 views
in Technique[技术] by (71.8m points)

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

I'm having trouble getting a function call using a generic type to work using a URLSession DataTaskPublisher...

The API I'm calling always responds with an HTTP 200, and indicates whether it was successful or not in the JSON via a code string with an indicator.

I created a protocol that all response objects conform to, e.g.:

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

My actual response will be something like:

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

Now I want a function like this that I can use:

    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 {
                    $0.data
                }
                .decode(type: T.self, decoder: decoder)
                .tryMap {
                    response in
                    if response.code.suffix(1) != "I" {
                        throw MyError(message: "Error (code)")
                    }
                    return response
                }
                .eraseToAnyPublisher()
    }

So far, so good!

But here's the problem... I want to use it like this:

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

The compiler complains with Type 'LoginResponse.Type' cannot conform to 'APIResponse'; only struct/enum/class types can conform to protocols

My fix (which works, but is kludgy), it to provide a no-arg init() for LoginResponse and call it like this: call("login", using: LoginRequest(), providing: LoginResponse())

Any way to get the Generic to work without the no-arg init()?

Should I be taking a totally different approach?

question from:https://stackoverflow.com/questions/65942698/calling-a-function-with-a-generic-parameter-requires-a-no-arg-init-for-the-type

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You don't really need to pass the response type to this function:

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

But you might have to help the compiler here by specifying the type of the closure used in map as it has no way of guessing which type is returned from this generic function:

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

You can also simplify your LoginResponse:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...