如何为URLSession.DataTaskPublisher添加无需额外sink的logEvents()方法
为URLSession.DataTaskPublisher实现logEvents()方法
需求说明
需要为URLSession.DataTaskPublisher添加logEvents()方法,调用时实现以下功能:
- 打印
URLRequest的请求信息 - 请求成功时打印返回的数据及响应对象
- 请求失败时打印错误信息
期望用法示例:
URLSession.shared.dataTaskPublisher(for: URL(string: "https://jsonplaceholder.typicode.com/posts")!) .logEvents() .map { data, _ in return data } .decode(type: [Post].self, decoder: JSONDecoder()) .eraseToAnyPublisher() .sink { /// 处理接收的值 }
已有实现参考
此前已为URLSession.dataTask实现了带日志的版本,核心逻辑通过URLRequest.debugPrintYAML方法完成:
func dataTask(withLogging request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> ()) -> URLSessionDataTask { return self.dataTask(with: request) { data, response, error in URLRequest.debugPrintYAML(request: request, response: response, received: data, error: error) completionHandler(data, response, error) } }
尝试过的方案及问题
- 在
.map中实现:虽然能获取data和response,但需要修改返回类型,且在map中执行打印这类副作用操作不符合Combine的设计原则 - 在
.sink中实现:到达sink前的映射操作会丢失response和原始data信息,无法完成完整日志打印 - 自定义方法中使用
.handleEvents:尝试的代码未保留处理后的Publisher,直接返回原DataTaskPublisher导致日志逻辑未触发,且存在cancellable管理问题:
extension Publisher where Output == (data: Data, response: URLResponse), Self == URLSession.DataTaskPublisher { func logEvents() -> URLSession.DataTaskPublisher { let cancellable = self.handleEvents { values in print(values.description) } // cancellable未被持有,因此不会触发 return self } }
正确解决方案
通过扩展Publisher,利用handleEvents捕获流的输出和完成事件,结合DataTaskPublisher的request属性完成日志打印,同时返回处理后的流保证逻辑生效:
extension Publisher where Output == (data: Data, response: URLResponse), Self == URLSession.DataTaskPublisher { func logEvents() -> AnyPublisher<Output, Failure> { self.handleEvents( receiveOutput: { [request] (data, response) in URLRequest.debugPrintYAML( request: request, response: response, received: data, error: nil ) }, receiveCompletion: { [request] completion in if case .failure(let error) = completion { URLRequest.debugPrintYAML( request: request, response: nil, received: nil, error: error ) } } ) .eraseToAnyPublisher() } }
方案说明
- 借助
DataTaskPublisher自身的request属性,无需额外传递请求对象 - 在
receiveOutput回调中处理请求成功的场景,传入完整的请求、响应和数据信息 - 在
receiveCompletion回调中处理请求失败的场景,传入请求和错误信息 - 返回
handleEvents处理后的Publisher并通过eraseToAnyPublisher()隐藏内部类型,保证后续流操作正常执行,同时符合Combine的最佳实践 - 完全兼容期望的用法示例,无需修改后续的操作符调用
内容的提问来源于stack exchange,提问作者bogen
相关产品推荐
相关产品推荐

