我将异步请求包装在组合发布者中,以便可以轻松地跨不同管道使用它们。
消费者可能会按如下方式保留这些发布者:
struct Dependencies {
var loadImageRequest: AnyPublisher<UIImage, Never>
var saveToDatabaseRequest: AnyPublisher<Void, Never>
var saveToUserDefaultsRequest: AnyPublisher<Never, Never>
}
Run Code Online (Sandbox Code Playgroud)
两种更常见的请求类型是:
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()
Run Code Online (Sandbox Code Playgroud)
Void -> Never
,等待Void完成
由于有一个内置运算符,因此转换很容易:
let voidPublisher: AnyPublisher<Void, Never> = ...
let neverPublisher: AnyPublisher<Never, Never> = voidPublisher
.ignoreOutput()
.eraseToAnyPublisher()
Run Code Online (Sandbox Code Playgroud)
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()
Run Code Online (Sandbox Code Playgroud)
问题:
Never -> Void
立即完成)而不需要同时调用map
and append
?(例如类似于如何ignoreOutput
用于解决第二次转换?)Void -> Never
不需要的第三次转换(立即完成)cancellables
?我还没有找到更优雅的方法来完成#2。
\n这是更好的#3(Void -> Never
立即完成)解决方案:
let voidPublisher: AnyPublisher<Void, Never> = ...\nlet neverPublisher: AnyPublisher<Never, Never> = Publishers.Merge(Just<Void>(()), saveToDatabase)\n .first()\n .ignoreOutput()\n .eraseToAnyPublisher()\n
Run Code Online (Sandbox Code Playgroud)\n这是我用来确保一切按预期运行的计时游乐场:
\nimport Combine\nimport Foundation\n\nlet delaySec: TimeInterval = 0.1\nlet halfDelaySec: TimeInterval = delaySec / 2\nlet halfDelayMicroSeconds: useconds_t = useconds_t(halfDelaySec * 1_000_000)\nlet sleepBufferMicroSeconds: useconds_t = useconds_t(0.01 * 1_000_000)\n\nvar cancellables = [AnyCancellable]()\nvar output: [String] = []\n\nfunc performVoidAction(voidPublisher: AnyPublisher<Void, Never>) {\n voidPublisher\n .handleEvents(\n receiveCompletion: { _ in output.append("performVoidAction - completion") },\n receiveCancel: { output.append("performVoidAction - cancel") })\n .sink(receiveValue: { output.append("performVoidAction - sink") })\n .store(in: &cancellables)\n}\n\nfunc performNeverAction(neverPublisher: AnyPublisher<Never, Never>) {\n neverPublisher\n .handleEvents(\n receiveCompletion: { _ in output.append("performNeverAction - completion") },\n receiveCancel: { output.append("performNeverAction - cancel") })\n .sink(receiveValue: { _ in output.append("performNeverAction - sink") })\n .store(in: &cancellables)\n}\n\nfunc makeSaveToDatabasePublisher() -> AnyPublisher<Void, Never> {\n Deferred { _saveToDatabase() }.eraseToAnyPublisher()\n}\n\nfunc makeSaveToUserDefaultsPublisher() -> AnyPublisher<Never, Never> {\n Deferred { _saveToUserDefaults() }.eraseToAnyPublisher()\n}\n\n// --->(Void)|\n// AnyPublisher<Void, Never> wraps an API that does something and finishes upon completion.\nprivate func _saveToDatabase() -> AnyPublisher<Void, Never> {\n return Future<Void, Never> { promise in\n output.append("saving to database")\n DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: DispatchTime.now() + delaySec) {\n output.append("saved to database")\n promise(.success(()))\n }\n }.eraseToAnyPublisher()\n}\n\n// |\n// AnyPublisher<Never, Never> wraps an API that does something and completes immediately, it does not wait for completion.\nprivate func _saveToUserDefaults() -> AnyPublisher<Never, Never> {\n output.append("saved to user defaults")\n return Empty<Never, Never>(completeImmediately: true)\n .eraseToAnyPublisher()\n}\n\nfunc assert(_ value: Bool) -> String {\n value ? "\xe2\x9c\x85" : "\xe2\x9d\x8c"\n}\n\n// tests\nassert(output.isEmpty)\n\nvar saveToDatabase = makeSaveToDatabasePublisher()\nassert(output.isEmpty, "It should not fire the action yet.")\n\n// verify database save, first time\nperformVoidAction(voidPublisher: saveToDatabase)\nassert(!output.isEmpty && output.removeFirst() == "saving to database")\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(!output.isEmpty && output.removeFirst() == "saved to database")\nassert(!output.isEmpty && output.removeFirst() == "performVoidAction - sink")\nassert(!output.isEmpty && output.removeFirst() == "performVoidAction - completion")\nassert(output.isEmpty)\n\n// verify database save, second time\nperformVoidAction(voidPublisher: saveToDatabase)\nassert(!output.isEmpty && output.removeFirst() == "saving to database")\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(!output.isEmpty && output.removeFirst() == "saved to database")\nassert(!output.isEmpty && output.removeFirst() == "performVoidAction - sink")\nassert(!output.isEmpty && output.removeFirst() == "performVoidAction - completion")\nassert(output.isEmpty)\n\nvar saveToUserDefaults = makeSaveToUserDefaultsPublisher()\nassert(output.isEmpty, "It should not fire the action yet.")\n\n// verify user defaults save, first time\nperformNeverAction(neverPublisher: saveToUserDefaults)\nassert(!output.isEmpty && output.removeFirst() == "saved to user defaults")\nassert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")\nassert(output.isEmpty) // \'perform never action\' should never be output\n\n// verify user defaults save, second time\nperformNeverAction(neverPublisher: saveToUserDefaults)\nassert(!output.isEmpty && output.removeFirst() == "saved to user defaults")\nassert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")\nassert(output.isEmpty) // \'perform never action\' should never be output\n\n// MARK: - Problem: AnyPublisher<Never, Never> -> AnyPublisher<Void, Never>\n\n// MARK: Solution 1\n// `|` \xe2\x9e\xa1\xef\xb8\x8f `(Void)|`\n\nperformVoidAction(\n voidPublisher: saveToUserDefaults\n .map { _ in () }\n .append(Just(()))\n .eraseToAnyPublisher())\nassert(output.removeFirst() == "saved to user defaults")\nassert(!output.isEmpty && output.removeFirst() == "performVoidAction - sink")\nassert(!output.isEmpty && output.removeFirst() == "performVoidAction - completion")\nassert(output.isEmpty) // \'perform never action\' should never be output"\n\n\n// MARK: - Problem: AnyPublisher<Void, Never> -> AnyPublisher<Never, Never>\n\n// MARK: Solution 2 (Wait)\n// `--->(Void)|` \xe2\x9e\xa1\xef\xb8\x8f `--->|`\n\nperformNeverAction(\n neverPublisher: saveToDatabase.ignoreOutput().eraseToAnyPublisher())\nassert(!output.isEmpty && output.removeFirst() == "saving to database")\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(!output.isEmpty && output.removeFirst() == "saved to database")\nassert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")\nassert(output.isEmpty)\n\n\n// MARK: Solution 3 (No wait)\n// `--->(Void)|` \xe2\x9e\xa1\xef\xb8\x8f `|`\n\nperformNeverAction(\n neverPublisher: Publishers.Merge(Just<Void>(()), saveToDatabase)\n .first()\n .ignoreOutput()\n .eraseToAnyPublisher())\nassert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")\nassert(!output.isEmpty && output.removeFirst() == "saving to database")\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(!output.isEmpty && output.removeFirst() == "saved to database")\nassert(output.isEmpty)\n\nprint("done")\n
Run Code Online (Sandbox Code Playgroud)\n最后,作为Publisher运营商的解决方案如下:
\nextension Publisher where Output == Never {\n func asVoid() -> AnyPublisher<Void, Failure> {\n self\n .map { _ in () }\n .append(Just(()).setFailureType(to: Failure.self))\n .eraseToAnyPublisher()\n }\n}\n\nextension Publisher where Output == Void {\n func asNever(completeImmediately: Bool) -> AnyPublisher<Never, Failure> {\n if completeImmediately {\n return Just<Void>(())\n .setFailureType(to: Failure.self)\n .merge(with: self)\n .first()\n .ignoreOutput()\n .eraseToAnyPublisher()\n } else {\n return self\n .ignoreOutput()\n .eraseToAnyPublisher()\n }\n }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n
归档时间: |
|
查看次数: |
7762 次 |
最近记录: |