Combine | (II) Combine Operator:转化 |过滤 |组合

操作符(Operator)是 Combine 生态的重要组成部分,使咱们以有意义的办法操作上游 Publisher 宣布的值。咱们将了解 Combine 的大多数 Operator:转化(Transforming)、过滤(Filtering)、组合(Combining)、时刻操作(Time Manipulation)和序列(Sequence)。

转化操作符(Transforming Operators)

Combine 的每个 Operator 都回来一个 Publisher。 一般来说,Publisher 接纳上游事情,对其进行某些操作,将操作后的事情向下流发送给顾客。

收集值

collect()

func collect() -> Publishers.Collect<Self>
func collect(_ count: Int) -> Publishers.CollectByCount<Self>

一个 Publisher 能够宣布单个值或值的调集。collect() Operator 是将来自 Publisher 的单值流通化为单个数组的办法。以下弹珠图图描绘了 collect() 缓冲单值流,直到上游的 Publisher 完结,然后它向下流宣布该数组:

Combine | (II) Combine Operator:转换 |过滤 |组合

新建 Playground 并增加以下代码:

import Combine
import Foundation
func example(_ desc: String, _ action:() -> Void) {
    print("--- \(desc) ---")
    action()
}
var subscriptions = Set<AnyCancellable>()
example("Collect") {
    ["A", "B", "C", "D", "E"]
        .publisher
        .sink { print($0) } 
            receiveValue: { print($0) }
        .store(in: &subscriptions)
}

运转 Playground,咱们会看到每个值都出现在单独的行上,终究跟着一个完结事情:

--- Collect ---
A
B
C
D
E
finished

现在在调用 sink 之前运用 collect():

example("Collect") {
    ["A", "B", "C", "D", "E"]
        .publisher
        .collect()
        .sink { print($0) } 
            receiveValue: { print($0) }
        .store(in: &subscriptions)
}

再次运转 Playground,咱们将看到接纳到单个数组值,然后是完结事情:

--- Collect ---
["A", "B", "C", "D", "E"]
finished

但在运用 collect() 和其他不需求指定 count 这类 Operator 时要当心,,由于它们不会在上游完结之前宣布值,所以它们可能将运用大量内存来存储接纳到的值。

collect() Operator 有一些变体。例如咱们能够指定收集必定数量的值,将上游切割成多个“批次”。咱们能够将上述实例代码中的 .collect() 替换为:

.collect(2)

再次运转 Playground,咱们将看到以下输出:

--- Collect ---
["A", "B"]
["C", "D"]
["E"]
finished

由于上游 Publisher 在 collect() 填满其缓冲区之前就完结了,所以它将剩余的内容作为一个数组发送,即值 E 也作为一个数组。

映射值

map(_:)

func map<T>(_ transform: @escaping (Self.Output) -> T) -> Publishers.Map<Self, T>

咱们通常期望以某种办法转化值。 map 的工作办法与 Swift 的规范 map 相似。在弹珠图中,咱们将每个值乘以 2,此 Operator 在上游发布值后当即发布值:

Combine | (II) Combine Operator:转换 |过滤 |组合

将这个新示例增加到咱们的 Playground:

