【问题标题】:How can I return a generic type that implements another protocol如何返回实现另一个协议的泛型类型
【发布时间】:2020-07-02 19:12:14
【问题描述】:

我有一个网络模块,它实现了一个标准接口来公开我的网络客户端

protocol HTTPClientTask {
    func cancel()
}
protocol HTTPClient {
    @discardableResult
    func execute(_ request: URLRequest, _ completion: @escaping (Result<(response: HTTPURLResponse, data: Data), Error>) -> Void) -> HTTPClientTask
}

这是实现的类似

final class URLSessionHTTPClient: HTTPClient {
    
    private let session: URLSession
    private struct RequestError: Error { }
    private struct URLSessionTaskWrapper: HTTPClientTask {
        let wrapped: URLSessionTask
        func cancel() {
            wrapped.cancel()
        }
    }
    
    init(session: URLSession = .shared) {
        self.session = session
    }
    
    func execute(_ request: URLRequest, _ completion: @escaping (Result<(response: HTTPURLResponse, data: Data), Error>) -> Void) -> HTTPClientTask {
        let task = session.dataTask(with: request) { data, response, error in
            completion(Result {
                if let error = error {
                    throw error
                } else if let data = data, let response = response as? HTTPURLResponse {
                    return (response, data)
                } else {
                    throw RequestError()
                }
            })
        }
        task.resume()
        return URLSessionTaskWrapper(wrapped: task)
    }
}

一个在操场上跑步的例子是

let requestURL = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!

let httpClient = URLSessionHTTPClient()

httpClient.execute(.init(url: requestURL)) { result in
    if let code = try? result.get().response.statusCode {
        print(code)
    }
}

我想将另一个框架添加到我的应用程序中,该框架公开了一个相同的界面。

我不希望我的其他模块依赖于这个网络模块,而是希望它公开它需要的接口并让我的网络模块依赖它。

所以下面的接口被我的其他模块暴露了

protocol AuthHTTPClientTask {
    func cancel()
}

protocol AuthHTTPClient {
    @discardableResult
    func execute(_ request: URLRequest, _ completion: @escaping (Result<(response: HTTPURLResponse, data: Data), Error>) -> Void) -> AuthHTTPClientTask
}

如您所见,接口是精确的,但我希望避免仅为该接口创建整个 URLSessionHTTPClient - URLSessionAuthHTTPClient 或行为等相同的东西。

是否可以创建URLSessionHTTPClient 实现的某种类型,使其返回AuthHTTPClientTaskHTTPClientTask

我认为我可以做类似的事情

extension HTTPClientTask: AuthHTTPClientTask { }

final class URLSessionHTTPClient: HTTPClient, AuthHTTPClient {
    
    private let session: URLSession
    private struct RequestError: Error { 
.........

但这会产生Extension of protocol 'HTTPClientTask' cannot have an inheritance clauseType 'URLSessionHTTPClient' does not conform to protocol 'AuthHTTPClient'

【问题讨论】:

    标签: ios swift swift-protocols urlsession


    【解决方案1】:

    您收到有关 inheritance clause 的错误,因为出于一致性目的需要约束,在您的示例中,编译器认为它是继承。

    您可以创建一个通用的私有方法来处理分派请求并扩展该方法的返回类型以符合您的模块类型。

    protocol HTTPClientTask {
        func cancel()
    }
    
    protocol HTTPClient {
        @discardableResult
        func execute(_ request: URLRequest, _ completion: @escaping (Result<(response: HTTPURLResponse, data: Data), Error>) -> Void) -> HTTPClientTask
    }
    
    protocol AuthHTTPClientTask {
        func cancel()
    }
    
    protocol AuthHTTPClient {
        @discardableResult
        func execute(_ request: URLRequest, _ completion: @escaping (Result<(response: HTTPURLResponse, data: Data), Error>) -> Void) -> AuthHTTPClientTask
    }
    
    
    private struct URLSessionTaskWrapper {
        let wrapped: URLSessionTask
        func cancel() {
            wrapped.cancel()
        }
    }
    
    extension URLSessionTaskWrapper: HTTPClientTask { }
    extension URLSessionTaskWrapper: AuthHTTPClientTask { }
    
    final class URLSessionHTTPClient {
        
        private let session: URLSession
        private struct RequestError: Error { }
    
        init(session: URLSession = .shared) {
            self.session = session
        }
        
        private func dispatch(_ request: URLRequest, _ completion: @escaping (Result<(response: HTTPURLResponse, data: Data), Error>) -> Void) -> URLSessionTaskWrapper {
            let task = session.dataTask(with: request) { data, response, error in
                completion(Result {
                    if let error = error {
                        throw error
                    } else if let data = data, let response = response as? HTTPURLResponse {
                        return (response, data)
                    } else {
                        throw RequestError()
                    }
                })
            }
            task.resume()
            return URLSessionTaskWrapper(wrapped: task)
        }
    }
    
    
    extension URLSessionHTTPClient: HTTPClient {
        func execute(_ request: URLRequest, _ completion: @escaping (Result<(response: HTTPURLResponse, data: Data), Error>) -> Void) -> HTTPClientTask {
            return dispatch(request, completion)
        }
    }
    
    extension URLSessionHTTPClient: AuthHTTPClient {
        func execute(_ request: URLRequest, _ completion: @escaping (Result<(response: HTTPURLResponse, data: Data), Error>) -> Void) -> AuthHTTPClientTask {
            return dispatch(request, completion)
        }
    }
    
    let requestURL = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
    
    let httpClient: HTTPClient = URLSessionHTTPClient()
    httpClient.execute(.init(url: requestURL)) { result in
        if let code = try? result.get().response.statusCode {
            print("HTTP", code)
        }
    }
    
    let authzHTTPClient: AuthHTTPClient = URLSessionHTTPClient()
    authzHTTPClient.execute(.init(url: requestURL)) { result in
        if let code = try? result.get().response.statusCode {
            print("Authz", code)
        }
    }
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-21
      • 1970-01-01
      • 2021-12-25
      • 1970-01-01
      • 1970-01-01
      • 2020-03-15
      • 2022-11-27
      • 2021-05-24
      相关资源
      最近更新 更多