Swift 合并完成按钮点击

Swift Combine completing button tap

我有一个连接到触发网络负载的 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
                    [=12=](.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)
    }

有两个问题:

我可以用 replaceError 解决这两个问题,但我想要的是将发布者的错误转化为 Result 错误 createListSubject 不接收任何完成事件(因为用户将来仍想点击该按钮。

执行此操作的 Combine 方法是什么? 我想我想要的是类似于 replaceError() 的东西,但它收到了旧错误并且 returns 成功了。

明确地说,您没有 post 任何完成 createListSubject 的代码。您在 createListSubject 下游构建新的发布者,这些下游发布者可能会失败。

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

我们要做的是发送那个输出,然后再次附加整个管道,像这样:

        .catch {
            Just(.failure([=10=]))
                .append(theWholePipelineAgain())
        }

这意味着我们需要将 setupCreateListSubject 分成两部分。一部分 returns 管道(直至并包括 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([=11=]) }
        )
        .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: [=11=], listMemberIds: )
        }
        .catch { [weak self] in
            Just(.failure([=11=]))
                .append(
                    // THIS IS WHERE THE MAGIC HAPPENS
                    self?.createListPublisher().eraseToAnyPublisher()
                        ?? Empty().eraseToAnyPublisher()
                )
        }
        .eraseToAnyPublisher()
}

这里是订阅管道的函数:

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

由于管道未完成,并且具有 Never 类型的 Failure,我们不再需要将 receiveCompletion: 块提供给 sink

模式是将它包装在 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)