ios - 如何返回实现另一个协议(protocol)的泛型类型

标签 ios swift swift-protocols urlsession

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

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

我有另一个框架要添加到我的应用程序中,它公开了一个相同的界面。
我不希望我的其他模块依赖于这个网络模块,而是希望它公开它所需的接口(interface)并让我的网络模块依赖它。
所以下面的接口(interface)被我的另一个模块暴露了
protocol AuthHTTPClientTask {
    func cancel()
}

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

如您所见,接口(interface)是精确的,但我希望避免创建整个 URLSessionHTTPClient只针对那个界面 - URLSessionAuthHTTPClient或行为等是相同的。
是否可以创建某种类型 URLSessionHTTPClient允许它返回 AuthHTTPClientTask 的实现或 HTTPClientTask ?
我以为我可以做类似的事情
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'

最佳答案

您收到有关 inheritance clause 的错误由于出于一致性目的需要约束,因此在您的示例中,编译器认为它是继承。
您可以创建一个通用的私有(private)方法来处理分派(dispatch)请求并扩展该方法的返回类型以符合您的模块类型。

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

关于ios - 如何返回实现另一个协议(protocol)的泛型类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62703484/

相关文章:

ios - 没有泛型约束的协议(protocol)中的泛型数组

ios - 在Objective-C中通过CoreTelephony.framework私有(private)API获取MCC和MNC

objective-c - 虚拟警告:不支持的配置最小字体大小大于当前字体大小

ios - 使用日期字符串Swift解析大型json数据时的CPU使用率高

swift - 约束关联类型

swift 协议(protocol)扩展默认实现与类中的实际实现

ios - 边框文本域

ios - this.NavigationController 为空(xamarin ios stoaryboard)

ios - 两个 UIStackView 的约束 - 一个在另一个下面

arrays - Scenekit 在将数组数据传递给 openGL 着色器中的统一数组时出现缓冲区大小错误