我们知道了什么是observable,怎么创建,怎么订阅,当你完成的时候怎么dispose。Observables是RxSwift的基础,但是通常在开发app时需要在运行时向订阅者发送一个新值。你想一个东西可以扮演observable也可以扮演observer,这个就叫Subject。
在这一章中,我们将会学习RxSwift中很多不同类的subjects,看看每个都怎么工作,你可以选一个或多个常用的。
Getting started
在示例中添加如下代码:
example(of: "PublishSubject") {
let subject = PublishSubject()
}
这里创建了一个PublishSubject。就像报纸一样,它接收信息和向订阅者发布信息,也可能会改变一些信息。这是string型的,它只能接收和发送字符串。在初始化之后,就可以用来接收。添加以下代码到上面示例中:
subject.onNext("Is anyone listening?")
这向subject中放了一个新的字符串。但是没有任何打印,因为没有observers。通过以下代码创建一个订阅者:
let subscriptionOne = subject
.subscribe(onNext: { string in
print(string)
})
你为subject创建了一个订阅者像上一章中,应该打印.next事件。但是,xcode控制台并没有打印任何东西。很荒谬么?你将马上了解到subjects的不同之处。
是什么导致PublishSubject只向当前订阅者发送事件。如果在你订阅之前你发送了一些东西,当你订阅的时候你不会接收到。就一伐木做类比,当树倒的时候,并没有人看见它倒,那么这个非法伐木就成功了吗?
为了修复这件事情,添加一下代码:
subject.on(.next("1"))
注意,因为publish subject被定义为字符串类型,所以仅仅能放字符串在里面。现在,因为subject有了一个订阅者,将会发送文本“1”。
--- Example of: PublishSubject ---
1
和subscribe操作符一样, on(.next(_:)是为subject添加一个新的.next事件,以element作为参数。这儿有一个简短的语法如下:
subject.onNext("2")
onNext(:)和on(.next())一样,只是看起来更简单。打印如下:
--- Example of: PublishSubject ---
1
2
接收完毕,现在我们去学习所有的subjects。
什么是subjects
Subjects既是observable又是observer。就像先前看到的它是如何接收事件和被订阅的。subject接收.next事件,每次接收一个事件,就会转身给subscriber发送。
在RxSwift中有4中类型的subject:
- PublishSubject:创建一个空的Subject和只向订阅者发送新的elements。
- BehaviorSubject:创建一个含初始值的Subject和只向新的订阅者发送最后的值。
- ReplaySubject:初始化缓冲区大小的elements然后把他发送给新的订阅者。
- Variable:维护当前值得状态,只向新的订阅者发送最新的值或初始值。
轮流接收上面的每一个,你将学会更多的subjects和怎么使用。
Working with PublishSubjects
当你只想简单的知道subscribers在被订阅之后是否收到新的值用Publish subjects 就很方便,知道它取消订阅,或者以.completed或.error时间中断。
在下面的图表中,第一条线是publish subject,第二条和第三条是subscribers。向上的线表示subscriptions,向下的线表示发送事件。
第一个订阅者在1)之后订阅,所以不能接收到1)产生的事件,能接收到2)和3)。第二个订阅者知道第二部2)之后才加入,所以只能接受3)的事件。
在示例中添加如下代码:
let subscriptionTwo = subject
.subscribe { event in
print("2)", event.element ?? event)
}
.next事件中包含了一个可选的element属性。你可以用 nil-coalescing操作符打印值或者打印事件。
和预期一样,subscriptionTwo没有打印任何东西因为1)和2)都已经触发之后才被订阅。现在加入以下代码:
subject.onNext("3")
3被打印了两次,一个是subscriptionOne一次是subscriptionTwo。
3
2) 3
添加以下代码中断subscriptionOne和为subject添加另一个.next事件:
subscriptionOne.dispose()
subject.onNext("4")
4只被打印在subscriptionTwo的一次,因为subscriptionOne已经被disposed了
2) 4
当publish subject接收了一个.completed 或 .error事件,
又叫做stop事件,会立即向新的subscribers触发stop事件并不在触发.next事件。然而,还会为以后的订阅者重新触发stop事件。在示例中添加以下代码:
// 1
subject.onCompleted()
// 2
subject.onNext("5")
// 3
subscriptionTwo.dispose()
let disposeBag = DisposeBag()
// 4
subject
.subscribe {
print("3)", $0.element ?? $0)
}
.addDisposableTo(disposeBag)
subject.onNext("?")
它将做的事情是:
1.向subject发送了一个.completed事件,这将终端subject的observable序列。
2.向subject中添加另一个element。不会被发送和打印,因为这个subject已经被中断了。
3.dispose subscriptionTwo。
4.为subject创建一个新的subscription,这次添加到dispose bag中。
也许新的subscriber会触发打印事件?不会,你只会看见.completed事件。
2) completed
3) completed
事实上,每种类型的subject,一旦终止,都会对将来的订阅者重新发送stop事件。所以在你的代码中包含stop事件的操作是一个好主意,并不是中断了之后才被通知,除非在你订阅之前已经被中断了。
有时你想让新的订阅者知道最新的值是什么,甚至这些值在被订阅前发出。对于这个,你有一些选择。
Working with BehaviorSubjects
Behavior subjects和publish subjects相似,除非你想把最后的.next事件发送给新的subscribers。观察下面图表:
第一条线表示subject。第一个订阅者在第二条线上在1)之后2)之前,所以会立即得到1),当2)和3)发送时会得到2)和3)。相似的,第二个观察者值在2)之后3)之前,所以会立即得到2),当3)发送时得到3)。添加如下示例:
// 1
enum MyError: Error {
case anError
}
// 2
func print(label: String, event: Event) {
print(label, event.element ?? event.error ?? event)
}
// 3
example(of: "BehaviorSubject") {
// 4
let subject = BehaviorSubject(value: "Initial value")
let disposeBag = DisposeBag()
}
一步一步演示:
1.定义一个error类型
2.在上述示例中扩展三元操作符,创建了一个打印元素值,还是错误类型,还是世界本身的函数!
3.开始示例。
4.创建一个包含默认值的BehaviorSubject实例。
添加以下代码:
subject
.subscribe {
print(label: "1)", event: $0)
}
.addDisposableTo(disposeBag)
给subject创建了一个订阅者,但是是在subject之后创建的。没有在追加其他elements,所以只打印初始值。
--- Example of: BehaviorSubject ---
1) Initial value
现在我们插入以下代码到创建subject之后和订阅之前:
subject.onNext("X")
此时会把X打印出来,因为在订阅产生时X是最后一个element:
--- Example of: BehaviorSubject ---
1) X
添加以下代码,想想会打印出什么:
// 1
subject.onError(MyError.anError)
// 2
subject
.subscribe {
print(label: "2)", event: $0)
}
.addDisposableTo(disposeBag)
一步一步分解:
1.向subject中添加一个error事件。
2.给subject创建一个新的订阅者
你能想到error事件被打印了两次吗.每个订阅打印一次?
1) anError
2) anError
当你根据很多最近的数据提前构造一个view时会很有用。例如,你可以用behavior subject提前给用户绑定一个事件,当app获取数据时最后一个值会被展示出来。
但是如果你想展示更多的最新的数据呢?例如,在一个搜索界面,你想展示出最近被搜索的5条数据,这就是需要用到replay subjects的地方。
Working with ReplaySubjects
Replay subjects将会根据你选择的缓存区大小临时缓存最新发送的elements,然后将缓存区的数据发送给订阅者。以下图表描述了两个缓存区大小的 replay subject。第一个订阅者已经订阅了 replay subject 所以会得到它发送的elements。第二个订阅者在2)之后订阅,所以可以得到1)和2)来取代它.
当使用replay subject时缓存区会一直存在内存中。如果你设置一个缓冲区很大并且每个实例都占有很大内存的replay subject,就像图片。另一件值得注意的是创建一组replay subject。如果不小心会很容易造成内存紧张。示例:
example(of: "ReplaySubject") {
// 1
let subject = ReplaySubject.create(bufferSize: 2)
let disposeBag = DisposeBag()
// 2
subject.onNext("1")
subject.onNext("2")
subject.onNext("3")
// 3
subject
.subscribe {
print(label: "1)", event: $0)
}
.addDisposableTo(disposeBag)
subject
.subscribe {
print(label: "2)", event: $0)
}
.addDisposableTo(disposeBag)
}
1.创建一个缓冲区为2的replaysubject。用create(bufferSize:)初始化。
2.向subject中添加添加2个elements。
3.为subject创建两个订阅者。
最后的两个elements被发送到subscribers。1永远不会发送,因为2和3已经被添加到了一个缓存区大小为2的replay subject中在被订阅之前。
--- Example of: ReplaySubject ---
1) 2
1) 3
2) 2
2) 3
现在添加以下代码:
subject.onNext("4")
subject
.subscribe {
print(label: "3)", event: $0)
}
.addDisposableTo(disposeBag)
这段代码中,你添加了另一个element到subject中,然后创建一个新的订阅。前两个订阅能够正常接收element是因为在添加element到subject之前就已经被订阅了,第三个订阅者接收最后的两个elements。
1) 4
2) 4
3) 3
3) 4
你现在已经大概了解了,没有什么特别的。但是如果仅仅是这样你在工作中会遇到什么问题?添加下面代码在subject.onNext("4")之后和第三个订阅者之前。
subject.onError(MyError.anError)
打印结果如下,是否感到惊喜?
1) 4
2) 4
1) anError
2) anError
3) 3
3) 4
3) anError
发生了什么?replay subject 被一个error中断,就像你看见的一样它会向subjects重新发送。但是缓冲区仍然空闲,所以在重新发送stop事件之前仍然可以发送到新的订阅者。现在在error后面添加下面代码:
subject.dispose()
在事先调用dispose(),新的订阅者只会接收error事件因为subject已经被disposed。
3) Object `RxSwift.ReplayMany` was already disposed.
调用dispose()就像不需要你在做什么事情了,因为如果你把订阅者添加到dispose bag上(避免创建强类型的循环引用),当持有者销毁时所有的东西都会被销毁(如view controller 和 view model)。这里需要注意一下边界问题。
通过使用publish, behavior, 和 replay subject,你可能适用于大多数请看。或许有那么几次你想得到旧值和观察者类型,“现在的值是什么?”,Variables将派上用场!
Working with Variables
和先前提到的一样,Variable包裹了一个BehaviorSubject和存储了当前值。你可以通过value属性使用当前,值,不像一般的subjects和observables,你也可以使用value属性设置一个新值。换句话说,你不需要使用onNext(_:)。
因为它包含了behavior subject,variable用一个初始值创建,它将向订阅者发送最后的值或者初始值。你可以调用 asObservable()得到variable里面的behavior subject。
Variable和其他subjects比起来比较独特的是,它不会发送error。你不可以添加.error 事件,当它销毁时会自动complete,所以不用添加complete事件。示例:
example(of: "Variable") {
// 1
var variable = Variable("Initial value")
let disposeBag = DisposeBag()
// 2
variable.value = "New initial value"
// 3
variable.asObservable()
.subscribe {
print(label: "1)", event: $0)
}
.addDisposableTo(disposeBag)
}
他们做了什么:
1.用初始值创建了一个variable。类型是可推理的,因为明确的申明类型是Variable
2.向variable添加一个新的element。
3.订阅variable,先调用asObservable()得到behavior subject。
订阅者将会得到最后的值:
--- Example of: Variable ---
1) New initial value
添加以下代码到示例中:
// 1
variable.value = "1"
// 2
variable.asObservable()
.subscribe {
print(label: "2)", event: $0)
}
.addDisposableTo(disposeBag)
// 3
variable.value = "2"
1.向variable中添加一个新的elemen。
2.创建一个新的订阅。
3.添加另一个新的element。
已经存在的订阅者1)接收新值1.新的订阅者接收同样的值当他订阅的时候,因为这是最后一个值。两个订阅者都会接收2当它被加入到variable时。
1) 1
2) 1
1) 2
2) 2
这里没有添加任何的.error 或 .completed事件。任何尝试添加这类都会报错。w