Swift Combine:`append`不需要输出相等吗? 与ignoreOutput 与Future

使用Apple的Combine,我想在第一个发布者foo完成后添加发布者Failure(可以将Never约束为let foo: AnyPublisher<Fruit,Never> = /* actual publisher irrelevant */ let bar: AnyPublisher<Fruit,Never> = /* actual publisher irrelevant */ // A want to do concatenate `bar` to start producing elements // only after `foo` has `finished`,and let's say I only care about the // first element of `foo`. let fooThenBar = foo.first() .ignoreOutput() .append(bar) // Compilation error: `Cannot convert value of type 'AnyPublisher<Fruit,Never>' to expected argument type 'Publishers.IgnoreOutput<Upstream>.Output' (aka 'Never')` )。基本上我想RxJava's andThen

我有这样的东西:

let fooThenBar = foo.first()
    .ignoreOutput()
    .flatMap { _ in Empty<Fruit,Never>() }
    .append(bar) 

我想出了一个解决方案,我认为它可以工作,但是看起来非常难看/过于复杂。

ToggleModel

我在这里想念什么吗?

编辑

在下面的答案中添加了我最初提议的更好版本。非常感谢@RobNapier!

aaawang312 回答:Swift Combine:`append`不需要输出相等吗? 与ignoreOutput 与Future

我认为您只想过滤所有项目,然后附加:

,而不是ignoreOutput
let fooThenBar = foo.first()
    .filter { _ in false }
    .append(bar)

您可能会发现更好地重命名dropAll()

extension Publisher {
    func dropAll() -> Publishers.Filter<Self> { filter { _ in false } }
}

let fooThenBar = foo.first()
    .dropAll()
    .append(bar)

潜在的问题是,ignoreAll()生成的Publisher的输出为Never(从不),这通常是有道理的。但是在这种情况下,您只想获取值而不改变类型,那就是过滤。

,

通过与@RobNapier进行的深入讨论,我们得出的结论是,flatMap { Empty }.append(otherPublisher)解决方案在两个发布者的输出不同时是最佳的。因为我想在first / base /'foo'发布者完成后使用它,所以我在Publishers.IgnoreOutput上写了一个扩展名,结果是:

解决方案

protocol BaseForAndThen {}
extension Publishers.IgnoreOutput: BaseForAndThen {}
extension Combine.Future: BaseForAndThen {}

extension Publisher where Self: BaseForAndThen,Self.Failure == Never {
    func andThen<Then>(_ thenPublisher: Then) -> AnyPublisher<Then.Output,Never> where Then: Publisher,Then.Failure == Failure {
        return
            flatMap { _ in Empty<Then.Output,Never>(completeImmediately: true) } // same as `init()`
                .append(thenPublisher)
                .eraseToAnyPublisher()
    }
}

用法

在我的用例中,我想控制/了解何时基本发布者,因此我的解决方案基于此。

ignoreOutput

由于第二个发布者(在appleSubject以下)在第一个发布者完成之前不会开始生成元素(输出值),因此我使用first()运算符(还有一个{{3} }运算符),使bananaSubject在一次输出后结束。

bananaSubject.first().ignoreOutput().andThen(appleSubject)

Future

Future仅产生一个元素,然后完成。

futureBanana.andThen(applePublisher)

测试

这是完整的单元测试(last()

import XCTest
import Combine

protocol Fruit {
    var price: Int { get }
}

typealias ? = Banana
struct Banana: Fruit {
    let price: Int
}

typealias ? = Apple
struct Apple: Fruit {
    let price: Int
}

final class CombineAppendDifferentOutputTests: XCTestCase {

    override func setUp() {
        super.setUp()
        continueAfterFailure = false
    }

    func testFirst() throws {
        try doTest { bananaPublisher,applePublisher in
            bananaPublisher.first().ignoreOutput().andThen(applePublisher)
        }
    }

    func testFuture() throws {
        var cancellable: Cancellable?
        try doTest { bananaPublisher,applePublisher in

            let futureBanana = Future<?,Never> { promise in
                cancellable = bananaPublisher.sink(
                    receiveCompletion: { _ in },receiveValue: { value in promise(.success(value)) }
                )
            }

            return futureBanana.andThen(applePublisher)
        }

        XCTAssertNotNil(cancellable)
    }

    static var allTests = [
        ("testFirst",testFirst),("testFuture",testFuture),]
}

private extension CombineAppendDifferentOutputTests {

    func doTest(_ line: UInt = #line,_ fooThenBarMethod: (AnyPublisher<?,Never>,AnyPublisher<?,Never>) -> AnyPublisher<?,Never>) throws {
        // GIVEN
        // Two publishers `foo` (?) and `bar` (?)
        let bananaSubject = PassthroughSubject<Banana,Never>()
        let appleSubject = PassthroughSubject<Apple,Never>()

        var outputtedFruits = [Fruit]()
        let expectation = XCTestExpectation(description: self.debugDescription)

        let cancellable = fooThenBarMethod(
            bananaSubject.eraseToAnyPublisher(),appleSubject.eraseToAnyPublisher()
            )
            .sink(
                receiveCompletion: { _ in expectation.fulfill() },receiveValue: { outputtedFruits.append($0 as Fruit) }
        )

        // WHEN
        // a send apples and bananas to the respective subjects and a `finish` completion to `appleSubject` (`bar`)
        appleSubject.send(?(price: 1))
        bananaSubject.send(?(price: 2))
        appleSubject.send(?(price: 3))
        bananaSubject.send(?(price: 4))
        appleSubject.send(?(price: 5))

        appleSubject.send(completion: .finished)

        wait(for: [expectation],timeout: 0.1)

        // THEN
        // A: I the output contains no banana (since the bananaSubject publisher's output is ignored)
        // and
        // B: Exactly two apples,more specifically the two last,since when the first Apple (with price 1) is sent,we have not yet received the first (needed and triggering) banana.
        let expectedFruitCount = 2
        XCTAssertEqual(outputtedFruits.count,expectedFruitCount,line: line)
        XCTAssertTrue(outputtedFruits.allSatisfy({ $0 is ? }),line: line)
        let apples = outputtedFruits.compactMap { $0 as? ? }
        XCTAssertEqual(apples.count,line: line)
        let firstApple = try XCTUnwrap(apples.first)
        let lastApple = try XCTUnwrap(apples.last)
        XCTAssertEqual(firstApple.price,3,line: line)
        XCTAssertEqual(lastApple.price,5,line: line)
        XCTAssertNotNil(cancellable,line: line)
    }
}

,

只要您使用 .ignoreOutput(),就可以安全地将“丑陋的”.flatMap { _ in Empty<Fruit,Never>() } 替换为简单的 .map { Fruit?.none! },它无论如何都不会被调用,只会更改输出类型。

本文链接:https://www.f2er.com/3156943.html

大家都在问