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)
}
有两个问题:
- 当
tryMap
中的验证失败时,将调用 catch
- 当API调用失败时,会发生与上述相同的情况。
我可以用 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)
我有一个连接到触发网络负载的 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)
}
有两个问题:
- 当
tryMap
中的验证失败时,将调用catch
- 当API调用失败时,会发生与上述相同的情况。
我可以用 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)