在 Combine 框架中,并没有一个名为 flatMapLatest 的直接操作符,这是 ReactiveX(例如 RxSwift)中的一个术语,用于描述一个特定的 flatMap 行为:每当源发布者发出一个新的值,flatMapLatest 会切换到新的发布者,并取消订阅之前的发布者。

在 Combine 中,要实现 flatMapLatest 的行为,可以使用 map 和 switchToLatest 操作符结合起来。switchToLatest 是 Combine 提供的操作符,用于只接收最新发布者发出的值,忽略旧的发布者的值。

以下是一个示例,演示如何模拟 flatMapLatest 的行为:

var cancellables = Set<AnyCancellable>()
// 返回 Publisher 的函数
func publisher(for value: Int) -> AnyPublisher<Int, Never> {
    // 这里我们创建一个将 Int 值封装在 Publisher 中的 Publisher
        return Just(value)
            .delay(for: .seconds(Double(value)), scheduler: RunLoop.main)
            .eraseToAnyPublisher()
}

// 源 Publisher,可以是任何类型的 Publisher
let sourcePublisher = PassthroughSubject<Int, Never>()

// 使用 map 来处理每个发送的值,并确保在任何时刻只有一个内部 Publisher 被订阅
sourcePublisher
    .map { value -> AnyPublisher<Int, Never>  in
        return publisher(for: value)
    }
    .switchToLatest() // 只订阅最新的内部 Publisher
    .sink(receiveCompletion: { completion in
        print("Completed with: \(completion)")
    }, receiveValue: { value in
        print("Received value: \(value)")
    }).store(in: &cancellables)
sourcePublisher.send(1)
sourcePublisher.send(2) // 1 的结果会被忽略,因为 2 是最新的
sourcePublisher.send(3) // 2 的结果会被忽略,因为 3 是最新的

在这个例子中,sourcePublisher 是一个 PassthroughSubject,它可以发送值。每当它发送一个新的值,map 操作符会根据这个值创建一个新的 Publisher。然后,switchToLatest 只会订阅最新的 Publisher,之前的订阅会被取消,从而模拟出 flatMapLatest 的行为。

使用 map 和 switchToLatest 的组合是 Combine 中实现 flatMapLatest 的方式,我们也可以写一个flatMapLatest扩展操作方法。

extension Publisher {
    func flatMapLatest<T: Publisher>(_ transform: @escaping (Output) -> T) -> Publishers.SwitchToLatest<T, Publishers.Map<Self, T>> where T.Failure == Failure {
        return map(transform).switchToLatest()
    }
}

这样的话可以把之前的代码简化成:

// 使用 map 来处理每个发送的值,并确保在任何时刻只有一个内部 Publisher 被订阅
sourcePublisher
    .flatMapLatest { value -> AnyPublisher<Int, Never>  in
        return publisher(for: value)
    }
    .sink(receiveCompletion: { completion in
        print("Completed with: \(completion)")
    }, receiveValue: { value in
        print("Received value: \(value)")
    }).store(in: &cancellables)