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)

总结

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

(0)

相关推荐

  • RxSwift学习之Observable的新建、订阅及取消订阅

    前言 我们在前一篇基础之上,本文将会介绍 RxSwift 中的 Observables 部分. 在 RxSwift 中 Observable 也被称为 Observable Sequence.Sequence.Stream.Observable 会以异步的方式不断的发射事件形成事件流,并且数据也会沿着事件流进行传播.下图是事件流的图像化表示: 其中从左到右的箭头代表时间轴,而三个圆圈则构成了可观察序列.而整个过程会按照从左到右的顺序.另外,事件可能在可观察序列生命周期内的任意时刻被触发. Obs

  • RxSwift学习教程之基础篇

    前言 我们在 iOS 开发过程中,几乎无时无刻都要面对异步事件的处理.例如,按键点击.数据保存..音频后台播放.交互动画展示.这些事件并不具备特定时序性,甚至它们可能同时发生. 虽然 Apple 提供了通知.代理.GCD.闭包等异步机制,但是这些机制缺乏一个统一的抽象表述.另外,这些机制在处理共享的可变数据或状态时不够清晰简练.当然,这并不是说编写优雅的异步代码不现实.毕竟与其他平台相比 iOS 的异步机制还是很强大的. 幸运的是,我们能够通过 RxSwift 优雅的处理异步代码. 至于 RxS

  • RxSwift使用技巧之过滤操作详解

    前言 在前面的基础之上接下来我会介绍一些常用的函数和实用技巧.首先,本文将会介绍那些用于对 next 事件进行过滤的操作.这些过滤操作类似于 Swift 标准库中的 filter 操作.它能在我们开始真正进行业务处理前先把那些不符合条件的过滤掉,而且这种函数式编程的范式也能开阔我们的思维. Ignore 过滤 RxSwift 中最简单直接的过滤操作就是 ignoreElements 了.该操作会屏蔽所有的 next 事件,只会将注意力放在 error 和 completed 事件上.如下图所示,

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

    前言 在上一篇文章我们介绍了 Observable 的基本概念和使用情形.但是大多数情形下,我们需要在应用运行时添加数据到 Observable 中并将其发送给订阅者.在这种需求场景下,我们就不得不使用 RxSwift 中另一种类型对象了 - Subject . 在应用中 Subject 实际上同时扮演了两个不同的角色:既是可观察对象同时也是观察者.这意味着 Subject 实例对象既可以接收事件也可以发送事件.例如,Subject 实例对象可以接收 next 事件信息,然后再将其发送给它自己的

  • Go语言学习教程之反射的示例详解

    目录 介绍 反射的规律 1. 从接口值到反射对象的反射 2. 从反射对象到接口值的反射 3. 要修改反射对象,该值一定是可设置的 介绍 reflect包实现运行时反射,允许一个程序操作任何类型的对象.典型的使用是:取静态类型interface{}的值,通过调用TypeOf获取它的动态类型信息,调用ValueOf会返回一个表示运行时数据的一个值.本文通过记录对reflect包的简单使用,来对反射有一定的了解.本文使用的Go版本: $ go version go version go1.18 dar

  • ES6学习教程之块级作用域详解

    前言 众所周知ES5之前javascript语言只有函数作用域和全局作用域,使用var来声明变量,var声明的变量还存在变量提升使人困惑不已.我们先来复习一下ES5的var声明,再对比学习let和const . var var声明之函数作用域和全局作用域. 来段代码体会一下: function getName() { if (1 + 1 === 2) { var name = 'xixi'; } console.log(name); } getName();//xixi 在c或java语言中na

  • Struts2学习教程之输入校验示例详解

    前言 数据校验几乎是每个应用都要做的工作.用户输入的数据,发送到服务器端,天知道用户输入的数据是否是合法的,是否为恶意输入.所以一个健壮的应用系统必须对用户的输入进行校验,将非法的输入阻止在应用之外,防止这些非法的输入进入系统,从而保证系统的稳定性.安全性. 我们都知道,为了更好的用户体验,以及更高的效率,现在的Web应用都存在以下两重数据校验: 客户端数据校验 服务器端数据校验 对于客户端数据校验主要是通过JavaScript代码来完成:而对于服务器端数据校验是整个应用阻止非法数据的最后防线,

  • 正则表达式学习教程之回溯引用backreference详解

    本文实例讲述了正则表达式回溯引用backreference.分享给大家供大家参考,具体如下: 在所有例子中正则表达式匹配结果包含在源文本中的[和]之间,有的例子会使用Java来实现,如果是java本身正则表达式的用法,会在相应的地方说明.所有java例子都在JDK1.6.0_13下测试通过. 一.问题引入 一个在HTML页面中匹配标题标签(H1-H6)的问题: 文本: <body> <h1>Welcome to my page</H1> Content is divid

  • Java对象类型的判断详解

    instanceof 判断某个对象是否是某个类的实例或者某个类的子类的实例.它的判断方式大概是这样的: public<T> boolean function(Object obj, Class<T> calzz) { if (obj == null) { return false; } try { T t = (T) obj; return true; } catch (ClassCastException e) { return false; } } Class.equals()

  • Bootstrap学习笔记之进度条、媒体对象实例详解

    1.基础进度条 要写在<div class="progress"></div>里面. <div class="col-md-6"> <div class="progress"> <div class="progress-bar" style="width:30%;"></div> </div> </div> 2.

  • Spring IoC学习之ApplicationContext中refresh过程详解

    refresh() 该方法是 Spring Bean 加载的核心,它是 ClassPathXmlApplicationContext 的父类 AbstractApplicationContext 的一个方法 , 顾名思义,用于刷新整个Spring 上下文信息,定义了整个 Spring 上下文加载的流程. public void refresh() throws BeansException, IllegalStateException { synchronized(this.startupShu

  • R语言学习ggplot2绘制统计图形包全面详解

    目录 一.序 二.ggplot2是什么? 三.ggplot2能画出什么样的图? 四.组装机器 五.设计图纸 六.机器的零件 1. 零件--散点图 1) 变换颜色 2) 拟合曲线 3) 变换大小 4) 修改透明度 5) 分层 6) 改中文 2. 零件--直方图与条形图 1) 直方图 2) 润色 3) 条形图 3. 零件--饼图 4. 零件--箱线图 5. 零件--小提琴图 6. 零件打磨 7. 超级变变变 8. 其他常用零件 七.实践出真知 八.学习资源 九.参考资料 一.序 作为一枚统计专业的学

  • Python学习之yaml文件的读取详解

    目录 yaml 文件的应用场景与格式介绍 yaml 文件的应用场景 yaml 文件的格式 第三方包 - pyyaml 读取 yaml 文件的方法 yaml文件读取演示案例 yaml 文件的应用场景与格式介绍 yaml 文件的应用场景 yaml其实也类似于 json.txt ,它们都属于一种文本格式.在我们的实际工作中, yaml 文件经常作为服务期配置文件来使用. 比如一些定义好的内容,并且不会修改的信息,我们就可以通过定义 yaml 文件,然后通过读取这样的文件,将数据导入到我们的服务中进行使

随机推荐