SwiftUI学习(10)-Combine入门Part IV-Mapping&Filter Operators

SwiftUI学习(10)-Combine入门Part IV-Mapping&Filter Operators

2021, Oct 25    

コニクマル撰写

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

Operator概述

Combine框架里, Publisher包含非常多的Operator。Publisher通过一些方法转换对其发出的事件元素进行操作这个就是OperatorPublisher发布数据,然后Opeartor加工数据并返回一个新的Publisher。下面会介绍下这些操作,以助于理解和使用。

元素映射(Mapping Elements)

map/tryMap

和Swift标准库里的map 操作类似,通过一个闭包函数将来把从pulisher传下来的元素进行转换。tryMapmap操作相同只不过可以在闭包里进行throw异常。

Map操作如下图所示, 将一个每一个元素转换成另外一个:

image-20211030171142668

举个例子:

let input = PassthroughSubject<Int, Never>()
let output = PassthroughSubject<Int, Never>()
let sub = input.map { value in
    return value * 2
}.subscribe(output)
output.sink(receiveValue: { print("Output: ", $0)})

input.send(1)
input.send(2)
input.send(3)
  • 创建一个PassthroughSubject作为Input
  • Input进行map操作,将每个元素乘以2
  • 将Input通过map转换后的publisher附加(attach)到output subject上
  • 订阅通过sink来input和output
  • input 发送数据

输出如下:

Output: 2
Output: 4
Output: 6

mapError

mapErrormap类似,只不过map转换的是publisher发布事件元素,而mapError转换的是publisher是抛出的异常.

举个例子, 当收到数据3后抛出error1,转换error1成error2

enum TestError: Error{
    case error1
    case error2
}

let input = PassthroughSubject<Int, Never>()
let output = PassthroughSubject<Int, TestError>()
let sub = input.tryMap { value in
    if value == 3 {
        throw TestError.error1
    }
    return value * 2
}
.mapError({ _ in
    return TestError.error2
})
.subscribe(output)

output.sink(receiveCompletion: {print("Output Completed:", $0)}, receiveValue: {print("Output Value:", $0)})

input.send(1)
input.send(2)
input.send(3)
  • 自定义error1error2
  • input进行tryMap操作,当value == 3抛出error1
  • 通过mapError将error1转换成error2, 并附加到output上
  • input 发送数据

输出:

Output Value: 2
Output Value: 4
Output Completed: failure(__lldb_expr_111.TestError.error2)

当input发送3的时候,output收到Test.error2错误.

replaceNil

替换Publisher发送下来的事件元素的nil数据。

replaceNil流程如下图:

image-20211030165323997

举个例子:

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

let v = input.replaceNil(with: 5).subscribe(output)

output.sink(receiveCompletion: {print("Output Completed:", $0)}, receiveValue: {print("Output Value:", $0)})

input.send(1)
input.send(nil)
input.send(3)

  • input进行replaceNil操作将nil转换成5,并附加到Output
  • input分别发1, nil, 3

输出:

Output Value: 1
Output Value: 5
Output Value: 3

output收到1, 5, 3

scan

scan有点类似Swift标准库里的reduce操作, 第一个参数是初始值, 第二参数是一个闭包.将Publisher发送下来的事件元素和上一次闭包中返回的值一起传入闭包中并返回新值。

scan的事件流程如下图:

image-20211030164637049

举个例子:


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

let sub = input.scan(0, { $0 + $1}).subscribe(output)

output.sink(receiveValue: {print("Output vaule:", $0)})

input.send(1)
input.send(2)
input.send(3)
  • input进行scan操作
  • input 分别发送 1, 2, 3

输出:

Output vaule: 1
Output vaule: 3
Output vaule: 6

ouptut收到: 1 , 3, 6

元素过滤(Filtering Elements)

filter/tryFilter:

filter和swift标准库里的filter差不多,传递一个闭包返回true表示接受元素,false表示过滤掉元素. tryFilter可以在闭包里抛出异常。

filter流程如下:

image-20211030171202605

举个例子:

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


let sub = input.filter{$0 > 1}.subscribe(output)
output.sink(receiveValue: {print("Output vaule:", $0)})

input.send(1)
input.send(2)
input.send(3)
  • 过滤掉小于等于1事件元素

输出:

Output vaule: 2
Output vaule: 3

output 只有2和3

compactMap/tryCompactMap

compactMap将从Publisher接受到的事件元素进行转换,如果返会nil的值会被过滤掉。tryCompactMap可以在闭包中抛出异常。

compactMap流程如下:

image-20211104213059842

举个例子:

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


let sub = input.compactMap{Int($0)}.subscribe(output)

output.sink(receiveValue: {print("Output vaule:", $0)})

input.send("1")
input.send("a")
input.send("2")

  • input输入字符串通过compactMap转换成为Int

输出:

Output vaule: 1
Output vaule: 2

output 输出1和2, Int(“a”)返会nil被compactMap过滤掉了

removeDuplicates/tryRemoveDuplicates

removeDuplicates会过滤掉和上一次接受事件元素相同的事件元素, 如果需要自定义比较方法可以传入闭包来进行比较。tryRemoveDuplicates可以在传入的比较闭包里抛出异常.

流程如下:

image-20211030173630754

举个例子:

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


let sub = input.removeDuplicates().subscribe(output)

output.sink(receiveValue: {print("Output vaule:", $0)})

input.send(1)
input.send(2)
input.send(2)
input.send(3)
input.send(3)
input.send(1)
  • input连续输入1,2, 2, 3,3,1
  • removeDuplicates操作

输出:

Output vaule: 1
Output vaule: 2
Output vaule: 3
Output vaule: 1

output过滤掉连续相同的输入,第一个和最后一个1并不是连续输入的所以都没有被过滤.

relpaceEmpty

如果Publisher没有发布任何事件就send(completion: .finished),将返回relpaceEmpty的参数内容。

比如:

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

let sub = input.replaceEmpty(with: 42).subscribe(output)
output.sink(receiveValue: {print("Output vaule:", $0)})

input.send(completion: .finished)

输出:

Output vaule: 42

Publisehr直接结束则,ouput收到的是relpaceEmpty替换后的值.

修改代码在finished之前添加一行发送个数据:

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

输出:

Output vaule: 1

Publisher有事件发出, replaceEmpty就不会进行替换了。

replaceError

replaceError可以将上游抛出的异常替换成一个传入的事件元素。replaceError是用正常的内容替换错误, mapError使用异常替换异常.

举个例子:

enum TestError: Error{
    case error
}

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


let sub = input.tryMap({ value -> Int in
    guard value < 2 else{
        throw TestError.error
    }
    return value
})
.replaceError(with: 42)
.subscribe(output)


output.sink(receiveValue: {print("Output vaule:", $0)})

input.send(1)
input.send(2)
input.send(3)

  • tryMap里判断接受到的事件元素大于等于2的时候抛出异常
  • replaceError将异常替换成42

输出:

Output vaule: 1
Output vaule: 42
  • 当input发出2的时候抛出异常,ouput收到替换后的值42
  • 因为发出2的时候抛出了异常, 所以收不到上。