You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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)
    }
}

尝试过的方案及问题

  1. 在.map中实现:虽然能获取data和response,但需要修改返回类型,且在map中执行打印这类副作用操作不符合Combine的设计原则
  2. 在.sink中实现:到达sink前的映射操作会丢失response和原始data信息,无法完成完整日志打印
  3. 自定义方法中使用.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 03:53:15