【问题标题】:Combine sink: ignore receiveValue, only completion is needed合并sink:忽略receiveValue,只需要完成
【发布时间】:2021-09-24 07:46:58
【问题描述】:

考虑以下代码:

        CurrentValueSubject<Void, Error>(())
            .eraseToAnyPublisher()
            .sink { completion in

                switch completion {
                case .failure(let error):
                    print(error)
                    print("FAILURE")
                case .finished:
                    print("SUCCESS")
                }
            } receiveValue: { value in
                // this should be ignored
            }

只需查看 CurrentValueSubject 初始化程序,就可以清楚地看出该值不需要/无关紧要。

我正在使用这个特定的发布者发出一个异步网络请求,该请求可以通过也可以失败。

由于我对此发布者返回的值不感兴趣(没有),我怎样才能摆脱 receiveValue 闭包?

理想情况下,调用站点代码应如下所示:

        CurrentValueSubject<Void, Error>(())
            .eraseToAnyPublisher()
            .sink { completion in

                switch completion {
                case .failure(let error):
                    print(error)
                    print("FAILURE")
                case .finished:
                    print("SUCCESS ")
                }
            }

也可能是我应该使用 AnyPublisher 以外的其他东西,所以如果它更适合目的,请随意提出/重写 API。

我能找到的最接近的解决方案是ignoreOutput,但它仍然返回一个值。

【问题讨论】:

  • 最后使用} receiveValue: { _ in } 有什么问题。 sink 的接口契约没有可选参数,所以必须指定参数。
  • 你只是对美学很挑剔吗?我认为没有类似的内置功能,但您始终可以在 Publisher 扩展中编写不带 receiveValue 参数的 sink 重载。另外,请考虑使用Future,而不是CurrentValueSubject
  • 是的,我只是不确定我是否为该任务使用了正确的 API。 AnyPublisher 适合这个用例吗?如果没有更好的选择,我会坚持使用} receiveValue: { _ in } 的建议。

标签: swift swiftui reactive-programming combine


【解决方案1】:

您可以通过完成来声明另一个接收器:

extension CurrentValueSubject where Output == Void {
    
    func sink(receiveCompletion: @escaping ((Subscribers.Completion<Failure>) -> Void)) -> AnyCancellable {
        sink(receiveCompletion: receiveCompletion, receiveValue: {})
    }
}

【讨论】:

    【解决方案2】:

    CurrentValueSubject 似乎是一个令人困惑的选择,因为当您第一次订阅它时,它将发送一个初始值(Void)。

    您可以使用Future 使事情变得不那么模糊,它会在完成后发送一个且唯一的值。

    为了避免接收您不关心的值,您可以翻转情况并使用Result&lt;Void, Error&gt; 的输出类型和Never 的失败类型。在处理您的网络请求时,您可以使用.failure(error).success(()) 履行承诺,并在 sink 中处理:

    let pub = Future<Result<Void, Error>, Never> {
        promise in
        // Do something asynchronous
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            promise(.success(.success(())))
            //or
            //promise(.success(.failure(error)))
        }
    }.eraseToAnyPublisher()
    
    // somewhere else...
    pub.sink {
        switch $0 {
        case .failure(let error):
            print("Whoops \(error)")
        case .success:
            print("Yay")
        }
    }
    

    您正在将链的一端的丑陋代码交换为另一端的丑陋代码,但如果它隐藏在 AnyPublisher 后面并且您担心正确使用,那似乎是要走的路。消费者可以通过查看输出和错误类型确切地看到预期的结果,而不必在单独的闭包中处理它们。

    【讨论】:

    • 它实际上是 API 其他部分的 Future。所以CurrentValueSubject&lt;Void, Error&gt; 我只是为了举例而发明的。可能是AnyPublisher。但在引擎盖下它实际上是一个未来,所以你的例子似乎运作良好。调用现场的正确性优先。
    • 这种方法的缺点是我不能使用.mapError({$0 as Error})在链中稍后类型擦除Error,所以两者都有它们的权衡
    • 您可以将 Result 映射到 Result 但是是的,这都是关于权衡
    • 我已经通过删除 API 中的错误类型解决了这个问题。尽管如此,由于它的复杂性,我最终还是没有使用这个 API:`promise(.success(.success(())))`
    猜你喜欢
    • 2020-08-07
    • 1970-01-01
    • 2021-03-27
    • 1970-01-01
    • 2013-09-20
    • 1970-01-01
    • 1970-01-01
    • 2015-05-21
    • 1970-01-01
    相关资源
    最近更新 更多