为啥 URLSession.DataTaskPublisher 从来不发布值?

Posted

技术标签:

【中文标题】为啥 URLSession.DataTaskPublisher 从来不发布值?【英文标题】:Why doesn't URLSession.DataTaskPublisher ever publish values?为什么 URLSession.DataTaskPublisher 从来不发布值? 【发布时间】:2019-08-31 07:24:24 【问题描述】:

在 Xcode 11 beta 5 或 6 中,我依赖于 URLSession.DataTaskPublisher 的现有代码停止工作。 DataTaskPublisher 似乎从未发布任何值,但我不知道为什么。

我已尝试将.sink.handleEvents 作为订阅者。我已经用Just 发布者测试了 .sink 并确认它在那里收到了一个值。

我也尝试过给DataTaskPublisher 一个URL 和一个URLRequest。我尝试了对 API 的请求,包括授权标头,以及对 google.com 和 apple.com 的基本请求。我尝试使用URLSession.shared 并创建URLSession 的新实例。我也尝试过使用和不使用 mapdecode 运算符。

我已经使用 XCTest 期望来确认测试每次都会超时,即使我给它设置了 4 分钟的超时时间。

我刚刚创建了一个新的示例项目,并在根视图控制器中使用以下代码复制了问题:

override func viewDidLoad() 
        super.viewDidLoad()

        print("view did load")

        URLSession.shared.dataTaskPublisher(for: URL(string: "http://apple.com")!)
            .handleEvents(receiveSubscription:  (sub) in
                print(sub)
            , receiveOutput:  (response) in
                print(response)
            , receiveCompletion:  (completion) in
                print(completion)
            , receiveCancel: 
                print("cancel")
            , receiveRequest:  (demand) in
                print(demand)
            )
    

项目打印“视图已加载”,但没有打印任何其他内容。关于我在哪里出错的任何想法?谢谢!

【问题讨论】:

avanderlee.com/debugging/combine-swift 可能有一些提示。 print 和 catch 似乎很有用。 @matt — URL 重定向应该没问题,但我也尝试了其他 URL,并且所有这些 URL 都有相同的问题,所以这似乎不是问题。不过,感谢您的建议。 【参考方案1】:

我认为您的代码存在两个问题,首先您只有一个发布者(handleEvent 返回一个发布者),其次该发布者超出范围并消失了。这虽然不是很优雅,但仍然有效。


import Combine
import SwiftUI

var pub: AnyPublisher<(data: Data, response: URLResponse), URLError>? = nil
var sub: Cancellable? = nil

var data: Data? = nil
var response: URLResponse? = nil

func combineTest() 
    guard let url = URL(string: "https://apple.com") else 
        return
    
    pub = URLSession.shared.dataTaskPublisher(for: url)
            .print("Test")
            .eraseToAnyPublisher()
    sub = pub?.sink(
        receiveCompletion:  completion in
            switch completion 
            case .finished:
                break
            case .failure(let error):
                fatalError(error.localizedDescription)
            
        ,
        receiveValue:  data = $0.data; response = $0.response 
    )


struct ContentView: View 
    var body: some View 
        Button(
            action:  combineTest() ,
            label:  Text("Do It").font(.largeTitle) 
        )
    


我是在 SwiftUI 中完成的,这样我就不用担心了,我使用了 3 个变量以便我可以更好地遵循。您需要使用 2 参数接收器,因为发布者的错误不是 Never。最后 print() 仅用于测试并且效果很好。

【讨论】:

说出版商超出范围并不完全正确。它从未在范围内,因为它从未被分配过。对不起。 谢谢!正如我所提到的,我尝试使用sink,但它仍然不起作用,但您确实帮助我找到了主要问题,即我需要保留对订阅者的引用。我曾尝试为它创建一个局部变量,但我需要为订阅者创建一个类属性,这样它就不会超出范围。非常感谢您的帮助,这一直让我发疯!编辑:为了清楚起见,在类中保留对订阅者的引用,但保留对发布者的引用。因此,保留对发布者的引用似乎是可选的。 对不起,我没有说清楚。在您的原始代码中,发布者在一阵烟雾中消失了,但重要的是保留订阅者。我相信可取消的在他们的 deinit 中有一个取消,这会破坏链。我发现如果您有多个订阅者,那么如果有任何取消它会停止所有订阅者,也许这就是多播的用途。

以上是关于为啥 URLSession.DataTaskPublisher 从来不发布值?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 DataGridView 上的 DoubleBuffered 属性默认为 false,为啥它受到保护?

为啥需要softmax函数?为啥不简单归一化?

为啥 g++ 需要 libstdc++.a?为啥不是默认值?

为啥或为啥不在 C++ 中使用 memset? [关闭]

为啥临时变量需要更改数组元素以及为啥需要在最后取消设置?

为啥 CAP 定理中的 RDBMS 分区不能容忍,为啥它可用?