Swift 合并发布者不会在完成事件后在 flatMap 中触发

Swift Combine publisher won't trigger in flatMap after finished event

我正在尝试在进行网络调用时使用 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 [=11=] {
                        case .failure:
                            print("Failure")
                        case .finished:
                            print("Finished")
                    }
                },
                receiveValue: {
                    print("Value: \([=11=])")
                }
            )
            .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 { _ -> Just<String> in Just("Error") } 来捕获错误,这样可以防止发布者终止。但是为什么它在发布者终止时不起作用?活动结束后如何工作?

您不应映射到发布者的单个实例,而应映射到一个新实例。您持久化的 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的闭包内创建的,强调每次都是一个新的实例。

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

捕获错误
changeSubject
   .flatMap {
      networkPublisher
         .catch { _ in Empty() }
   }
   .sink {
      print("Value: \([=10=])")
   }
   .store(in: &bag)

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