SwiftUI学习(11)-Combine入门Part V-Reducing Elements&Applying Mathematical&Applying Macthing Criteria Operators

SwiftUI学习(11)-Combine入门Part V-Reducing Elements&Applying Mathematical&Applying Macthing Criteria Operators

2021, Nov 04    

コニクマル撰写

本文使用目前最新版本的 xcode 13 + macOS Monterey + iOS15

元素减少(Reducing Elements)

collect

collect操作是将Publisher发布的事件元素收集起来成为一个数组再发布出去。

collect有三个重载版本:

1.collect()

Publisher发布的元素收集起来,直到收到Publisher发出finish,再发布出一个数组出去。流程图如下:

image-20211104201157660

举个例子:


var store = Set<AnyCancellable>()

let input = PassthroughSubject<Int, Never>()
let output = PassthroughSubject<[Int], Never>()

input.collect().subscribe(output)

output.sink(receiveCompletion: { print("Completed", $0)}, receiveValue: { print("Received Value", $0)})
    .store(in: &store)

input.send(1)
input.send(2)
input.send(3)
input.send(completion: .finished)

  • 将input通过collect()后给output
  • input 分别发布 1, 2, 3和finish

输出

Received Value [1, 2, 3]
Completed finished

如果移除input.send(completion: .finished),则没有输出。

自定义一个Error

enum TestError: Error{
    case error
}

inputoutput的错误类型改成自定义TextError并把input.send(completion: .finished)改成input.send(completion: .failure(TestError.error))

输出:

Completed  failure(__lldb_expr_4.TestError.error)

collect()只会在收到finish的时候才发送事件元素。

2.collect(Int)

第一个参数是Int类型,当收到元素数量等于传入参数或者Publisher发出finish, 将这些元素组成一个数组发布出去。。流程图如下:

image-20211104202927831

举个例子:

let input = PassthroughSubject<Int, Never>()
let output = PassthroughSubject<[Int], Never>()

input.collect(3).subscribe(output)


output.sink(receiveCompletion: { print("Completed ", $0)}, receiveValue: { print("Received Value ", $0)})
    .store(in: &store)

input.send(1)
input.send(2)
input.send(3)
input.send(4)
input.send(5)
input.send(6)
input.send(7)
input.send(completion: .finished)	

输出:

Received Value  [1, 2, 3]
Received Value  [4, 5, 6]
Received Value  [7]
Completed  finished

如果把inputoutput错误类型改成上文TestError并且将input.send(completion: .finished) 改成input.send(completion: .failure(TestError.error))

输出:

Received Value  [1, 2, 3]
Received Value  [4, 5, 6]
Completed  failure(__lldb_expr_12.TestError.error)

collect收到finish后会将没有达到数量的元素组成数组发布出去,收到failure则不会。

3.collect(_:options:)

第一个参数是个枚举类型:

  • byTime(Scheduler, Scheduler.SchedulerTimeType.Stride)

    表示每间隔一定时间, 将此期间收到的事件元素组成一个数组发布出去。

    • 第一个参数:执行的线程,Scheduler是一个协议表示执行的线程。
    • 第二个参数: 这个就是间隔。

    流程图:

    image-20211104210449202

    使用DispatchQueue.main.asyncAfter来延时发送数据, 如下:

      
    var store = Set<AnyCancellable>()
    let input = PassthroughSubject<Int, Never>()
    let output = PassthroughSubject<[Int], Never>()
      
    input.collect(.byTime(RunLoop.main, .seconds(1))).subscribe(output).store(in: &store)
      
      
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
        input.send(1)
    }
      
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
        input.send(2)
    }
      
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.8) {
        input.send(3)
    }
      
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.1) {
        input.send(4)
    }
      
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.5) {
        input.send(5)
    }
      
    output.sink(receiveCompletion: { print("Completed ", $0)}, receiveValue: { print("Received Value ", $0)})
        .store(in: &store)
    

    输出:

    Received Value  [1, 2, 3]
    Received Value  [4, 5]
    
  • byTimeOrCount(Scheduler, Scheduler.SchedulerTimeType.Stride, Int)

    表示每间隔一定时间或者收到的事件元素数量达到一定数量后, 将此期间收到的事件元素或者达到数量的所有元素组成一个数组发布出去。

    • 第一个参数:执行的线程,Scheduler是一个协议表示执行的线程。
    • 第二个参数: 这个就是间隔。
    • 第三个参数: 达到的元素数量。

    流程如下图:

    image-20211104211223460

使用DispatchQueue.main.asyncAfter来延时发送数据, 如下:

var store = Set<AnyCancellable>()
let input = PassthroughSubject<Int, Never>()
let output = PassthroughSubject<[Int], Never>()

input.collect(.byTimeOrCount(RunLoop.main, .seconds(1), 3)).subscribe(output).store(in: &store)


DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
    input.send(1)
}

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
    input.send(2)
    input.send(3)
    input.send(4)
}

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.8) {
    input.send(5)
}

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.1) {
    input.send(6)
}

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.5) {
    input.send(7)
}

