【问题标题】:Swift Combine publisher won't trigger in flatMap after finished event [duplicate]完成事件后,Swift Combine 发布者不会在 flatMap 中触发 [重复]
【发布时间】:2020-10-30 18:46:02
【问题描述】:

我正在尝试在进行网络调用时使用 CurrentValueSubject 创建刷新事件。因此,每当网络请求失败时,我可以按下按钮再次发出请求,但无法使其工作,因为失败事件将终止发布者并且它不会再次工作。

import Combine
import SwiftUI

struct ContentView: View {

    @ObservedObject var viewModel: TestViewModel = TestViewModel()

    var body: some View {
        Button("Test", action: viewModel.test)
        Button("Refresh", action: viewModel.refresh)
    }
}

class TestViewModel: ObservableObject {

    var bag = Set<AnyCancellable>()
    private let changeSubject = CurrentValueSubject<Void, Never>(())

    func test() {
        changeSubject
            .flatMap { self.networkPublisher }
            .sink(
                receiveCompletion: {
                    switch $0 {
                        case .failure:
                            print("Failure")
                        case .finished:
                            print("Finished")
                    }
                },
                receiveValue: {
                    print("Value: \($0)")
                }
            )
            .store(in: &bag)
    }

    func refresh() {
        changeSubject.send(())
    }

    var networkPublisher: AnyPublisher<String, Error> {
        var url = URLRequest(url: URLComponents(string: "www.google.com")!.url!)
        url.httpMethod = "GET"
        return URLSession.shared
            .dataTaskPublisher(for: url)
            .tryMap { _ -> String in "Result" }
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
}

要解决这个问题,我可以使用.tryCatch { _ -&gt; Just&lt;String&gt; in Just("Error") } 来捕获错误,这将阻止发布者终止。但是为什么发布者终止时它不起作用?完成活动后如何工作?

【问题讨论】:

标签: swift combine


【解决方案1】:

在出错时不终止管道的模式是使用flatMap 捕获错误:

changeSubject
   .flatMap {
      networkPublisher
         .catch { _ in Empty() }
   }
   .sink {
      print("Value: \($0)")
   }
   .store(in: &bag)

如您所见,.sink 仅收到 String 的输出和 Never 的错误,因为错误在 .flatMap 内部完全处理

【讨论】:

  • 这与我在问题中解释的几乎相同,您可以在使用 tryCatch 时执行此操作。但这不是我想要的。我想使用 error 而不是 never 并且通常只是理解为什么新的网络请求实例不起作用。
  • @kosas,错误的整个想法是表示一个具有故障状态的链的完成,即订阅(存储在bag 中的订阅)是完成,不应再收到任何值。您说“我想使用错误”- 究竟如何使用?
  • 我认为在发出类似于 .retry() 的错误后,可以让发布者保持活动状态。就我而言,我希望它保持活动状态,直到我将其从包中取出或完成,但不是在发出错误时。
  • @kosas, .retry 重新订阅上游。好的,所以您的用例可以完全满足 Never 失败类型...即即使发生错误,订阅仍然有效,直到取消
【解决方案2】:

您应该映射到一个新实例,而不是映射到发布者的单个实例。您的持久化 networkPublisher 触发其数据任务并完成,但您继续在 flatMap 中映射相同的已完成流。

var networkPublisher 应该是一个计算值,这样您每次传递都会返回一个新的发布者(和一个新的数据任务)。

var networkPublisher: AnyPublisher<String, Error> {
        var url = URLRequest(url: URLComponents(string: "www.google.com")!.url!)
        url.httpMethod = "GET"
        return URLSession.shared
            .dataTaskPublisher(for: url)
            .tryMap { _ -> String in "Result" }
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
}

看看来自 Apple 的this sample code。数据发布者本身是在 flatMap 的闭包中创建的,这强调了它每次都是一个新实例这一事实。

【讨论】:

  • 实际上,我在项目中将它作为计算变量,但这也不起作用。
猜你喜欢
  • 2019-11-16
  • 1970-01-01
  • 1970-01-01
  • 2011-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-25
  • 1970-01-01
相关资源
最近更新 更多