如何转换to/from AnyPublisher<Void, Never>和AnyPublisher<Never, Never>?
How to convert to/from AnyPublisher<Void, Never> and AnyPublisher<Never, Never>?
我将异步请求包装在 Combine 发布者中,以便它们可以轻松地跨不同的管道使用。
消费者可能会按如下方式保留这些发布者:
struct Dependencies {
var loadImageRequest: AnyPublisher<UIImage, Never>
var saveToDatabaseRequest: AnyPublisher<Void, Never>
var saveToUserDefaultsRequest: AnyPublisher<Never, Never>
}
两种更常见的请求类型是:
- 即发即弃(立即完成):例如,将值保存到用户默认值,可以建模为即发即弃。到目前为止,
AnyPublisher<Never, Never>
似乎是表达这种类型的好方法。这可以通过 Empty<Never, Never>(completeImmediately: true)
. 轻松构建
- 触发、等待并忽略结果:一个例子是将值保存到数据库(并忽略结果),但仍想等到保存完成在继续管道之前完成。我一直在使用
AnyPublisher<Void, Never>
来模拟这些请求类型。构造这些的一种简单方法是通过 Future<Void, Never>() { promise in promise(.success(()))}
.
这两个都有忽略结果的共同主题。因此,在将这些交给消费者时,有时在这两种数据类型之间进行转换很有用:AnyPublisher<Never, Never>
和 AnyPublisher<Void, Never>
.
可以通过三种方式在两者之间进行转换:
Never -> Void
,立即完成
一种转换方法是强制转换:
let neverPublisher: AnyPublisher<Never, Never> = ...
let voidPublisher: AnyPublisher<Void, Never> = neverPublisher
.map { _ in () }
.append(Just(()))
.eraseToAnyPublisher()
Void -> Never
,等待虚空完成
因为这个有一个内置运算符,所以转换很容易:
let voidPublisher: AnyPublisher<Void, Never> = ...
let neverPublisher: AnyPublisher<Never, Never> = voidPublisher
.ignoreOutput()
.eraseToAnyPublisher()
Void -> Never
,立即完成
我不确定进行此转换的最佳方法。我提出的解决方案有两个主要缺点:使用 handleEvents
,并且需要在某处定义 cancellables
:
let cancellables: Set<AnyCancellable> = []
let voidPublisher: AnyPublisher<Void, Never> = ...
let neverPublisher: AnyPublisher<Never, Never> = Empty<Never, Never>(completeImmediately: true)
.handleEvents(receiveCompletion: { _ in voidPublisher.sink(receiveValue: { _ in }).store(in: &cancellables) })
.eraseToAnyPublisher()
问题:
- 是否有更好的方法来执行转换 #1(
Never -> Void
,立即完成)而不需要同时调用 map
和 append
? (例如类似于ignoreOutput
是如何解决第二次转换的?)
- 有没有更好的方法来进行不需要
cancellables
的第三次转换(Void -> Never
,立即完成)?
我还没有找到更优雅的方法来执行 #2。
这是更好的#3(Void -> Never
,立即完成)解决方案:
let voidPublisher: AnyPublisher<Void, Never> = ...
let neverPublisher: AnyPublisher<Never, Never> = Publishers.Merge(Just<Void>(()), saveToDatabase)
.first()
.ignoreOutput()
.eraseToAnyPublisher()
这里是 我用来确保一切都按预期运行的方法:
import Combine
import Foundation
let delaySec: TimeInterval = 0.1
let halfDelaySec: TimeInterval = delaySec / 2
let halfDelayMicroSeconds: useconds_t = useconds_t(halfDelaySec * 1_000_000)
let sleepBufferMicroSeconds: useconds_t = useconds_t(0.01 * 1_000_000)
var cancellables = [AnyCancellable]()
var output: [String] = []
func performVoidAction(voidPublisher: AnyPublisher<Void, Never>) {
voidPublisher
.handleEvents(
receiveCompletion: { _ in output.append("performVoidAction - completion") },
receiveCancel: { output.append("performVoidAction - cancel") })
.sink(receiveValue: { output.append("performVoidAction - sink") })
.store(in: &cancellables)
}
func performNeverAction(neverPublisher: AnyPublisher<Never, Never>) {
neverPublisher
.handleEvents(
receiveCompletion: { _ in output.append("performNeverAction - completion") },
receiveCancel: { output.append("performNeverAction - cancel") })
.sink(receiveValue: { _ in output.append("performNeverAction - sink") })
.store(in: &cancellables)
}
func makeSaveToDatabasePublisher() -> AnyPublisher<Void, Never> {
Deferred { _saveToDatabase() }.eraseToAnyPublisher()
}
func makeSaveToUserDefaultsPublisher() -> AnyPublisher<Never, Never> {
Deferred { _saveToUserDefaults() }.eraseToAnyPublisher()
}
// --->(Void)|
// AnyPublisher<Void, Never> wraps an API that does something and finishes upon completion.
private func _saveToDatabase() -> AnyPublisher<Void, Never> {
return Future<Void, Never> { promise in
output.append("saving to database")
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: DispatchTime.now() + delaySec) {
output.append("saved to database")
promise(.success(()))
}
}.eraseToAnyPublisher()
}
// |
// AnyPublisher<Never, Never> wraps an API that does something and completes immediately, it does not wait for completion.
private func _saveToUserDefaults() -> AnyPublisher<Never, Never> {
output.append("saved to user defaults")
return Empty<Never, Never>(completeImmediately: true)
.eraseToAnyPublisher()
}
func assert(_ value: Bool) -> String {
value ? "✅" : "❌"
}
// tests
assert(output.isEmpty)
var saveToDatabase = makeSaveToDatabasePublisher()
assert(output.isEmpty, "It should not fire the action yet.")
// verify database save, first time
performVoidAction(voidPublisher: saveToDatabase)
assert(!output.isEmpty && output.removeFirst() == "saving to database")
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(!output.isEmpty && output.removeFirst() == "saved to database")
assert(!output.isEmpty && output.removeFirst() == "performVoidAction - sink")
assert(!output.isEmpty && output.removeFirst() == "performVoidAction - completion")
assert(output.isEmpty)
// verify database save, second time
performVoidAction(voidPublisher: saveToDatabase)
assert(!output.isEmpty && output.removeFirst() == "saving to database")
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(!output.isEmpty && output.removeFirst() == "saved to database")
assert(!output.isEmpty && output.removeFirst() == "performVoidAction - sink")
assert(!output.isEmpty && output.removeFirst() == "performVoidAction - completion")
assert(output.isEmpty)
var saveToUserDefaults = makeSaveToUserDefaultsPublisher()
assert(output.isEmpty, "It should not fire the action yet.")
// verify user defaults save, first time
performNeverAction(neverPublisher: saveToUserDefaults)
assert(!output.isEmpty && output.removeFirst() == "saved to user defaults")
assert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")
assert(output.isEmpty) // 'perform never action' should never be output
// verify user defaults save, second time
performNeverAction(neverPublisher: saveToUserDefaults)
assert(!output.isEmpty && output.removeFirst() == "saved to user defaults")
assert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")
assert(output.isEmpty) // 'perform never action' should never be output
// MARK: - Problem: AnyPublisher<Never, Never> -> AnyPublisher<Void, Never>
// MARK: Solution 1
// `|` ➡️ `(Void)|`
performVoidAction(
voidPublisher: saveToUserDefaults
.map { _ in () }
.append(Just(()))
.eraseToAnyPublisher())
assert(output.removeFirst() == "saved to user defaults")
assert(!output.isEmpty && output.removeFirst() == "performVoidAction - sink")
assert(!output.isEmpty && output.removeFirst() == "performVoidAction - completion")
assert(output.isEmpty) // 'perform never action' should never be output"
// MARK: - Problem: AnyPublisher<Void, Never> -> AnyPublisher<Never, Never>
// MARK: Solution 2 (Wait)
// `--->(Void)|` ➡️ `--->|`
performNeverAction(
neverPublisher: saveToDatabase.ignoreOutput().eraseToAnyPublisher())
assert(!output.isEmpty && output.removeFirst() == "saving to database")
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(!output.isEmpty && output.removeFirst() == "saved to database")
assert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")
assert(output.isEmpty)
// MARK: Solution 3 (No wait)
// `--->(Void)|` ➡️ `|`
performNeverAction(
neverPublisher: Publishers.Merge(Just<Void>(()), saveToDatabase)
.first()
.ignoreOutput()
.eraseToAnyPublisher())
assert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")
assert(!output.isEmpty && output.removeFirst() == "saving to database")
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(!output.isEmpty && output.removeFirst() == "saved to database")
assert(output.isEmpty)
print("done")
最后,作为 Publisher 的运营商解决方案如下:
extension Publisher where Output == Never {
func asVoid() -> AnyPublisher<Void, Failure> {
self
.map { _ in () }
.append(Just(()).setFailureType(to: Failure.self))
.eraseToAnyPublisher()
}
}
extension Publisher where Output == Void {
func asNever(completeImmediately: Bool) -> AnyPublisher<Never, Failure> {
if completeImmediately {
return Just<Void>(())
.setFailureType(to: Failure.self)
.merge(with: self)
.first()
.ignoreOutput()
.eraseToAnyPublisher()
} else {
return self
.ignoreOutput()
.eraseToAnyPublisher()
}
}
}
我将异步请求包装在 Combine 发布者中,以便它们可以轻松地跨不同的管道使用。
消费者可能会按如下方式保留这些发布者:
struct Dependencies {
var loadImageRequest: AnyPublisher<UIImage, Never>
var saveToDatabaseRequest: AnyPublisher<Void, Never>
var saveToUserDefaultsRequest: AnyPublisher<Never, Never>
}
两种更常见的请求类型是:
- 即发即弃(立即完成):例如,将值保存到用户默认值,可以建模为即发即弃。到目前为止,
AnyPublisher<Never, Never>
似乎是表达这种类型的好方法。这可以通过Empty<Never, Never>(completeImmediately: true)
. 轻松构建
- 触发、等待并忽略结果:一个例子是将值保存到数据库(并忽略结果),但仍想等到保存完成在继续管道之前完成。我一直在使用
AnyPublisher<Void, Never>
来模拟这些请求类型。构造这些的一种简单方法是通过Future<Void, Never>() { promise in promise(.success(()))}
.
这两个都有忽略结果的共同主题。因此,在将这些交给消费者时,有时在这两种数据类型之间进行转换很有用:AnyPublisher<Never, Never>
和 AnyPublisher<Void, Never>
.
可以通过三种方式在两者之间进行转换:
Never -> Void
,立即完成一种转换方法是强制转换:
let neverPublisher: AnyPublisher<Never, Never> = ... let voidPublisher: AnyPublisher<Void, Never> = neverPublisher .map { _ in () } .append(Just(())) .eraseToAnyPublisher()
Void -> Never
,等待虚空完成因为这个有一个内置运算符,所以转换很容易:
let voidPublisher: AnyPublisher<Void, Never> = ... let neverPublisher: AnyPublisher<Never, Never> = voidPublisher .ignoreOutput() .eraseToAnyPublisher()
Void -> Never
,立即完成我不确定进行此转换的最佳方法。我提出的解决方案有两个主要缺点:使用
handleEvents
,并且需要在某处定义cancellables
:let cancellables: Set<AnyCancellable> = [] let voidPublisher: AnyPublisher<Void, Never> = ... let neverPublisher: AnyPublisher<Never, Never> = Empty<Never, Never>(completeImmediately: true) .handleEvents(receiveCompletion: { _ in voidPublisher.sink(receiveValue: { _ in }).store(in: &cancellables) }) .eraseToAnyPublisher()
问题:
- 是否有更好的方法来执行转换 #1(
Never -> Void
,立即完成)而不需要同时调用map
和append
? (例如类似于ignoreOutput
是如何解决第二次转换的?) - 有没有更好的方法来进行不需要
cancellables
的第三次转换(Void -> Never
,立即完成)?
我还没有找到更优雅的方法来执行 #2。
这是更好的#3(Void -> Never
,立即完成)解决方案:
let voidPublisher: AnyPublisher<Void, Never> = ...
let neverPublisher: AnyPublisher<Never, Never> = Publishers.Merge(Just<Void>(()), saveToDatabase)
.first()
.ignoreOutput()
.eraseToAnyPublisher()
这里是
import Combine
import Foundation
let delaySec: TimeInterval = 0.1
let halfDelaySec: TimeInterval = delaySec / 2
let halfDelayMicroSeconds: useconds_t = useconds_t(halfDelaySec * 1_000_000)
let sleepBufferMicroSeconds: useconds_t = useconds_t(0.01 * 1_000_000)
var cancellables = [AnyCancellable]()
var output: [String] = []
func performVoidAction(voidPublisher: AnyPublisher<Void, Never>) {
voidPublisher
.handleEvents(
receiveCompletion: { _ in output.append("performVoidAction - completion") },
receiveCancel: { output.append("performVoidAction - cancel") })
.sink(receiveValue: { output.append("performVoidAction - sink") })
.store(in: &cancellables)
}
func performNeverAction(neverPublisher: AnyPublisher<Never, Never>) {
neverPublisher
.handleEvents(
receiveCompletion: { _ in output.append("performNeverAction - completion") },
receiveCancel: { output.append("performNeverAction - cancel") })
.sink(receiveValue: { _ in output.append("performNeverAction - sink") })
.store(in: &cancellables)
}
func makeSaveToDatabasePublisher() -> AnyPublisher<Void, Never> {
Deferred { _saveToDatabase() }.eraseToAnyPublisher()
}
func makeSaveToUserDefaultsPublisher() -> AnyPublisher<Never, Never> {
Deferred { _saveToUserDefaults() }.eraseToAnyPublisher()
}
// --->(Void)|
// AnyPublisher<Void, Never> wraps an API that does something and finishes upon completion.
private func _saveToDatabase() -> AnyPublisher<Void, Never> {
return Future<Void, Never> { promise in
output.append("saving to database")
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: DispatchTime.now() + delaySec) {
output.append("saved to database")
promise(.success(()))
}
}.eraseToAnyPublisher()
}
// |
// AnyPublisher<Never, Never> wraps an API that does something and completes immediately, it does not wait for completion.
private func _saveToUserDefaults() -> AnyPublisher<Never, Never> {
output.append("saved to user defaults")
return Empty<Never, Never>(completeImmediately: true)
.eraseToAnyPublisher()
}
func assert(_ value: Bool) -> String {
value ? "✅" : "❌"
}
// tests
assert(output.isEmpty)
var saveToDatabase = makeSaveToDatabasePublisher()
assert(output.isEmpty, "It should not fire the action yet.")
// verify database save, first time
performVoidAction(voidPublisher: saveToDatabase)
assert(!output.isEmpty && output.removeFirst() == "saving to database")
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(!output.isEmpty && output.removeFirst() == "saved to database")
assert(!output.isEmpty && output.removeFirst() == "performVoidAction - sink")
assert(!output.isEmpty && output.removeFirst() == "performVoidAction - completion")
assert(output.isEmpty)
// verify database save, second time
performVoidAction(voidPublisher: saveToDatabase)
assert(!output.isEmpty && output.removeFirst() == "saving to database")
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(!output.isEmpty && output.removeFirst() == "saved to database")
assert(!output.isEmpty && output.removeFirst() == "performVoidAction - sink")
assert(!output.isEmpty && output.removeFirst() == "performVoidAction - completion")
assert(output.isEmpty)
var saveToUserDefaults = makeSaveToUserDefaultsPublisher()
assert(output.isEmpty, "It should not fire the action yet.")
// verify user defaults save, first time
performNeverAction(neverPublisher: saveToUserDefaults)
assert(!output.isEmpty && output.removeFirst() == "saved to user defaults")
assert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")
assert(output.isEmpty) // 'perform never action' should never be output
// verify user defaults save, second time
performNeverAction(neverPublisher: saveToUserDefaults)
assert(!output.isEmpty && output.removeFirst() == "saved to user defaults")
assert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")
assert(output.isEmpty) // 'perform never action' should never be output
// MARK: - Problem: AnyPublisher<Never, Never> -> AnyPublisher<Void, Never>
// MARK: Solution 1
// `|` ➡️ `(Void)|`
performVoidAction(
voidPublisher: saveToUserDefaults
.map { _ in () }
.append(Just(()))
.eraseToAnyPublisher())
assert(output.removeFirst() == "saved to user defaults")
assert(!output.isEmpty && output.removeFirst() == "performVoidAction - sink")
assert(!output.isEmpty && output.removeFirst() == "performVoidAction - completion")
assert(output.isEmpty) // 'perform never action' should never be output"
// MARK: - Problem: AnyPublisher<Void, Never> -> AnyPublisher<Never, Never>
// MARK: Solution 2 (Wait)
// `--->(Void)|` ➡️ `--->|`
performNeverAction(
neverPublisher: saveToDatabase.ignoreOutput().eraseToAnyPublisher())
assert(!output.isEmpty && output.removeFirst() == "saving to database")
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(!output.isEmpty && output.removeFirst() == "saved to database")
assert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")
assert(output.isEmpty)
// MARK: Solution 3 (No wait)
// `--->(Void)|` ➡️ `|`
performNeverAction(
neverPublisher: Publishers.Merge(Just<Void>(()), saveToDatabase)
.first()
.ignoreOutput()
.eraseToAnyPublisher())
assert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")
assert(!output.isEmpty && output.removeFirst() == "saving to database")
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(output.isEmpty)
usleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)
assert(!output.isEmpty && output.removeFirst() == "saved to database")
assert(output.isEmpty)
print("done")
最后,作为 Publisher 的运营商解决方案如下:
extension Publisher where Output == Never {
func asVoid() -> AnyPublisher<Void, Failure> {
self
.map { _ in () }
.append(Just(()).setFailureType(to: Failure.self))
.eraseToAnyPublisher()
}
}
extension Publisher where Output == Void {
func asNever(completeImmediately: Bool) -> AnyPublisher<Never, Failure> {
if completeImmediately {
return Just<Void>(())
.setFailureType(to: Failure.self)
.merge(with: self)
.first()
.ignoreOutput()
.eraseToAnyPublisher()
} else {
return self
.ignoreOutput()
.eraseToAnyPublisher()
}
}
}