在 Combine 中处理发布者错误的最佳实践是什么? (从 CoreLocation 发布航向更新)

What is the best practice for dealing with errors in a publisher in Combine? (Publishing heading updates from CoreLocation)

我一直在开发一个带有指南针的应用程序,该应用程序会根据 this example for publishing heading changes and this example 更新其航向,以创建一个发布者来处理这些更改。

我是响应式编程的新手,但我 运行 遇到的问题似乎是一个常见问题,所以我想 post 看看是否有人可以提供帮助。

有时它会工作一段时间,新的标题来自出版商并更新 UI,但随后停止工作。其他时候,它永远不会开始更新(在任何标题更新之前传递错误)。在任何一种情况下,由于将失败完成事件发送给发布者,事件将停止是有意义的:

func locationManager(_ manager: CLLocationManager,
                     didFailWithError error: Error) {

    headingPublisher.send(completion: Subscribers.Completion.failure(error))

    print("error: \(error.localizedDescription)")
}

首先,我试图弄清楚这些错误是什么,以便我可以尝试更好地处理它们...这些错误真的值得让发布者停止吗?我想知道标题更新会发生什么样的错误 mid-stream,因为标题更新在错误后恢复,即使发布者停止。我尝试打印错误,但我得到的只是这个,这似乎没有帮助: error: The operation couldn’t be completed. (kCLErrorDomain error 0.)

我不擅长 iOS,所以如果有人对如何在此处获得更好的错误描述有任何建议,请告诉我。

其次,我想知道如何忽略订阅发布者的错误,如果这是我应该做的,那么即使出现错误,.sink 也会继续获取航向更新。下面是我的发布者代码,基于示例:

    _ = headingProxy
        .publisher
        .receive(on: RunLoop.main)
        .sink(receiveCompletion: { completion in },
              receiveValue: { [weak self] (heading) in
                self?.currentHeadingAccuracy = heading.headingAccuracy
                self?.currentHeading = heading.trueHeading
        })
        .store(in: &cancellableSet)

我意识到我永远无法将错误发送给发布者(这是我应该做的吗?有一个特定的发布者只用于从不发送错误的标题更新吗?)但我假设是这个人做的包括有充分理由将错误发送给发布者,最好的做法是在订阅端处理它,或者有一些 best-practice 用于在错误后恢复。

正如您所指出的,当发生错误时,将始终终止使用现成运算符构建的 Combine 管道。这是一个功能,而不是错误。尤其令人惊讶的是,即使您使用 catchreplaceError 等错误处理运算符,管道也会结束。

您可以通过包装可能在 flatMap.

中发生错误的管道的任何部分来使管道对错误具有弹性

将您的主管道视为 "outer" 管道,将包裹在 flatMap 中的管道视为 "inner" 管道。确保外部管道的错误类型是 Never 并且它可以永远继续处理值。

考虑一个端点 returns 一个数字的平方:

let myNumberPublisher = PassthroughSubject<Int, Never>()

// Outer pipeline will never error (the Error type is Never):
myNumberPublisher
  .map(String.init) // convert to string
  .flatMap { number in
    // Inner pipeline can error:
    URLSession.shared.dataTaskPublisher(for: URL(string: "https://square?n=\(number)")!)
      .replaceError(with: "Oopsies")
      .map { answer in "The answer is \(answer)" }
  }
  .sink { result in print(result) }

myNumberPublisher.send("5")
// => The answer is 25
myNumberPublisher.send("3") // assume the endpoint errors here
// => The answer is Oopsies
myNumberPublisher.send("6")
// => The answer is 36


要在您的情况下使用 flatMap,出售发布者的 class 可能会要求您在出现错误时随时请求新的发布者。

或者,您可以将 headingPublisher 修改为 Never 错误类型。如果你走这条路,错误应该由提供 headingPublisher.

的对象处理

另一种选择是出售两个发布者,一个用于值,另一个用于错误:

  • AnyPublisher<ValueType, Never>
  • AnyPublisher<Error, Never>

这些选项中的任何一个都可以,您选择哪个将取决于您的需要。