簡體   English   中英

Swift 組合:`前綴(直到CompletionFrom)`?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM