章节3:Subjects

我们知道了什么是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,向下的线表示发送事件。

章节3:Subjects_第1张图片
21287902-70A2-4F71-BE83-3C546CC6CFF0.png

第一个订阅者在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。观察下面图表:

章节3:Subjects_第2张图片
9E54B28A-CC6E-4D02-BCB2-43C043268616.png

第一条线表示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)来取代它.

章节3:Subjects_第3张图片
9375AC7E-EF98-4676-8F5F-C9C552B0479B.png

当使用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("Initial value").
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

你可能感兴趣的:(章节3:Subjects)