output.sink(receiveCompletion: { print("Completed ", $0)}, receiveValue: { print("Received Value ", $0)})
    .store(in: &store)

输出:

Received Value  [1, 2, 3]
Received Value  [4, 5]
Received Value  [6, 7]

当时间到或者数量到的时候抛出数组。

第二参数是个Options, 根据传入的Scheduler来定义的参数, 有些Scheduler事不存在options的比如Runloop就没有。

IgnoreOutput

这个就是忽略所有收到的数据,当收到finish或者failure后发出事件。

reduce

Publisher发送下来的事件元素和上一次闭包中返回的值一起传入闭包中,直到收到finish事件后将计算结果发布出去。和scan的操作差不多, 区别是scan每次收到事件元素后通过闭包处理的完的新值会发布出去,而且reduce只会在收到finish后把最后计算完成的的值发布出去。

流程如下:

image-20211104212724693

举个例子:


var store = Set<AnyCancellable>()
let input = PassthroughSubject<Int, TestError>()
let output = PassthroughSubject<Int, TestError>()

input.reduce(0, {$0 + $1}).subscribe(output).store(in: &store)

output.sink(receiveCompletion: { print("Completed ", $0)}, receiveValue: { print("Received Value ", $0)})
    .store(in: &store)

input.send(1)
input.send(2)
input.send(3)
input.send(completion: .finished)

输出:

Received Value  6
Completed  finished

数学运算(Applying Mathematical)

count

统计Publisher发布的事件元素数量,当收到finish事件后发布收到的事件元素数量。

比如统计收到的元素数量:

var store = Set<AnyCancellable>()

let input = PassthroughSubject<Int, TestError>()
let output = PassthroughSubject<Int, TestError>()

input.count().subscribe(output).store(in: &store)
output.sink(receiveCompletion: {print("Complete", $0)}, receiveValue: {print("Received Value", $0)}).store(in: &store)

input.send(1)
input.send(2)
input.send(3)
input.send(4)
input.send(5)
input.send(completion: .finished)

输出:

Received Value 5
Complete finished

max/min

统计Publisher发布的事件元素中的最大/小值,当收到finish事件后发布收到的事件的最大/小值。

max/min无参数版本只能支持继承 Comparable协议的Output类型

如果Output数据类型没有继承Comparable可以使用带有闭包的版本,通过闭包比较元素的大小。

比如统计Input发送元素的最小值:


var store = Set<AnyCancellable>()

let input = PassthroughSubject<Int, TestError>()
let output = PassthroughSubject<Int, TestError>()

input.min().subscribe(output).store(in: &store)
output.sink(receiveCompletion: {print("Complete", $0)}, receiveValue: {print("Received Value", $0)}).store(in: &store)

input.send(1)
input.send(2)
input.send(3)
input.send(4)
input.send(5)
input.send(completion: .finished)

输出:

Received Value 1
Complete finished

tryMax/Min(by: (Output, Output) throws -> Bool)

max/min(by: (Output, Output)->Bool)一样,只不过闭包里可以跑出错误。

规则匹配(Applying Matching Criteria)

contains/tryContains

判断Publisher发布的事件元素中是否包含指定的元素或者规则,当收到finish事件后发布true/false表示是否包含。

无参数版本只能支持继承 ``Equatable协议的Output`类型

如果Output数据类型没有继承Equatable可以使用带有闭包的版本,通过闭包来判断元素是否符合规则。tryContains可以在传入的闭包里添加throw异常。

比如判断是否有大于3的元素:

var store = Set<AnyCancellable>()

let input = PassthroughSubject<Int, TestError>()
let output = PassthroughSubject<Bool, TestError>()

input.contains(where: {$0 > 3}).subscribe(output).store(in: &store)
output.sink(receiveCompletion: {print("Complete", $0)}, receiveValue: {print("Received Value", $0)}).store(in: &store)

input.send(1)
input.send(2)
input.send(3)
input.send(4)
input.send(5)
input.send(completion: .finished)

输出:

Received Value true
Complete finished

allSatisfy/tryAllSatisfy

判读Publisher发出的元素是不是全部满足传入闭包的里的规则,如果当收到finish后,之前闭包返回的值全是true则发送出true。tryAllSatisfy可以在闭包里跑出异常。

比如判读发送的数据是不是都是2的倍数:

var store = Set<AnyCancellable>()

let input = PassthroughSubject<Int, TestError>()

let output = PassthroughSubject<Bool, TestError>()

input.allSatisfy({$0 % 2  == 0}).subscribe(output).store(in: &store)
output.sink(receiveCompletion: {print("Complete", $0)}, receiveValue: {print("Received Value", $0)}).store(in: &store)

input.send(2)
input.send(4)
input.send(8)
input.send(6)
input.send(10)
input.send(completion: .finished)

输出:

Received Value true
Complete finished