[英]Swift Combine: `prefix(untilCompletionFrom)`?
RxJava 有一個takeUntil
運算符,文檔將其描述為:
在第二個 Observable 發出一個項目或終止后丟棄任何由 Observable 發出的項目
最后一部分是我想要使用Combine
實現的。 但是我還沒有找到任何等效的運算符。 我發現的唯一類似的運算符是prefix: untilOutputFrom
,文檔:
重新發布元素,直到另一個發布者發出一個元素。
所以給出:
fooPublisher.prefix(untilOutputFrom: barPublisher)
不像我想要的那樣行事,因為它僅在barPublisher
發出元素時完成。 但我希望一些操作員在barPublisher
完成時完成。
我在這里錯過了什么嗎? 我想要的操作員實際上是否以其他名稱存在?
我最終自己實現了這個運算符。 實際上,我創建了五個運算符,它們都基於相同的共享( internal
)function。 我為它們添加了一些單元測試,它們似乎工作正常。
// finish when `barPublisher` completes with `.finish`
fooPublisher.prefix(untilFinishFrom: barPublisher)
// finish when `barPublisher` completes with `.output` OR `.finish`
fooPublisher.prefix(untilOutputOrFinishFrom: barPublisher)
// finish when `barPublisher` completes either with `.finish` OR `.failure`
fooPublisher.prefix(untilCompletionFrom: barPublisher)
// finish when `barPublisher` completes either with `.output` OR `.finish` OR `.failure`
fooPublisher.prefix(untilCompletionFrom: barPublisher)
// finish when `barPublisher` completes with `.failure`
// (I'm not so sure how useful this is... might be better to handle with an of
// the operators working with errors)
fooPublisher.prefix(untilFailureFrom: barPublisher)
internal extension Publisher {
func prefix<CompletionTrigger>(
untilEventFrom completionTriggeringPublisher: CompletionTrigger,
completionTriggerOptions: Publishers.CompletionTriggerOptions
) -> AnyPublisher<Output, Failure> where CompletionTrigger: Publisher {
guard completionTriggerOptions != .output else {
// Fallback to Combine's bundled operator
return self.prefix(untilOutputFrom: completionTriggeringPublisher).eraseToAnyPublisher()
}
let completionAsOutputSubject = PassthroughSubject<Void, Never>()
var cancellable: Cancellable? = completionTriggeringPublisher
.sink(
receiveCompletion: { completion in
switch completion {
case .failure:
guard completionTriggerOptions.contains(.failure) else { return }
completionAsOutputSubject.send()
case .finished:
guard completionTriggerOptions.contains(.finish) else { return }
completionAsOutputSubject.send()
}
},
receiveValue: { _ in
guard completionTriggerOptions.contains(.output) else { return }
completionAsOutputSubject.send()
}
)
func cleanUp() {
cancellable = nil
}
return self.prefix(untilOutputFrom: completionAsOutputSubject)
.handleEvents(
receiveCompletion: { _ in cleanUp() },
receiveCancel: {
cancellable?.cancel()
cleanUp()
}
)
.eraseToAnyPublisher()
}
}
// MARK: Publishers + CompletionTriggerOptions
public extension Publishers {
struct CompletionTriggerOptions: OptionSet {
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
}
}
public extension Publishers.CompletionTriggerOptions {
static let output = Self(rawValue: 1 << 0)
static let finish = Self(rawValue: 1 << 1)
static let failure = Self(rawValue: 1 << 2)
static let completion: Self = [.finish, .failure]
static let all: Self = [.output, .finish, .failure]
}
public extension Publisher {
func prefix<CompletionTrigger>(
untilCompletionFrom completionTriggeringPublisher: CompletionTrigger
) -> AnyPublisher<Output, Failure>
where CompletionTrigger: Publisher
{
prefix(untilEventFrom: completionTriggeringPublisher, completionTriggerOptions: .completion)
}
func prefix<CompletionTrigger>(
untilFinishFrom completionTriggeringPublisher: CompletionTrigger
) -> AnyPublisher<Output, Failure>
where CompletionTrigger: Publisher
{
prefix(untilEventFrom: completionTriggeringPublisher, completionTriggerOptions: .finish)
}
func prefix<CompletionTrigger>(
untilFailureFrom completionTriggeringPublisher: CompletionTrigger
) -> AnyPublisher<Output, Failure>
where CompletionTrigger: Publisher
{
prefix(untilEventFrom: completionTriggeringPublisher, completionTriggerOptions: .failure)
}
func prefix<CompletionTrigger>(
untilOutputOrFinishFrom completionTriggeringPublisher: CompletionTrigger
) -> AnyPublisher<Output, Failure>
where CompletionTrigger: Publisher
{
prefix(untilEventFrom: completionTriggeringPublisher, completionTriggerOptions: [.output, .finish])
}
///
func prefix<CompletionTrigger>(
untilOutputOrCompletionFrom completionTriggeringPublisher: CompletionTrigger
) -> AnyPublisher<Output, Failure>
where CompletionTrigger: Publisher
{
prefix(untilEventFrom: completionTriggeringPublisher, completionTriggerOptions: [.output, .completion])
}
}
import Foundation
import XCTest
import Combine
final class PrefixUntilCompletionFromTests: TestCase {
// MARK: Combine's bundled
func test_that_publisher___prefix_untilOutputFrom___completes_when_received_output() {
let finishTriggeringSubject = PassthroughSubject<Void, Never>()
doTestPublisherCompletes(
triggerFinish: {
finishTriggeringSubject.send()
}
) {
return $0.merge(with: $1).prefix(untilOutputFrom: finishTriggeringSubject).eraseToAnyPublisher()
}
}
// MARK: Custom `prefix(until*`
// MARK: `prefix:untilCompletionFrom`
func test_that_publisher___prefix_untilCompletionFrom___completes_when_received_finish() {
let finishTriggeringSubject = PassthroughSubject<Int, Never>()
doTestPublisherCompletes(
triggerFinish: {
finishTriggeringSubject.send(completion: .finished)
}
) {
$0.merge(with: $1).prefix(untilCompletionFrom: finishTriggeringSubject)
}
}
// MARK: `prefix:untilOutputOrFinishFrom`
func test_that_publisher___prefix_untilOutputOrFinishFrom___completes_when_received_finish() {
let finishTriggeringSubject = PassthroughSubject<Int, Never>()
doTestPublisherCompletes(
triggerFinish: {
finishTriggeringSubject.send(completion: .finished)
}
) {
$0.merge(with: $1).prefix(untilOutputOrFinishFrom: finishTriggeringSubject)
}
}
func test_that_publisher___prefix_untilOutputOrFinishFrom___completes_when_received_output() {
let finishTriggeringSubject = PassthroughSubject<Void, Never>()
doTestPublisherCompletes(
triggerFinish: {
finishTriggeringSubject.send()
}
) {
$0.merge(with: $1).prefix(untilOutputOrFinishFrom: finishTriggeringSubject)
}
}
// MARK: `prefix:untilOutputOrCompletionFrom`
func test_that_publisher___prefix_untilOutputOrCompletionFrom___completes_when_received_finish() {
let finishTriggeringSubject = PassthroughSubject<Int, Never>()
doTestPublisherCompletes(
triggerFinish: {
finishTriggeringSubject.send(completion: .finished)
}
) {
$0.merge(with: $1).prefix(untilOutputOrCompletionFrom: finishTriggeringSubject)
}
}
func test_that_publisher___prefix_untilOutputOrCompletionFrom___completes_when_received_output() {
let finishTriggeringSubject = PassthroughSubject<Void, Never>()
doTestPublisherCompletes(
triggerFinish: {
finishTriggeringSubject.send()
}
) {
$0.merge(with: $1).prefix(untilOutputOrCompletionFrom: finishTriggeringSubject)
}
}
func test_that_publisher___prefix_untilOutputOrCompletionFrom___completes_when_received_failure() {
struct ErrorMarker: Swift.Error {}
let finishTriggeringSubject = PassthroughSubject<Void, ErrorMarker>()
doTestPublisherCompletes(
triggerFinish: {
finishTriggeringSubject.send(completion: .failure(ErrorMarker()))
}
) {
$0.merge(with: $1).prefix(untilOutputOrCompletionFrom: finishTriggeringSubject)
}
}
// MARK: `prefix:untilFailureFrom`
func test_that_publisher___prefix_untilFailureFrom___completes_when_received_output() {
struct ErrorMarker: Swift.Error {}
let finishTriggeringSubject = PassthroughSubject<Void, ErrorMarker>()
doTestPublisherCompletes(
triggerFinish: {
finishTriggeringSubject.send(completion: .failure(ErrorMarker()))
}
) {
$0.merge(with: $1).prefix(untilFailureFrom: finishTriggeringSubject)
}
}
// MARK: `prefix:untilEventFrom`
func test_that_publisher___prefix_untilEventFrom___outut_completes_when_received_output() {
let finishTriggeringSubject = PassthroughSubject<Void, Never>()
doTestPublisherCompletes(
triggerFinish: {
finishTriggeringSubject.send()
}
) {
$0.merge(with: $1).prefix(untilEventFrom: finishTriggeringSubject, completionTriggerOptions: [.output])
}
}
func doTestPublisherCompletes(
_ line: UInt = #line,
triggerFinish: () -> Void,
makePublisherToTest: (
_ first: AnyPublisher<Int, Never>,
_ second: AnyPublisher<Int, Never>
) -> AnyPublisher<Int, Never>
) {
let first = PassthroughSubject<Int, Never>()
let second = PassthroughSubject<Int, Never>()
let publisherToTest = makePublisherToTest(
first.eraseToAnyPublisher(),
second.eraseToAnyPublisher()
)
var returnValues = [Int]()
let expectation = XCTestExpectation(description: self.debugDescription)
let cancellable = publisherToTest
.sink(
receiveCompletion: { _ in expectation.fulfill() },
receiveValue: { returnValues.append($0) }
)
first.send(1)
first.send(2)
first.send(completion: .finished)
first.send(3)
second.send(4)
triggerFinish()
second.send(5)
wait(for: [expectation], timeout: 0.1)
// output `3` sent by subject `first` is ignored, since it's sent after it has completed.
// output `5` sent by subject `second` is ignored since it's sent after our `publisherToTest` has completed
XCTAssertEqual(returnValues, [1, 2, 4], line: line)
XCTAssertNotNil(cancellable, line: line)
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.