RxSwift学习教程之类型对象Subject详解

前言

在上一篇文章我们介绍了 Observable 的基本概念和使用情形。但是大多数情形下,我们需要在应用运行时添加数据到 Observable 中并将其发送给订阅者。在这种需求场景下,我们就不得不使用 RxSwift 中另一种类型对象了 - Subject 。

在应用中 Subject 实际上同时扮演了两个不同的角色:既是可观察对象同时也是观察者。这意味着 Subject 实例对象既可以接收事件也可以发送事件。例如,Subject 实例对象可以接收 next 事件信息,然后再将其发送给它自己的订阅者。示例代码:

let subject = PublishSubject<String>()

let subscriptionOne = subject
       .subscribe(onNext: { string in
        print(string)
       })

subject.on(.next("1"))

/* 打印结果:
1
*/

上面代码中使用的是 PublishSubject 类型的示例,而 RxSwift 中总共也四种类型的 Subject:

  • PublishSubject:初始化时并不包含数据,并且只会给订阅者发送后续数据。
  • BehaviorSubject:创建时需要包含初始数据,并且会给订阅者发送后续数据和最近的一次数据。
  • ReplaySubject:创建时需要指定对象缓存区容量,该容量表示会给订阅者重新发送订阅前数据的大小。
  • Variable:BehaviorSubject 对象的封装类型。它会将当前数据保存为状态并且只会给订阅者重新发送最近或者初始值。

下面将详细介绍这四种类型对象的概念以及它们的区别和使用情况。

PublishSubject

如果你仅仅是想让订阅者获取被观察者在生命周期内若产生的数据的话,那么你完全可以选用 PublishSubject 。而且 PublishSubject 对象的行为符合正常的预期,它只会给订阅者发送其订阅开始之后的数据。

例如,下图的最上面的时间线表示被观察者所发送的事件,而下面两个则分别代表不同的观察者。可以看到下面两个观察者都只会接收到订阅后所发送的事件而无法获知之前的情形。

对应的代码为:

let subject = PublishSubject<String>()

let subscriptionOne = subject
       .subscribe(onNext: { event in
        print("1) \( event.element ?? event)" )
       })

subject.on(.next("1")) 

let subscriptionTwo = subject
       .subscribe(onNext: { event in
        print("2) \(event.element ?? event)")
       })
 
subject.on(.next("2")) 

subject.on(.next("3")) 

/* 打印结果
1) 1
1) 2
2) 2
1) 3
2) 3
*/

如果此时我们取消 subscriptionOne 的订阅并发送新数据的话,那么结果为:

subscriptionOne.dispose()
subject.on(.next("4")) 

 /* 打印结果
 2) 4
 */

另外,当 PublishSubject 对象生命周期结束时,无论后续是否继续有数据产生该对象只会简单的发送之前结束生命周期的事件。

// 结束生命周期
subject.onCompleted()

// 发送新数据
subject.onNext("5")

// 结束观察
subscriptionTwo.dispose()

let disposeBag = DisposeBag()
// 重新进行订阅操作 
subject
 .subscribe { 
  print("3)", $0.element ?? $0) 
 } 
 .addDisposableTo(disposeBag)
// 发送新数据
subject.onNext("?")

/* 打印结果
2) completed
3) completed
 */

对于时序敏感的操作来说,PublishSubject 显然是非常合适的选择。但是并不是所有的情形都时序敏感,有时候我们可能会希望在订阅时能够获知最近一次的数据。此时,我们就需要使用 BehaviorSubject 对象了。

BehaviorSubject

BehaviorSubject 的行为与 PublishSubject 几乎一致,不过前者会给订阅者多发送一个最近的数据。图解如下:

图示中最上面对应的是所发射的数据,其中第二行表示第一个观察者,第三行则表示另一个。可以发现第一个观察者是在 1 之后 2 之前进行观察的,但是它依然能够获取到数据 1 。我们可以通过代码进行验证:

let subject = BehaviorSubject(value: "1")
let bag = DisposeBag()

subject
 .subscribe { event in
   print("1) event: \(event.element!) ")
 }
 .addDisposableTo(bag)

subject
 .subscribe { event in
  print("2) event: \(event.element!) ")
 }
 .addDisposableTo(bag)

subject.onNext("2")

subject
 .subscribe { event in
  print("3) event: \(event.element!) ")
 }
 .addDisposableTo(bag)

subject.onNext("3")

因为始终都能获取到最近的数据,所以对于那些可能需要默认值的场景,BehaviorSubject 显然是一个好的选择。

ReplaySubject

ReplaySubject 与 BehaviorSubject 的行为非常接近,只不过前者允许订阅者获取多于 1 个的最近数据。所以从某种意义上来说,后者是前者的一个特例。

下图就是一个 buffer 大小为 2 的 ReplaySubject 对象。它总过发射了三次数据,其中第一个观察者获取了所以的数据。而第二个观察者虽然是在第二个数据发射后才开始,但它依旧能获取缓存区中保存的数据。

代码表示如下:

let subject = ReplaySubject<String>.create(bufferSize: 2)

let bag = DisposeBag()

subject
 .subscribe { event in
  print("1) event: \(event.element!) ")
 }
 .addDisposableTo(bag)

subject.onNext("1")

subject.onNext("2")

subject
 .subscribe { event in
  print("2) event: \(event.element!) ")
 }
 .addDisposableTo(bag)

subject.onNext("3")

/* 打印结果:
1) event: 1 
1) event: 2 
2) event: 1 
2) event: 2 
1) event: 3 
2) event: 3 
*/

不过有一点值得我们注意,因为 ReplaySubject 缓存机制使用了数组结构,所以当有大量 ReplaySubject 对象时可能导致内存暴增。另外,如果缓存对象是图片等极耗内存的资源时也可能导致内存问题。所以 ReplaySubject 不可滥用且缓存区大小应该合理进行设置。

Variable

前面提到过 Variable 类型是 BehaviorSubject 的封装类型,从某种意义上你可以将前者当作后者的子类(实际上并不是)。Variable 类型实例的行为与 BehaviorSubject 一致,只不过前者添加了一些自有特性。你可以通过 value 属性访问和设置 Variable 类型实例当前的状态值,这意味着我们无需调用 onNext(_:) 了。

作为 BehaviorSubject 封装后的类型,Variable 在初始化时也需要设置默认值。另外,它发送数据的行为也与 BehaviorSubject 一致:只会给订阅者重新发送最近或者初始值。另一个独有的特性是,Variable 实例是不会触发 error 事件的。也就是说,你可以订阅 Variable 实例的错误事件,但是你并不能添加一个错误事件到实例中。

代码示例:

var variable = Variable("Initial value")

let bag = DisposeBag()

variable.value = "New initial value"

variable.asObservable()
 .subscribe { event in
  print("1) event: \(event.element!) ")
 }
 .addDisposableTo(bag)

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对呐喊教程的支持。

声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:notice#nhooo.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。