快速合并完成按钮点击

时间:2020-07-01 14:37:14

标签: ios swift swiftui reactive combine

我有一个连接到PassthroughSubject的按钮,它触发了网络负载。我遇到的问题是,如果网络请求失败(或者飞行前验证失败),则PassthroughSubject已完成:

private let createListSubject = PassthroughSubject<Void, Never>()

点击按钮后,我会通过send(())向该主题发送一个新事件。

// Later on, in some function I set up the subject
    private func setupCreateListSubject() {
        self.createListSubject
            .combineLatest(self.$listName, self.$selectedClients)
            .tryMap { [weak self] (_, listName, selectedClients) -> (String, [String]) in
                let clients = Array(selectedClients)
                try self?.validate(listName: listName, selectedClients: clients)
                return (listName, clients)
            }
            .flatMap { [clientListCreator] (listName, selectedClients) -> AnyPublisher<Result<ClientListMembersDisplayable, Error>, Error> in
                return clientListCreator.createClientList(listName: listName, listMemberIds: selectedClients)
            }
            .catch { error  in
                return Future<Result<ClientListMembersDisplayable, Error>, Error> { // <-- One of the problems is that Future completes after 1 event
                    $0(.success(.failure(error)))
                }
            }
            .sink(receiveCompletion: { [weak self] completion in
                switch completion {
                case .failure(let error):
                    self?.errorAlertContext = AlertContext(title: error.localizedDescription)
                case .finished:
                    break
                }
            }, receiveValue: { [weak self] result in
                switch result {
                case .failure(let error):
                    self?.errorAlertContext = AlertContext(title: error.localizedDescription)
                case .success:
                    self?.errorAlertContext = nil
                }
            }).store(in: &self.disposeBag)
    }

有两个问题:

  • tryMap中验证失败时,将调用catch
  • 当API调用失败时,发生与上述相同的事情。

我可以使用replaceError来解决这两个问题,但是我想要的是将发布商的错误转换成Result错误的成功{ 1}}不会收到任何完成事件(因为用户将来仍希望点击该按钮。

执行此操作的合并方法是什么? 我认为我想要的是类似于createListSubject的东西,但是收到了旧错误并返回了成功的结果。

2 个答案:

答案 0 :(得分:1)

模式是将其包装在flatMap中,因此您可以通过产生一个输出为Result<..., Error>且失败为Never的新发布者来处理每个值:

createListSubject
   .combineLatest(self.$listName, self.$selectedClients)
   .flatMap { [weak self] (_, listName, selectedClients) -> AnyPublisher<Result< ClientListMembersDisplayable, Error>, Never> in
        
      Just(())
      .tryMap { 
         try self?.validate(listName: listName, selectedClients: selectedClients)
      }
      .flatMap {
         // I'm assuming this returns AnyPublisher<Result<ClientListMembersDisplayable, Error>, Error>
         clientListCreator.createClientList(listName: listName, listMemberIds: selectedClients)
      }
      .catch { err -> AnyPublisher<Result<ClientListMembersDisplayable, Error>, Never>
         Just(.failure(err))
      }
      .eraseToAnyPublisher()
   }
   .sink (...)
   .store(in: &self.disposeBag)

答案 1 :(得分:0)

为清楚起见,您没有发布任何完成createListSubject的代码。您在createListSubject的下游构建新的发布者,这些下游的发布者可能会失败。

您在catch区块的正确轨道上。您的catch块将上游故障替换为新发布者的输出。问题在于您的新发布者是Future,它仅发布一个输出然后完成。

我们要做的是发送该输出,然后再次追加整个管道,如下所示:

        .catch {
            Just(.failure($0))
                .append(theWholePipelineAgain())
        }

这意味着我们需要将setupCreateListSubject分为两部分。一部分返回管道(直到catch,包括sink),另一部分仅在管道上调用catch

由于我们使用Never将上游故障转换为正常输出,因此可以将Failure用作管道的private func createListPublisher() -> AnyPublisher<Result<ClientListMembersDisplayable, Error>, Never> { createListSubject .combineLatest( $listName, // Here I'm using .map to turn Set<String> into [String] so I don't have to do it in tryMap later. $selectedClients .map { Array($0) } ) .tryMap { [weak self] (_, listName, selectedClients) in // Here I'm creating a tuple and then returning just its .1 element, to let Swift // infer the return type. ( try self?.validate(listName: listName, selectedClients: selectedClients), (listName, selectedClients) ).1 } .flatMap { [clientListCreator] in clientListCreator.createClientList(listName: $0, listMemberIds: $1) } .catch { [weak self] in Just(.failure($0)) .append( // THIS IS WHERE THE MAGIC HAPPENS self?.createListPublisher().eraseToAnyPublisher() ?? Empty().eraseToAnyPublisher() ) } .eraseToAnyPublisher() } 类型。这是创建管道的函数:

private func subscribeToCreateListPublisher() {
    createListPublisher()
        .sink { [weak self] in
            switch $0 {
            case .failure(let failure):
                self?.errorAlertContext = AlertContext(title: failure.localizedDescription)
            case .success(_):
                self?.errorAlertContext = nil
            }
        }
        .store(in: &disposeBag)
}

这是订阅管道的函数:

Failure

由于管道尚未完成,并且具有Never类型的receiveCompletion:,因此我们不再需要为sink赋予{{1}}块。