什么叫sequence operations呢?我们都知道,pipline就像水管一样,数据在管道中流动,因此数据是有顺序的,那么这个sequence就是顺序的意思。比如,如果你只想获取水管中的最后一个数据,或者第一个数据,或者中间的某个数据,类似于这样的操作就叫做顺序操作。
当然,我们可以把全部数据收集完成后再做处理也是可以的,但这不符合现代编程的思想,我需要什么?就给我什么,才是最合理的编程方式。
first
first
的一个独特之处在于当收到第一个数据后,就会结束该pipline。如上图所示,当收到数据1后,pipline就结束了,也就没有必要再接受后边的数据。
pipline结束的标志是publisher发送一个.finished
事件。
_ = [1, 2, 3, 4]
.publisher
.first()
.sink(receiveCompletion: { completion in
print("结束了")
switch completion {
case .finished:
print("完成")
case .failure(let error):
print("错误:\(error.localizedDescription)")
}
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
上图是firstWhere的示意图,很多时候,first
太不灵活了,只能返回第一个收到的数据,按照声明式编程思想,只要给它传入一个判断条件的闭包,它就会变的十分灵活。
这里的标题虽然是fitstWhere,但使用的时候并不是.firstWhere
,而是像下边这样使用:
.first { value -> Bool in
value > 2
}
重点是,我们要理解fisrtWhere的含义,它的核心思想是返回第一个符合某个条件的数据。
比如计算第一个数学,语文,英语成绩之和超过300的学生等等,类似于这样的例子,为了方便的演示这些Operator的功能,我们使用了[].publisher
这样的数据发送的模式,真实的开发中,这些数据很可能都是不定期的发送。
用到闭包的地方必然有try,因此tryFirstWhere
的代码如下:
.tryFirst { value -> Bool in
if value == 2 {
throw MyError.customError
}
value > 2
}
last
last
跟first
刚好相反,它会返回pipline中的最后一条数据。需要等待publisher发出.finished
事件后再返回数据。
由于它跟first
是相对的关系,这里就不做更加详细的解释了。它同样有lastWhere
和tryLastWhere
的概念,具体代码如下:
.last { value -> Bool in
value > 2
}
.tryLast { value -> Bool in
if value == 2 {
throw MyError.customError
}
value > 2
}
drop
drop
是一个很有意思的Operator,他有3种用法:
dropUntilOutput
dropWhile
tryDropWhile
最有意思的是dropUntilOutput
,**它允许我们用另一个publisher来触发当前的publisher。**什么意思呢?可以把另一个publisher想象成管道中的一个开关,只有开关打开后,水才回流向下一个地方。
这个比喻有一点问题,现实中,如果开关没打开,水会一直积累起来,然而在使用了drop
的pipline中,数据并不会积累起来,而是放弃掉以前的数据,这也正是drop的含义所在。
先看下边的代码:
/// 发送数据
let publisher = PassthroughSubject<String, Never>()
/// 触发
let triggerPublisher = PassthroughSubject<Int, Never>()
_ = publisher
.drop(untilOutputFrom: triggerPublisher)
.sink(receiveCompletion: { completion in
print("结束了")
switch completion {
case .finished:
print("完成")
case .failure(let error):
print("错误:\(error.localizedDescription)")
}
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
publisher.send("你好")
triggerPublisher.send(1)
publisher.send("张三")
publisher.send(completion: Subscribers.Completion.finished)
这次我们没有使用[].publisher
来发送数据,而是使用PassthroughSubject
,它能够让我们手动控制发送数据的时机。
我们用publihser来发送数据,也就是主管道,用triggerPublisher作为开关,注意,triggerPublisher可能会发送3种数据:
Int
, 正常数据.finished
,完成事件.failure
,错误Error
在上边的代码中,我们通过triggerPublisher.send(1)
发送了一个正常的数据,看下打印:
someValue: 张三
结束了
完成
可以看出,在triggerPublisher触发之前,没有打印出数据。打印结果完全符合预期。
我们在看看第二种情况:
/// 发送数据
let publisher = PassthroughSubject<String, Never>()
/// 触发
let triggerPublisher = PassthroughSubject<Int, Never>()
publisher
.drop(untilOutputFrom: triggerPublisher)
.sink(receiveCompletion: { completion in
print("结束了")
switch completion {
case .finished:
print("完成")
case .failure(let error):
print("错误:\(error.localizedDescription)")
}
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
.store(in: &cancellables)
publisher.send("你好")
triggerPublisher.send(completion: Subscribers.Completion.finished)
publisher.send("张三")
打印结果如下:
结束了
完成
可以看出,开关triggerPublisher的作用还是很大的,它不仅可以作为数据的开关,还可以结束主管道。当triggerPublisher发送了一个.finished
事件后,publisher结束了,因为在上边的代码中,我们并没有调用下边的代码来技术主管道:
publisher.send(completion: Subscribers.Completion.finished)
我们再看看最后一种情况:
enum MyCustomError: Error {
case customError
}
/// 发送数据
let publisher = PassthroughSubject<String, Error>()
/// 触发
let triggerPublisher = PassthroughSubject<Int, Error>()
publisher
.drop(untilOutputFrom: triggerPublisher)
.sink(receiveCompletion: { completion in
print("结束了")
switch completion {
case .finished:
print("完成")
case .failure(let error):
print("错误:\(error.localizedDescription)")
}
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
.store(in: &cancellables)
publisher.send("你好")
triggerPublisher.send(completion: Subscribers.Completion.failure(MyCustomError.customError))
publisher.send("张三")
打印结果:
结束了
错误:The operation couldn’t be completed. (MCMarbleDiagramSwiftUITests.MCMarbleDiagramSwiftUITests.(unknown context at $1068f51d0).(unknown context at $1068f5224).MyCustomError error 0.)
基本上同.finished
差不多,直接结束了pipline。
总结一下,当triggerPublisher发送正常数据后,则开启publisher,当triggerPublisher发送.finished
或者.failure
后,则关闭publisher。
对drop的第二种使用方法是dropWhile,它的核心思想是声明drop的方式,基于此,我们可以自由定义条件。
仔细观察上图可以发现,dropWhile
过滤掉的是一开始闭包返回true的数据,这同样体现了drop的含义,当闭包第一次返回false,就不会再继续过滤数据,不管数据是否符合闭包的条件,都会流向下一个阶段。
总结一下,dropWhile更像是一个触发开关,当它第一次返回false的时候,就是打开开关之时。
_ = [-40, -10, 0, 10, 0, 2, -30]
.publisher
.drop { $0 <= 0 }
.sink { print($0) }
dropWhile
用到了闭包,同样存在一个tryDropWhile
,它允许闭包中抛出异常,在这里就不做多余解释了。
prepend
prepend是前置的意思,我们很自然地想到它所表达的一个思想是两个publisher的顺序关系。用代码表示,应该是这样的:
Publishers.Concatenate(prefix: firstPublisher, suffix: secondPublisher)
按照字面意思,firstPublisher的后边应该连接着secondPublisher,数据依次流过这两个publisher。实际情况却不是这样的,它们的关系更像是下边这张图片:
可以看出,一开始sink接收firstPublisher中流出的数据,secondPublisher的开关是关闭的,只有当firstPublisher发送了.finished
事件后,secondPublisher的开关就会打开,数据从secondPublisher流向sink。
任何时候,任何publisher遇到Error,都会结束该pipline。
let firstPublisher = PassthroughSubject<String, Never>()
let secondPublisher = PassthroughSubject<String, Never>()
_ = secondPublisher
.prepend(firstPublisher)
.sink(receiveCompletion: { completion in
print("结束了")
switch completion {
case .finished:
print("完成")
case .failure(let error):
print("错误:\(error.localizedDescription)")
}
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
firstPublisher.send("你好")
firstPublisher.send(completion: Subscribers.Completion.finished)
secondPublisher.send("张三")
secondPublisher.send(completion: Subscribers.Completion.finished)
prepend
还有两个便利构造器,能够非常方便的输出数据序列,比如firstPublisher如果是一个序列的话,代码大概是这样的:
let firstPublisher = ["1", "2", "3"].publisher
let secondPublisher = PassthroughSubject<String, Never>()
secondPublisher
.prepend(firstPublisher)
.sink(receiveCompletion: { completion in
...
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
.store(in: &cancellables)
secondPublisher.send("4")
打印结果如下:
someValue: 1
someValue: 2
someValue: 3
someValue: 4
我们可以把上边的代码简化为:
let secondPublisher = PassthroughSubject<String, Never>()
secondPublisher
.prepend(["1", "2", "3"])
.sink(receiveCompletion: { completion in
...
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
.store(in: &cancellables)
secondPublisher.send("4")
当然,还可以只传递一个值:
secondPublisher
.prepend("1")
dropFirst
正如上图所示,.dropFirst
的主要目的是过滤掉pipline中的第一个数据,这也是其默认写法,它还有一种扩展写法,我们可以指定过滤数据的个数,看下图:
上图已经非常明确的表达了.dropFirst(2)
的意义,当我们指定了某个数值后,就会过滤掉pipline最开始这个数值个数的数据。
那么这有什么用呢?由于pipline中的数据具有不确定性,我们不知道数据何时流出,使用dropFirst
就能很轻易的实现起始数据过滤,具体的使用场景还需大家自己开发。
_ = [1, 2, 3, 4]
.publisher
.dropFirst(2)
.sink(receiveCompletion: { completion in
print("结束了")
switch completion {
case .finished:
print("完成")
case .failure(let error):
print("错误:\(error.localizedDescription)")
}
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
prefix
prefix是前缀的意思,当某个事物成为另一个事物的prefix时,通常情况下,这个事物有限制作用。
因此prefix的本质就是为某个publisher增加限制条件。
它有以下4种用法:
prefix(untilOutputFrom:)
prefix(_ maxLength:)
prefix(while:)
tryPrefix(while:)
prefix(untilOutputFrom:)
非常的有意思,它有点类似于前边讲过的prepend
,firstPublisher.prepend(secondPublisher)
表示当first publisher发送.finished
事件后,second publisher开始发送数据,而firstPublisher.prefix(untilOutputFrom:secondPublisher)
则表示当second publisher发送第一个数据后,frist publisher就会立刻终止该pipline。
这说明了prefix(untilOutputFrom:)
可以实现通过second publisher来为整个pipline设置开关,这在某些开发场景下会非常有用。
let firstPublisher = PassthroughSubject<Int, Never>()
let secondPublisher = PassthroughSubject<Int, Never>()
firstPublisher
.prefix(untilOutputFrom: secondPublisher)
.sink(receiveCompletion: { completion in
print("结束了")
switch completion {
case .finished:
print("完成")
case .failure(let error):
print("错误:\(error.localizedDescription)")
}
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
.store(in: &cancellables)
firstPublisher.send(1)
firstPublisher.send(2)
secondPublisher.send(3)
firstPublisher.send(4)
打印结果:
someValue: 1
someValue: 2
结束了
完成
既然知道了prefix的本质是为publisher增加限制条件,那么prefix(_ maxLength:)
是为了限制publisher的最大输出数据的个数。也就是说,它可以控制publisher最多可以输出多少数据。
我们通过代码验证一下:
[1, 2, 3, 4]
.publisher
.prefix(3)
.sink(receiveCompletion: { completion in
print("结束了")
switch completion {
case .finished:
print("完成")
case .failure(let error):
print("错误:\(error.localizedDescription)")
}
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
.store(in: &cancellables)
打印结果如下:
someValue: 1
someValue: 2
someValue: 3
结束了
完成
通过上边的代码,我们可以发现,.prefix(3)
会在收到3个数据后,就立马结束了pipline,并不是过滤其他的数据,因此,它可以称之为publisher的限制条件。
如上图所示,prefix(while:)
也是publisher的限制条件,while是一个闭包参数,它接收上游输出的数据,返回Bool值,当该闭包第一次返回false的时候,该pipline就会立马结束。
[1, 2, 3, 4]
.publisher
.prefix { value -> Bool in
value <= 2
}
.sink(receiveCompletion: { completion in
print("结束了")
switch completion {
case .finished:
print("完成")
case .failure(let error):
print("错误:\(error.localizedDescription)")
}
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
.store(in: &cancellables)
打印结果:
someValue: 1
someValue: 2
结束了
完成
tryPrefix(while:)
是prefix(while:)
的一个扩展,它允许闭包中throw异常,我们就不详细讲解了。
总结一下,当我们想利用某个条件来控制是否结束该pipeline时,就可以考虑使用prefix。这个条件可以是一个publisher,也可以是一个闭包,也可以是一个上限值。
output
output
能够让我们精确地控制哪个位置上的数据可以输出,它也是对一个数据序列的操作方法。从上图可以看出,4其实是一个index, 就跟数组中的索引一样,从0开始,实际上取得值是序列中的第5个值。
_ = [1, 2, 3, 4, 5, 6, 7]
.publisher
.output(at: 4)
.sink(receiveCompletion: { completion in
...
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
使用.output(at index: Int)
只能输出一个数据,与之相对应的是,还可以使用.output(in range: RangeExpression)
输出一定范围内的数据。如下图所示:
_ = [1, 2, 3, 4, 5, 6, 7]
.publisher
.output(in: (2...4))
.sink(receiveCompletion: { completion in
...
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
output比较适用于获取数据流中间某个范围内的数据。