组合操作符(Combing Operators)
startWith Operator
如果想在发送原观察者序列元素之前,发送指定序列的元素(emit a specified sequence of items before beginning to emit the items from the source Observable)。那么可以使用startWith操作符,使用startWith将操作符之后将首先发送startWith中的元素,执行完成之后再执行发送原序列元素。注意:当多个startWith使用链式,链接在一起,最后面的startWith操作符中的元素将首先发送,即类似导航入栈,先进后出,后进先出。
example(of: "startWith") {
let disposeBag = DisposeBag()
Observable.of(2,3,4)
.startWith(1)
.subscribe(onNext: {print($0)})
.addDisposableTo(disposeBag)
/*
--- Example of: startWith ---
1
2
3
4
*/
}
多个startWith组合使用,后面的元素将插入到观察者序列的最前面
example(of: "startWith") {
let disposeBag = DisposeBag()
Observable.of(2,3,4)
.startWith(1)
.startWith(5,6)
.startWith(0)
.subscribe(onNext: {print($0)})
.addDisposableTo(disposeBag)
/*
--- Example of: startWith ---
0
5
6
1
2
3
4
*/
}
concat Operator
concat操作符将连接多个输出观察者序列,其功能相当于一个单一观察者序列执行操作。并且由第一个观察者序列开始发送元素,等第一个观察者序列元素发送完成,执行第二个观察者序列,如果有多个观察者序列一次类推。
1 拼接集合
example(of: "concat-collection") {
let disposeBag = DisposeBag()
//创建两个观察者序列
let first = Observable.of(1,2,3)
let second = Observable.of(4,5,6)
//1
Observable.concat([first,second])
.subscribe(onNext: {print($0)})
.addDisposableTo(disposeBag)
/*
--- Example of: concat ---
1
2
3
4
5
6
*/
}
注释1部分,以集合的方式,连接两个观察者序列,发送观察者序列元素的顺序就是序列在集合中的顺序。简单描述:观察者序列创建了一个订阅者,首先对集合中的第一个观察者序列进行订阅,当第一个序列发送元素完成,那么这时移动到第二个观察者序列,继续发送第二个观察者序列中的元素。如果有多个观察者序列,这个过程将重复执行,一直到集合中所有的观察者序列执行完毕。如果序列执行的过程中发生了错误,将发送错误事件并终止。
2 拼接序列(sequences)
example(of: "concat-sequence") {
let disposeBag = DisposeBag()
let germanCities = Observable.of("Berlin", "Münich", "Frankfurt")
let spanishCities = Observable.of("Madrid", "Barcelona", "Valencia")
//使用concat连接第二个观察者序列,这里执行顺序是:等待源观察者序列执行完成,然后执行第二个观察者序列
germanCities.concat(spanishCities)
.subscribe(onNext: {print($0)})
.addDisposableTo(disposeBag)
/*
--- Example of: concat-sequence ---
Berlin
Münich
Frankfurt
Madrid
Barcelona
Valencia
*/
}
3 使用concat模仿startWith
example(of: "concat imitate startWith") {
let disposeBag = DisposeBag()
//注意:只能连接相同类型的序列
let numbers = Observable.of(2,3,4)
Observable.just(1)
.concat(numbers)
.subscribe(onNext: {print($0)})
.addDisposableTo(disposeBag)
/*
--- Example of: concat imitate startWith ---
1
2
3
4
*/
}
4 直接使用concat
example(of: "concat") {
let disposeBag = DisposeBag()
//1
let subject1 = BehaviorSubject(value: "1")
let subject2 = BehaviorSubject(value: "2")
//2
let variable = Variable(subject1)
//3
variable.asObservable()
.concat()
.subscribe(onNext: {print($0)})
.addDisposableTo(disposeBag)
//4
subject1.onNext("3")
subject1.onNext("4")
//5
variable.value = subject2
//6
subject1.onNext("still send elements")
//7
subject2.onNext("I would be ignored")
subject2.onNext("6")
//8
subject1.onCompleted()
subject2.onNext("7")
subject1.onNext("8")
}
执行结果:
--- Example of: concat —
1
3
4
still can send elements
6
7
代码分析:
1:使用BehaviorSubject创建两个subject
2:使用subject1初始化Variable,得到variable实例对象
3:将variable转换为观察者序列并执行concat操作符,并进行订阅variable。
4:观察者序列subject1发送消息,这里会打印输出1,3,4
5:改变variable的值为subject2
6:虽然改变了variable的值,但是subject1仍然可以发送消息
7: subject2发送两个消息,两个消息目前都无效
8:标记subject1发送完成消息,即观察者序列发送消息完成,这时subject2发送消息有效,subject1将不在能够发送消息
注意:
1:必须发送subject1完成消息(subject1.onCompleted()),否则subject2发送的所有消息都无效。因为使用了concat操作符,所以订阅者还是订阅subject1,所以需要发送subject1完成消息。然后订阅者会订阅subject2。并将获取subject2最近、最新的消息。
2: “I would be ignored”被忽略了,这是因为BehaviorSubject只发送序列中最新的消息。如果注释subject2.onNext("6")该行,那么将打印I would be ignored,如果两个部分都注释,那么发送subject2初始值消息。
merge Operator
组合原观察者序列到单一的新观察者序列,新序列中的每一个被发送的元素顺序跟原序列中发送的顺序一样(Combines elements from source Observable sequences into a single new Observable sequence, and will emit each element as it is emitted by each source Observable sequence.)
example(of: "merge") {
//错误枚举
enum MyError: Error{
case error
}
let disposeBag = DisposeBag()
//创建两个PublishSubject实例对象
let subject1 = PublishSubject<String>()
let subject2 = PublishSubject<String>()
//创建源观察者序列并进行合并,订阅消息
Observable.of(subject1,subject2)
.merge()
.subscribe(onNext: {print($0)}, onError: {print($0)})
.addDisposableTo(disposeBag)
//subject1发送消息
subject1.onNext("a")
subject1.onNext("b")
//subject2发送消息
subject2.onNext("1")
subject2.onNext("2")
//混合发送
subject1.onNext("ab")
subject2.onNext("3")
//发生错误,后续元素不再发送
subject1.onError(MyError.error)
subject1.onNext("4")
subject2.onNext("5")
}
执行结果:
--- Example of: merge ---
a
b
1
2
ab
3
error
combineLatest Operator
当两个或者多个观察者序列中任意一个观察者序列发送一个消息,combineLatest操作符号将组合每一个观察者序列最新的元素,通过执行具体的闭包,并发送最终结果。
example(of: "combineLatest") {
let disposeBag = DisposeBag()
//创建两个subject
let left = PublishSubject<String>()
let right = PublishSubject<String>()
//创建观察者序列组合两个源观察者序列的最新值
Observable.combineLatest(left, right) {"\($0)\($1)"}//执行任意想执行的操作
.subscribe(onNext: {print($0)})
.addDisposableTo(disposeBag)
//发送消息
left.onNext("1")
right.onNext("A")
left.onNext("2")
right.onNext("B")
right.onNext("C")
right.onNext("D")
left.onNext("3")
left.onNext("4")
left.onNext("5")
}
执行结果:
--- Example of: combineLatest ---
1A
2A
2B
2C
2D
3D
4D
5D
注意:
1:在闭包(resultSelector)之中,我们可以执行任意想要的操作,根据需求进行设置。
2:注意: 一直到所以的观察者序列都发送了一个消息(即元素),这时才会有响应,发送第一个事件,否则订阅者收不到消息。而且在每一个观察者序列都发送了一个消息之后,再发送消息,每发送一次新消息,闭包将接收每个观察者序列中的最新值,执行闭包组合操作,得到最终结果,并发送给订阅者。
常用情形:
1:登录界面,如果用户名和密码没有输入并满足一定的字符要求,那么button不可点击。我们可以使用
combineLates操作符来组合usernameTextfield和passwordTextfield来绑定button的isEnabled属性确定是否可以点击,如官方例子SimpleValidationViewController,部分源码如下
//用户名的输入验证
let usernameValid = usernameOutlet.rx.text.orEmpty
.map { $0.characters.count >= minimalUsernameLength}
.shareReplay(1)
//密码的输入验证
let passwordValid = passwordOutlet.rx.text.orEmpty
.map {
$0.characters.count >= minimalPasswordLength
}
.shareReplay(1)
//组合usernameValid、passwordValid
let everythingValid = Observable.combineLatest(usernameValid, passwordValid) {
$0 && $1
}
.shareReplay(1)
//绑定到button的isEnabled属性,确定是否可点击
everythingValid
.bind(to: doSomethingOutlet.rx.isEnabled)
.disposed(by: disposeBag)
combineLates更多详情,可以看到这篇文章
combineLatest的集合版本
example(of: "Array.combineLatest") {
let disposeBag = DisposeBag()
//1创建观察者3个序列
let observable1 = Observable.just("1")
let observable2 = Observable.from(["a", "b"])
let observable3 = Observable.of("x", "y", "z")
Observable.combineLatest([observable1, observable2,observable3],{ ("\($0[0])\($0[1])\($0[2])") })
.subscribe(onNext: { print($0) })
.addDisposableTo(disposeBag)
}
执行结果:
--- Example of: Array.combineLatest ---
1ax
1bx
1by
1bz
注意:因为combineLatest变体版本是使用集合(collection)作为参数,所以要求:集合中的每一个值的类型必须一样。例子中都是Observable<String>类型.
zip Operator
组合多个原观察者序列(最多8)到单一的新观察者序列,而且在新的组合观察者序列中发送的元素来自于原观察者序列的对应位置上的元素的组合。zip 和 combineLatest 相似,不同的是每当所有序列都发射一个值时, zip 才会发送一个值。它会等待每一个序列发射值,发送次数的多少由最短序列决定,结合的值都是一一对应的。
example(of: "zip") {
let disposeBag = DisposeBag()
//创建一个字符串subject,一个整型subject
let stringSubject = PublishSubject<String>()
let intSubject = PublishSubject<Int>()
//在resultSelector对subject进行操作,获取同一位置的值
Observable.zip(stringSubject, intSubject, resultSelector: { stringElement, intElement in
"\(stringElement)\(intElement)" })
.subscribe(onNext: { print($0) })
.addDisposableTo(disposeBag)
//开始发送消息
stringSubject.onNext("A")
stringSubject.onNext("B")
intSubject.onNext(1)
intSubject.onNext(2)
stringSubject.onNext("AB")
intSubject.onNext(3)
}
执行结果:
--- Example of: zip ---
A1
B2
AB3
应用场景:如果我们需要两个接口或者多个接口返回的数据,我们可以使用zip组合多个请求,等待所有请求完成,再发送返回的结果。部分代码如下:
Observable.zip(httpClient.rx.firstResource(1),
httpClient.rx.secondResource()) { ($0, $1) }
.subscribe(onNext: { response1, response2 in
print(response1, response2, separator:"\n")
}).addDisposableTo(disposeBag)
详情可以看这里
withLatestFrom Operator
组合两个观察者序列到一个观察者序列,每次当第一个观察者序列发送元素之后,如果withLatestFrom操作符提供了闭包,那么组合第二个观察者序列的最新元素作为结果值返回,如果仅仅只有第二个观察者序列作为参数,那么将使用第二个观察者序列的最新值作为返回值。
example(of: "withLatestFrom") {
let disposeBag = DisposeBag()
//1
let subject1 = PublishSubject<Int>()
let subject2 = PublishSubject<String>()
//2
subject1.withLatestFrom(subject2)
.subscribe(onNext: {print($0)})
.addDisposableTo(disposeBag)
//3
subject1.withLatestFrom(subject2, resultSelector: {($0,$1)})
.subscribe(onNext: {print($0)})
.addDisposableTo(disposeBag)
//4
subject2.onNext("A")
subject2.onNext("B")
subject1.onNext(1)
subject1.onNext(2)
subject2.onNext("C")
subject1.onNext(3)
}
执行结果:
--- Example of: withLatestFrom ---
B
(1, "B")
B
(2, "B")
C
(3, "C")
代码分析:
1:创建两个PublishSubject,命名为subject1、subject2
2:使用withLatestFrom操作符,subject2作为第二个观察者序列参数,这里,每次当subject1发送消息时,将获取subject2中发送的最新消息
3:同样是使用withLatestFrom操作符,但是这里提供了闭包,将组合subject1发送的消息和subject2最新的消息为元祖。
因为最开始我们使用subject2发送消息A、B,但是subject1并未发送消息,所以并不会触发订阅者,当subject1发送消息时,步骤2,3将根据自己withLatestFrom操作符的特色返回对应的值,并发送给订阅者。所以subject1发送1,得到 B和(1, "B"),subject1发送2时得到 B和(2, "B"),subject1发送3时得到 C和(3, "C")
使用情景:
1:发送登录请求
在已经验证了用户名和密码之后,我们想发送登录请求,那么我们就应该使用withLatestFrom,因为withLatestFrom将观察者序列作为输入并且触发来自观察者序列的最新事件。我们可以使用withLatestFrom监听按钮的点击情况,官方GithubSignupViewModel1例子如下:
//组合用户名、密码
let usernameAndPassword = Observable.combineLatest(input.username, input.password) { ($0, $1) }
//监听按钮的点击,只在用户名和密码都有效的情况下,点击按钮进行登录请求操作,而不是使用combineLatest每次改变用户名或者密码都进行请求
signedIn = input.loginTaps.withLatestFrom(usernameAndPassword)
.flatMapLatest { (username, password) in
return API.signup(username, password: password)
.observeOn(MainScheduler.instance)
.catchErrorJustReturn(false)
.trackActivity(signingIn)
}
.flatMapLatest { loggedIn -> Observable<Bool> in
let message = loggedIn ? "Mock: Signed in to GitHub." : "Mock: Sign in to GitHub failed"
return wireframe.promptFor(message, cancelAction: "OK", actions: [])
// propagate original value
.map { _ in
loggedIn
}
}
.shareReplay(1)
2:withLatestFrom & UITableView selection
使用withLatestFrom能够很容易的处理row的点击,UITableView给予我们选中的位置,我们转换位置到模型对象(model object),官方例子TableViewWithEditingCommandsViewController
tableView.rx.itemSelected
.withLatestFrom(viewModel) { i, viewModel in
let all = [viewModel.favoriteUsers, viewModel.users]
return all[i.section][i.row]
}
.subscribe(onNext: { [weak self] user in
self?.showDetailsForUser(user)
})
.disposed(by: disposeBag)
amb Operator
对于两个或者多个观察者序列,仅仅只有最先发送消息的观察者序列才能被订阅者监听。并且之后只有该序列发送消息有效。
example(of: "amb") {
let disposeBag = DisposeBag()
let left = PublishSubject<String>()
let right = PublishSubject<String>()
//1 在left和right两个观察者序列上使用amb操作符
left.amb(right)
.subscribe(onNext: {print($0)})
.addDisposableTo(disposeBag)
//2 两个观察者序列发送消息
left.onNext("1")
right.onNext("A")
left.onNext("2")
left.onNext("3")
right.onNext("B")
}
执行结果:
--- Example of: amb ---
1
2
3
amb操作符订阅了left和right两个观察者序列,它将等待left和right两个观察者序列任意一个发送消息,一旦有一个观察者序列发送消息,那么将取消对另外一个观察者序列的订阅,在这之后仅仅只有最开始发售消息的观察者序列处于活跃状态,即发送消息有效,其它序列无效。
switchLatest Operator
转换发送消息的观察者序列到一个观察者序列,而且发送消息只由最新的内部观察者序列进行发送。下图中刚开始第一个观察者序列发送了2个消息,然后切换到第二个观察者序列,之后第一个观察者序列再次发送消息无效,只有第二个观察者序列的消息能够被接收。
example(of: "switchLatest") {
let disposeBag = DisposeBag()
//1
let subject1 = BehaviorSubject(value: "a")
let subject2 = BehaviorSubject(value: "b")
let variable = Variable(subject1)
//2
variable.asObservable()
.switchLatest()
.subscribe(onNext: {print($0)})
.addDisposableTo(disposeBag)
//3
subject1.onNext("1")
subject1.onNext("2")
//4
variable.value = subject2
//5
subject1.onNext("3")
subject2.onNext("4")
}
执行结果
--- Example of: switchLatest ---
a
1
2
b
4
1: 创建两个BehaviorSubject对象subject1、subject2,并使用subject1初始化Variable实例对象
2: 对variable使用switchLatest操作法并进行订阅,添加到处理池中。
3: subject1发送消息
4:改变variable中value的值,也就是意味着内部观察者序列发生了改变,只要新的内部观察者序列可以发送消息
5:这里subject1发送消息无效,因为执行了variable.value = subject2。即新的观察者序列subject2取代了之前的观察者序列subject1,前面的观察者序列subject1将被取消订阅。所以只有subject2能够发送事件,因为只有最近的内部观察者序列将发送事件(元素)。