使用Combine组合先前的值

问题描述 投票:0回答:3

如何使用

ReactiveSwift/ReactiveCocoa
框架重写
Combine
代码?我附上了文档中
combinePrevious
的含义的屏幕截图。

let producer = SignalProducer<Int, Never>([1, 2, 3]).combinePrevious(0)
producer.startWithValues { value in
    print(value) // print: (0, 1), (1, 2), (2, 3)
}

swift reactive-cocoa combine reactive-swift
3个回答
42
投票

这些是我想出的自定义运算符(称为

withPrevious
)。有两种重载,一种重载的初始值是
nil
,另一种是您提供初始值的重载,这样您就不必处理可选值。

extension Publisher {

    /// Includes the current element as well as the previous element from the upstream publisher in a tuple where the previous element is optional.
    /// The first time the upstream publisher emits an element, the previous element will be `nil`.
    ///
    ///     let range = (1...5)
    ///     cancellable = range.publisher
    ///         .withPrevious()
    ///         .sink { print ("(\($0.previous), \($0.current))", terminator: " ") }
    ///      // Prints: "(nil, 1) (Optional(1), 2) (Optional(2), 3) (Optional(3), 4) (Optional(4), 5) ".
    ///
    /// - Returns: A publisher of a tuple of the previous and current elements from the upstream publisher.
    func withPrevious() -> AnyPublisher<(previous: Output?, current: Output), Failure> {
        scan(Optional<(Output?, Output)>.none) { ($0?.1, $1) }
            .compactMap { $0 }
            .eraseToAnyPublisher()
    }

    /// Includes the current element as well as the previous element from the upstream publisher in a tuple where the previous element is not optional.
    /// The first time the upstream publisher emits an element, the previous element will be the `initialPreviousValue`.
    ///
    ///     let range = (1...5)
    ///     cancellable = range.publisher
    ///         .withPrevious(0)
    ///         .sink { print ("(\($0.previous), \($0.current))", terminator: " ") }
    ///      // Prints: "(0, 1) (1, 2) (2, 3) (3, 4) (4, 5) ".
    ///
    /// - Parameter initialPreviousValue: The initial value to use as the "previous" value when the upstream publisher emits for the first time.
    /// - Returns: A publisher of a tuple of the previous and current elements from the upstream publisher.
    func withPrevious(_ initialPreviousValue: Output) -> AnyPublisher<(previous: Output, current: Output), Failure> {
        scan((initialPreviousValue, initialPreviousValue)) { ($0.1, $1) }.eraseToAnyPublisher()
    }
}

存在第三个重载,其中

Output
本身是可选的,这导致
previous
Output??
类型的双重可选(@richy 在评论中指出的场景。)这第三个重载使输出变平,以便两个
 previous
current
是可选的(
(previous: Output?, current: Output?)
(previous: Output??, current: Output?)
相反。)

extension Publisher {
    func withPrevious<T>() -> AnyPublisher<(previous: Output, current: Output), Failure> where Output == Optional<T> {
        scan(Optional<(Output, Output)>.none) { ($0?.1, $1) }
            .compactMap { $0 }
            .eraseToAnyPublisher()
    }
}

差异很微妙(超出了一般限制),所以我特别指出:

Optional<(Output, Output)>.none
vs
Optional<(Output?, Output)>.none


25
投票

我对

ReactiveSwift/ReactiveCocoa
不太熟悉,但是根据你的描述,你可以使用
.scan
,这似乎是比
combinePrevious
更通用的功能。

它需要一个初始结果 - 您可以将其制作成一个元组 - 以及一个包含存储值和当前值的闭包,并返回一个新的存储值 - 在您的情况下,是一个带有

(previous, current)
:

的元组
let producer = [1,2,3].publisher
                      .scan((0,0)) { ($0.1, $1) }

producer.sink { 
   print($0) 
}

7
投票

Cocoacasts 有一个很好的例子:

https://cocoacasts.com/combine-essentials-combining-publishers-with-combine-zip-operator

zip 运算符可用于创建发布者,该发布者发出发布者发出的前一个元素和当前元素。我们将同一个发布者传递给 Publishers.Zip 结构的初始化程序两次,但将 dropFirst 运算符应用于第二个发布者。这仅仅意味着第二个发布者不会发出原始发布者的第一个元素。

import Combine

let numbers = [1, 2, 3, 4, 5].publisher

Publishers.Zip(numbers, numbers.dropFirst(1))

用途:

import Combine

let numbers = [1, 2, 3, 4, 5].publisher

Publishers.Zip(numbers, numbers.dropFirst(1))
    .sink(receiveValue: { values in
        print(values)
    })
    
// (1, 2)
// (2, 3)
// (3, 4)
// (4, 5)
© www.soinside.com 2019 - 2024. All rights reserved.