example("map") {
    let formatter = NumberFormatter()
    formatter.numberStyle = .spellOut
    [9, 99, 999, 9999].publisher
        .map { 
            formatter.string(for: NSNumber(integerLiteral: $0)) ?? ""
        }
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

在这里,咱们创立一个 formatter 来拼出每个数字。创立 Int Publisher 后,运用 map,闭包中获取上游值并回来字符串。运转 Playground,咱们将看到以下输出:

--- map ---
九
九十九
九百九十九
九千九百九十九

map Operator 的 keyPath 有三个,它们能够运用 keyPath 映射到值的一个、两个或三个特点。 他们的签名如下:

func map<T>(
  _ keyPath: KeyPath<Self.Output, T>
) -> Publishers.MapKeyPath<Self, T>
func map<T0, T1>(
    _ keyPath0: KeyPath<Self.Output, T0>,
    _ keyPath1: KeyPath<Self.Output, T1>
) -> Publishers.MapKeyPath2<Self, T0, T1>
func map<T0, T1, T2>(
    _ keyPath0: KeyPath<Self.Output, T0>,
    _ keyPath1: KeyPath<Self.Output, T1>,
    _ keyPath2: KeyPath<Self.Output, T2>
) -> Publishers.MapKeyPath3<Self, T0, T1, T2>

将以下示例增加到 Playground:

example("map & keyPath") {
    struct Persion {
        var name: String
        var height: Float
        var weight: Float
    }
    let publisher = PassthroughSubject<Persion, Never>()
    publisher
        .map(\.name, \.height, \.weight)
        .map { "\($0)'s height is \($1) cm and weight is \($2) kg." }
        .sink { print($0) }
        .store(in: &subscriptions)
    publisher.send(.init(name: "A", height: 180, weight: 80))
    publisher.send(.init(name: "B", height: 170, weight: 60))
}

咱们定义了 Persion 结构,并订阅 publisher,将相关字段转化 String 并输出。运转 Playground,输出将如下所示:

--- map & keyPath ---
A's height is 180.0 cm and weight is 80.0 kg.
B's height is 170.0 cm and weight is 60.0 kg.

tryMap(_:)

func mapError<E>(_ transform: @escaping (Self.Failure) -> E) -> Publishers.MapError<Self, E> where E : Error

包括 map 在内的几个 Operator 都有一个带有 try 前缀的办法,该前缀的办法承受一个可抛出过错的闭包。办法中抛出的过错,将鄙人流被接纳:

example("tryMap") {
    Just("Path")
        .tryMap { try FileManager.default.contentsOfDirectory(atPath: $0) }
        .sink { print($0) } 
            receiveValue: { print($0) }
        .store(in: &subscriptions)
}

咱们创立了一个 String Publisher,运用 tryMap 尝试获取文件内容,接纳并打印出完结事情或值。运转 Playground,咱们将看到过错事情:

--- tryMap ---
failure(Error Domain=NSCocoaErrorDomain Code=260 "文件夹“Path”不存在。" UserInfo={NSFilePath=Path, NSUserStringVariant=(
    Folder
), NSUnderlyingError=0x15aec8790 {Error Domain=NSOSStatusErrorDomain Code=-43 "fnfErr: File not found"}})

扁平化 Publisher

flatMap(maxPublishers:_:)

func flatMap<T, P>(
    maxPublishers: Subscribers.Demand = .unlimited,
    _ transform: @escaping (Self.Output) -> P
) -> Publishers.FlatMap<P, Self> where T == P.Output, P : Publisher, Self.Failure == P.Failure

flatMap Operator 将多个上游 Publisher 展平为一个下流 Publisher。flatMap 回来的 Publisher 与接纳的上游 Publisher 通常是不同的。在 Combine 中 flatMap 的一个常见用例,是当咱们想要将一个 Publisher 宣布的元素,传递给一个回来 Publisher 的办法,并终究订阅第二个 Publisher 宣布的元素。在 Playground 中增加新代码:

example("flatMap") {
    [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]
        .publisher
        .collect()
        .flatMap { codes in
            Just(codes.map { String(UnicodeScalar($0)) }.joined())
        }
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

咱们将 ASCII 编码的 Int 数组转化为 publisher,将这些值 collect 为一个 Int 数组。运用 flatMap 转化为一个 Just Publisher,该 Publisher 将 Int 数组转变为一个 String 类型的值。接着订阅 Just Publisher,打印该值。终究输出如下:

--- flatMap ---
Hello, World!

上面的示例中,咱们运用了办法的第二个入参,供给了一个 transform 闭包,还有一个 maxPublishers 参数。

flatMap 将一切接纳到的 Publisher 的输出扁平化为一个 Publisher,由于它会缓冲多个 Publisher,来更新它鄙人流宣布的单个 Publisher,这可能会引起内存问题。而 maxPublishers 为咱们做了限制:

Combine | (II) Combine Operator:转换 |过滤 |组合

在图中,flatMap 接纳三个发布者:p0、p1 和 p2。 flatMap 从 p1 和 p2 宣布值,但疏忽 p3,由于 maxPublishers 设置为 2。上述逻辑用代码描绘为:

example("flatMap & maxPublishers") {
    let publisher = PassthroughSubject<AnyPublisher<String, Never>, Never>()
    publisher
        .flatMap(maxPublishers: .max(2)) { $0 }
        .sink { print($0) } 
            receiveValue: { print($0) }
        .store(in: &subscriptions)
    let p0 = CurrentValueSubject<String, Never>("p0 - 0")
    let p1 = CurrentValueSubject<String, Never>("p1 - 0")
    let p2 = CurrentValueSubject<String, Never>("p2 - 0")
    publisher.send(p0.eraseToAnyPublisher())
    publisher.send(p1.eraseToAnyPublisher())
    publisher.send(p2.eraseToAnyPublisher())
    p0.send("p0 - 1")
    p1.send("p1 - 1")
    p2.send("p2 - 1")
    p0.send("p0 - 2")
    p1.send("p1 - 2")
    p2.send("p2 - 2")
}

增加到 Playground 并运转,控制台将输出:

--- flatMap & maxPublishers ---
p0 - 0
p1 - 0
p0 - 1
p1 - 1
p0 - 2
p1 - 2

替换和刺进值

replaceNil(with:)

func replaceNil<T>(with output: T) -> Publishers.Sequence<[Publishers.Sequence<Elements, Failure>.Output], Failure> where Elements.Element == T?

当咱们期望始终供给非 Nil 值时能够运用该 Operator。如下图所示,replaceNil 接纳可选值并将 nil 值替换为咱们指定的值:

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下代码增加到 Playground:

example("replaceNil") {
    [Optional(1), nil, Optional(3)].publisher
        .eraseToAnyPublisher()
        .replaceNil(with: 2)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

咱们从可选 Int 数组创立 Publisher。运用 replaceNil(with:) 将来自上游发布者的 nil 值替换为 2:

--- replaceNil ---
1
2
3

上述代码运用 eraseToAnyPublisher() 调整了编译器的类型揣度,不然 Optional<String> 不会被打开。

replaceEmpty(with:)

func replaceNil<T>(with output: T) -> Publishers.Map<Self, T> where Self.Output == T?

假如 Publisher 完结了但没有宣布一个值,咱们能够运用 replaceEmpty(with:) Operator 来刺进一个值。鄙人面的弹珠图中,Publisher 完结时没有宣布任何内容,此刻 replaceEmpty(with:) 操作符刺进一个值并将其发布到下流:

Combine | (II) Combine Operator:转换 |过滤 |组合

在 Playground 中增加这个新示例以检查它的实践效果:

example("replaceEmpty") {
    let empty = Empty<Int, Never>()
    empty
        .replaceEmpty(with: 1)
        .sink(receiveCompletion: { print($0) },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
}

咱们创运用 Empty Publisher 当即宣布 .finished 事情。而 .replaceEmpty(with: 1) 将会在完结事情产生时,刺进值 1:

--- replaceEmpty ---
1
finished

值的增量输出

scan(_:_:)

func scan<T>(
    _ initialResult: T,
    _ nextPartialResult: @escaping (T, Self.Output) -> T
) -> Publishers.Scan<Self, T>

scan 将上次闭包回来的值,和上游 Publisher 宣布的最新值供给给闭包。鄙人面的弹珠图中,scan 从 0 开端,当它从 Publisher 接纳每个值时,它将其加到先前存储的值中,然后宣布成果:

Combine | (II) Combine Operator:转换 |过滤 |组合

上述图用代码描绘为:

example("scan") {
    [1, 2, 3]
        .publisher
        .scan(0) { latest, current in
            latest + current
        }
        .sink(receiveValue: { print($0)})
        .store(in: &subscriptions)
}

运转 Playground 将输出:

--- scan ---
1
3
6

过滤操作符(Filtering Operators)

当咱们想要限制 Publisher 宣布的值或事情,只运用其间需求的一部分时,能够运用运用一组特殊的 Operator 来做到这一点:过滤操作符。

过滤值

filter(_:)

func filter(_ isIncluded: @escaping (Self.Output) -> Bool) -> Publishers.Filter<Self>

咱们将了解根底过滤——消费 Publisher 的值,并有条件地决议将其间的哪些传递给下流的顾客。filter 采用回来 Bool 的闭包,只传递与条件匹配的值:

Combine | (II) Combine Operator:转换 |过滤 |组合

将这个新示例增加到 Playground:

example("filter") {
    (1...10)
        .publisher
        .filter { $0.isMultiple(of: 3) }
        .sink(receiveValue: { print("\($0) is a multiple of 3!")})
        .store(in: &subscriptions)
}

在上面的示例中,咱们创立一个新的 Publisher,它将宣布从 1 到 10。运用 filter,只答应经过是三的倍数的数字:

--- filter ---
3 is a multiple of 3!
6 is a multiple of 3!
9 is a multiple of 3!

removeDuplicates()

func removeDuplicates() -> Publishers.RemoveDuplicates<Self>

很多时候咱们可能会遇到 Publisher 宣布相同的值,咱们可能期望疏忽这些值:Combine 为该使命供给了 Operator:

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下示例增加到咱们的 Playground:

example("removeDuplicates") {
    ["a", "b", "b", "b", "c", "d", "d", "e", "f", "f"]
        .publisher
        .removeDuplicates()
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

咱们过滤了数组中重复的字符串,运转咱们的 Playground 并检查控制台:

--- removeDuplicates ---
a
b
c
d
e
f

移除 nil

compactMap(_:)

func compactMap<T>(_ transform: @escaping (Self.Output) -> T?) -> Publishers.CompactMap<Self, T>

Swift 规范库中的一个十分闻名的办法:compactMap—— Combine 有一个同名的 Operator:

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下内容增加到咱们的 Playground:

example("compactMap") {
    ["a", "1", "2", "b", "3", "4", "c"]
        .publisher
        .compactMap { Int($0) }
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

咱们创立一个 String Publisher,运用 compactMap 尝试从每个单独的字符串初始化一个 Int。 假如失利,则回来 nil。 这些 nil 值会被 compactMap 过滤。在咱们的 Playground 中运转上面的示例:

--- compactMap ---
1
2
3
4

疏忽值

ignoreOutput()

func ignoreOutput() -> Publishers.IgnoreOutput<Self>

有时咱们只关心 Publisher 完结事情的发送,而疏忽值。咱们能够运用 ignoreOutput

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下代码增加到咱们的 Playground:

example("ignoreOutput") {
    (1...100)
        .publisher
        .ignoreOutput()
        .sink(receiveCompletion: { print($0) },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
}

咱们创立一个 Publisher,从 1 到 100 宣布 100 个值,增加 ignoreOutput() Operator,它会疏忽一切值,只向顾客宣布完结事情:

--- ignoreOutput ---
finished

寻找值

first() first(where:)

func first() -> Publishers.First<Self>
func first(where predicate: @escaping (Self.Output) -> Bool) -> Publishers.FirstWhere<Self>

last() last(where:)

func last() -> Publishers.Last<Self>
func last(where predicate: @escaping (Self.Output) -> Bool) -> Publishers.LastWhere<Self>

相同起源于 Swift 规范库的 Operator:first(where:)last(where:)。 咱们能够运用它们分别仅查找和宣布与供给闭包条件匹配的第一个或终究一个值:

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下代码增加到咱们的 Playground:

example("first(where:)") {
     (1...5)
        .publisher
        .first(where: { $0 % 2 == 0 })
        .sink(receiveCompletion: { print($0) },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
}
example("last(where:)") {
     (1...5)
        .publisher
        .last(where: { $0 % 2 == 0 })
        .sink(receiveCompletion: { print($0) },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
}

咱们分别查找了宣布的第一个和终究一个偶数值:

--- first(where:) ---
2
finished
--- last(where:) ---
4
finished

一旦 first(where:) 找到匹配的值,它就会经过 Subscription 发送撤销,上游中止宣布值。能够增加 print() 验证:

example("first(where:)") {
     (1...5)
        .publisher
        .print()
        .first(where: { $0 % 2 == 0 })
        .sink(receiveCompletion: { print($0) },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
}

控制台将输出:

--- first(where:) ---
receive subscription: (1...5)
request unlimited
receive value: (1)
receive value: (2)
receive cancel
2
finished

first(where:) 是 lazy的,而 last(where:) 则是贪婪的,需求等 Publisher 发送完结事情后,才干确定所需的值。

删去值

dropFirst(_:)

func dropFirst(_ count: Int = 1) -> Publishers.Drop<Self>

dropFirst Operator 需求 count 参数(假如省掉,则默认为 1),疏忽 Publisher 宣布的前 count 个值:

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下代码增加到 Playground:

example("dropFirst") {
    (1...5)
        .publisher
        .dropFirst(3)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

咱们创立一个 Publisher 宣布 1 到 5 之间的数字。运用 dropFirst(3) 删去前三个值:

--- dropFirst ---
4
5

drop(while:)

func drop(while predicate: @escaping (Self.Output) -> Bool) -> Publishers.DropWhile<Self>

drop(while:) 是一个十分有用的变体,它用条件闭包疏忽 Publisher 宣布的任何值,直到第一次满意该条件后,值就能够经过:

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下代码增加到 Playground:

example("drop(while:)") {
    let numbers = (1...5)
        .publisher
        .drop(while: { $0 % 4 != 0 })
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

咱们创立了一个宣布 1 到 5 之间数字的 Publisher,运用 drop(while:) 等候能够被 4 整除的第一个值:

--- drop(while:) ---
4
5

drop(while:) 的闭包在满意条件后将不再执行,修改上述代码:

example("drop(while:)") {
    let numbers = (1...5)
        .publisher
        .drop(while: {
            print("Checking \($0)")
            return $0 % 4 != 0
        })
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

控制台将输出:

--- drop(while:) ---
Checking 1
Checking 2
Checking 3
Checking 4
4
5

drop(untilOutputFrom:)

func drop<P>(untilOutputFrom publisher: P) -> Publishers.DropUntilOutput<Self, P> where P : Publisher, Self.Failure == P.Failure

drop(untilOutputFrom:) 会跳过 Publisher 宣布的任何值,直到作为参数的 Publisher 开端宣布值:

Combine | (II) Combine Operator:转换 |过滤 |组合

咱们用代码来描绘这个过程:

example("drop(untilOutputFrom:)") {
    let isReady = PassthroughSubject<Void, Never>()
    let publisher = PassthroughSubject<Int, Never>()
    publisher
        .drop(untilOutputFrom: isReady)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
    (1...5).forEach { num in
        publisher.send(num)
        if num == 3 {
            isReady.send()
        }
    }
}

咱们创立了两个 PassthroughSubject。运用 drop(untilOutputFrom: isReady) 疏忽 publisher 的值,直到 isReady 宣布至少一个值,在 publisher 宣布 3 后, isReady 宣布值:

--- drop(untilOutputFrom:) ---
4
5

限制值

prefix(_:)

func prefix(_ maxLength: Int) -> Publishers.Output<Self>

prefix(while:)

func prefix(while predicate: @escaping (Self.Output) -> Bool) -> Publishers.PrefixWhile<Self>

prefix(untilOutputFrom:)

func prefix<P>(untilOutputFrom publisher: P) -> Publishers.PrefixUntilOutput<Self, P> where P : Publisher

与删去值的一系列 Operator 相反,prefix 系列将只取所供给的数量的值,prefix 系列是 lazy 的,获取所需的值后即发送完结事情:

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下代码增加到 Playground:

example("prefix") {
    (1...5)
        .publisher
        .prefix(2)
        .sink(receiveCompletion: { print($0) },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
}
example("prefix(while:)") {
    (1...5)
        .publisher
        .prefix(while: { $0 < 3 })
        .sink(receiveCompletion: { print($0) },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
}
example("prefix(untilOutputFrom:)") {
    let isReady = PassthroughSubject<Void, Never>()
    let publisher = PassthroughSubject<Int, Never>()
    publisher
        .prefix(untilOutputFrom: isReady)
        .sink(receiveCompletion: { print($0) },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
    (1...5).forEach { num in
        publisher.send(num)
        if num == 2 {
            isReady.send()
        }
    }
}

运转 Playground,控制台将输出:

--- prefix ---
1
2
finished
--- prefix(while:) ---
1
2
finished
--- prefix(untilOutputFrom:) ---
1
2
finished

组合操作符(Combining Operators)

这组 Operator 答应咱们组合不同 Publisher 宣布的值或事情,在代码中创立有意义的数据组合。

前置值

prepend(_:)

func prepend(_ elements: Self.Output...) -> Publishers.Concatenate<Publishers.Sequence<[Self.Output], Self.Failure>, Self>

... 语法指可变参数列表,这意味着该办法入参能够为恣意数量的与原始 Publisher 的 Output 类型相同的值:

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下代码增加到咱们的 Playground:

example("prepend") {
    [4, 5]
        .publisher
        .prepend(1, 2, 3)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

在上面的代码中,咱们创立一个发布数字 4、5 的 Publisher,运用 prepend 在 Publisher 的值之前增加数字 1 、2、3。运转 Playground,咱们会在调试控制台中看到以下内容:

--- prepend ---
1
2
3
4
5

咱们能够轻松增加多个前置:

example("prepend") {
    [4, 5]
        .publisher
        .prepend(1, 2, 3)
        .prepend(-1, 0)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

将会输出:

--- prepend ---
-1
1
2
3
4
5

prepend(Sequence)

func prepend<S>(_ elements: S) -> Publishers.Concatenate<Publishers.Sequence<S, Self.Failure>, Self> where S : Sequence, Self.Output == S.Element

prepend 的这种变体将任何契合 Sequence 的目标作为输入。 例如 Array 或 Set:

将以下代码增加到你的 Playground:

example("prepend(Sequence)") {
    [7, 8].publisher
        .prepend([5, 6])
        .prepend(Set(3...4))
        .prepend(stride(from: 0, to: 3, by: 1))
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

在此代码中,咱们创立了一个发布数字 7、8 的 Publisher。三次链接 prepend(Sequence) 到 Publisher, 一次从 Array 中增加值,第2次从 Set 中增加值、第三次从契合 SequenceStrideable 中增加值:

--- prepend(Sequence) ---
0
1
2
3
4
5
6
7
8

Set 是无序的,意味着上例中的两个值能够是 3 和 4,也能够是 4 和 5。

prepend(Publisher)

func prepend<P>(_ publisher: P) -> Publishers.Concatenate<P, Self> where P : Publisher, Self.Failure == P.Failure, Self.Output == P.Output

前两个 Operator 将值列表增加到现有 Publisher。 假如咱们有两个不同的 Publisher,并且想将它们的值合在一起,咱们能够运用 prepend(Publisher) 在 Publisher 的值之前增加第二个 Publisher 宣布的值:

example("prepend(Publisher)") {
    let publisher1 = [3, 4].publisher
    let publisher2 = [1, 2].publisher
    publisher1
        .prepend(publisher2)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

运转 Playground,控制台将输出:

--- prepend(Publisher) ---
1
2
3
4

该 Operator 还有一些细节,咱们继续增加以下内容增加到 Playground:

example("prepend(Publisher) V2") {
    let publisher1 = [3, 4].publisher
    let publisher2 = PassthroughSubject<Int, Never>()
    publisher1
        .prepend(publisher2)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
    publisher2.send(1)
    publisher2.send(2)
}

咱们创立了两个 Publisher。 第一个宣布值 3 和 4,而第二个是动态承受值的 PassthroughSubject。在 publisher1 的值之前增加 publisher2 的值,接着 publisher2 发送值 1 和 2。运转 Playground:

--- prepend(Publisher) V2 ---
1
2

为什么 publisher2 只宣布两个值? 由于 publisher2 宣布了值,但没有宣布完结事情,被前置的 Publisher 有必要宣布完结事情后,Combine 才知道需求进行切换 Publisher:

example("prepend(Publisher) V2") {
    let publisher1 = [3, 4].publisher
    let publisher2 = PassthroughSubject<Int, Never>()
    publisher1
        .prepend(publisher2)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
    publisher2.send(1)
    publisher2.send(2)
    publisher2.send(completion: .finished)
}

现在,控制台才会输出:

--- prepend(Publisher) V2 ---
1
2
3
4

追加值

append(_:)

func append(_ elements: Self.Output...) -> Publishers.Concatenate<Self, Publishers.Sequence<[Self.Output], Self.Failure>>

append 的工作办法与 prepend 相似:它也承受一个 Output 类型的可变参数列表,然后在原始 Publisher 完结事情后,附加在终究的完结事情前:

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下代码增加到 Playground:

example("append") {
    [1, 2, 3]
        .publisher
        .append(4, 5)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

在上面的代码中,创立一个发布 1、2、3 的 Publisher,运用 append,追加 4 和 5:

--- append ---
1
2
3
4
5

append 的工作办法与咱们期望的彻底相同,等候上游完结事情后,然后再增加自己的值。这意味着上游有必要宣布完结事情,不然 append 永远不会产生。

append(Sequence)

func append<S>(_ elements: S) -> Publishers.Concatenate<Self, Publishers.Sequence<S, Self.Failure>> where S : Sequence, Self.Output == S.Element

prepend 相似,append 的变体能够追加契合 Sequence 的目标:

example("append(Sequence)") {
    [1, 2, 3]
        .publisher
        .append([4, 5])
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

运转 Playground 将输出:

--- append(Sequence) ---
1
2
3
4
5

append(Publisher)

func append<P>(_ publisher: P) -> Publishers.Concatenate<Self, P> where P : Publisher, Self.Failure == P.Failure, Self.Output == P.Output

prepend 相似,append 的变体能够追加另一个 Publisher:

example("append(Publisher)") {
    let publisher1 = [1, 2].publisher
    let publisher2 = [3, 4].publisher
    publisher1
        .append(publisher2)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

运转 Playground 将输出:

--- append(Publisher) ---
1
2
3
4

相同需求留意,只有前一个 Publisher 发布完结事情,才干追加另一个 Publisher。

其他高级组合操作符

switchToLatest()

func switchToLatest() -> Publishers.SwitchToLatest<Self.Output, Self>

switchToLatest() 使咱们能够在撤销对旧 Publisher 订阅的同时,切换到对新 Publisher 的订阅:

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下代码增加到咱们的 Playground,描绘上述弹珠图:

example("switchToLatest") {
    let publishers = PassthroughSubject<PassthroughSubject<Int, Never>, Never>()
    let numbers1 = PassthroughSubject<Int, Never>()
    let numbers2 = PassthroughSubject<Int, Never>()
    let numbers3 = PassthroughSubject<Int, Never>()
    publishers
        .switchToLatest()
        .sink(
            receiveCompletion: { print($0) },
            receiveValue: { print($0) }
        )
        .store(in: &subscriptions)
    publishers.send(numbers1)
    numbers1.send(1)
    numbers1.send(2)
    publishers.send(numbers2)
    numbers1.send(3)
    numbers1.send(completion: .finished)
    numbers2.send(4)
    numbers2.send(5)
    publishers.send(numbers3)
    numbers2.send(6)
    numbers2.send(completion: .finished)
    numbers3.send(7)
    numbers3.send(8)
    numbers3.send(9)
    numbers3.send(completion: .finished)
    publishers.send(completion: .finished)
}

咱们创立一个 PassthroughSubject 宣布其他 PassthroughSubject 值,接着创立三个 PassthroughSubject。在 publishers 运用 switchToLatest。现在,每次咱们经过 publishers 发送 numbers1 numbers2 numbers3 时,咱们都会切换到新的 Publisher 并撤销之前的 Subscription。终究,咱们的控制台将打印:

--- switchToLatest ---
1
2
4
5
7
8
9
finished

在实践使用中,例如用户点击触发网络请求的按钮,再未拿到请求成果时,用户再次点击按钮,触发第二个网络请求。此刻,能够运用 switchToLatest() 来只运用新的请求成果。

merge(with:)

func merge(with other: Self) -> Publishers.MergeMany<Self>

此 Operator 将兼并来自同一类型的不同 Publisher 的值,如下所示:

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下代码增加到咱们的 Playground:

example("merge(with:)") {
    let publisher1 = PassthroughSubject<Int, Never>()
    let publisher2 = PassthroughSubject<Int, Never>()
    publisher1
        .merge(with: publisher2)
        .sink(receiveCompletion: { print($0) },
            receiveValue: { print($0) })
        .store(in: &subscriptions)
    publisher1.send(1)
    publisher1.send(2)
    publisher2.send(3)
    publisher1.send(4)
    publisher1.send(completion: .finished)
    publisher2.send(5)
    publisher2.send(completion: .finished)
}

在上述代码中,咱们创立来了两个 PassthroughSubjects 。将 publisher1publisher2 mergemerge 供给了重载,可让咱们兼并多达八个不同的 Publisher。将 1 和 2 增加到 publisher1,然后将 3 增加到 publisher2,然后再次将 4 增加到 publisher1 并发送完结事情,终究将 5 增加到 publisher2 并发送完结事情。

运转咱们的 Playground:

--- merge(with:) ---
1
2
3
4
5
finished

merge 将会等候一切 Publisher 宣布完结事情后才会宣布终究的完结事情。

combineLatest(_:_:)

func combineLatest<P, T>(
    _ other: P,
    _ transform: @escaping (Self.Output, P.Output) -> T
) -> Publishers.Map<Publishers.CombineLatest<Self, P>, T> where P : Publisher, Self.Failure == P.Failure

combineLatest 是另一个答应咱们组合不同 Publisher 的操作符。 它还答应咱们组合不同值类型的Publisher。它在一切 Publisher 宣布值时,宣布一个包含一切 Publisher 的最新值的元组。

所以会有一个问题:Publisher 和传递给 combineLatest 的每个 Publisher 都有必要至少宣布一个值,然后 combineLatest 本身才会宣布值:

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下代码增加到咱们的 Playground:

example("combineLatest") {
    let publisher1 = PassthroughSubject<Int, Never>()
    let publisher2 = PassthroughSubject<String, Never>()
    publisher1
        .combineLatest(publisher2)
        .sink(
            receiveCompletion: { print($0) },
            receiveValue: { print("P1: \($0), P2: \($1)") }
        )
        .store(in: &subscriptions)
    publisher1.send(1)
    publisher1.send(2)
    publisher2.send("a")
    publisher2.send("b")
    publisher1.send(3)
        publisher1.send(completion: .finished)
    publisher2.send("c")
    publisher2.send(completion: .finished)
}

咱们创立两个 PassthroughSubjects。将 publisher2 的与 publisher1 combineLatest 起来。 咱们能够运用不同的 combineLatest 的重载组合最多四个不同的 Publisher。将 1 和 2 发送到 publisher1,然后将“a”和“b”发送到 publisher2,然后将 3 发送到 publisher1 并发送完结事情,终究将“c”发送到 publisher2 并发送完结事情。

终究,控制台将输出:

--- combineLatest ---
P1: 2, P2: a
P1: 2, P2: b
P1: 3, P2: b
P1: 3, P2: c
finished

zip(_:)

func zip<P>(_ other: P) -> Publishers.Zip<Self, P> where P : Publisher, Self.Failure == P.Failure

该 Operator 的工作办法与规范库相似,宣布不同 Publisher 的相同索引的成对值的元组。即它等候每个 Publisher 宣布一个值,然后在一切 Publisher 在当时索引处宣布一个值后,宣布一个元组:

Combine | (II) Combine Operator:转换 |过滤 |组合

将以下代码增加到咱们的 Playground:

example("zip") {
    let publisher1 = PassthroughSubject<Int, Never>()
    let publisher2 = PassthroughSubject<String, Never>()
    publisher1
        .zip(publisher2)
        .sink(receiveCompletion: { print($0) },
            receiveValue: { print("P1: \($0), P2: \($1)") })
        .store(in: &subscriptions)
    publisher1.send(1)
    publisher1.send(2)
    publisher2.send("a")
    publisher2.send("b")
    publisher1.send(3)
    publisher1.send(completion: .finished)
    publisher2.send("c")
    publisher2.send("d")
    publisher2.send(completion: .finished)
}

咱们创立两个 PassthroughSubject。将 publisher1 与 publisher2 zip。将 1 和 2 发送到 publisher1,然后将“a”和“b”发送到 publisher2,然后将 3 再次发送到 publisher1 并发送完结事情,终究将“c”和“d”发送到 publisher2 并发送完结事情。

终究一次运转咱们的 Playground 并检查调试控制台:

--- zip ---
P1: 1, P2: a
P1: 2, P2: b
P1: 3, P2: c
finished

内容参考

  • Combine | Apple Developer Documentation;
  • 来自Kodeco的书本《Combine: Asynchronous Programming with Swift》;
  • 对上述Kodeco书本的汉语自译版《Combine: Asynchronous Programming with Swift》收拾与弥补。