RxSwift 入坑-需要知道的基础概念

环境:

Xcode 9.1
Swift 4.0
RxSwift 4.0

目录:

什么是 RxSwift
响应式编程
RxSwift 核心概念
Hot and Cold Observables

什么是 RxSwift

RxSwiftReactiveX 的 Swift 版本,全称 Reactive Extensions Swift,是一个响应式编程的基础框架。

响应式编程

在面向对象时代,大多数程序都像这样运行:你的代码告诉你的程序需要做什么,并且有很多方法来监听变化–但同时你又必须主动告诉系统什么时候发生的变化。

响应式编程的基本思想是:你的程序可以对底层数据的变化做出响应,而不需要你直接告诉它。这样,你可以更专注于所需要处理的业务逻辑,而不需要去维护特定的状态。

举个简单的例子:

a = b + c
赋值之后 b 或者 c 的值变化后,a 的值不会跟着变化
响应式编程,目标就是,如果 b 或者 c 的数值发生变化,a 的数值会同时发生变化;

另外推荐看看这篇 iOS 响应式架构

RxSwift 的核心

RxSwift 核心概念就是一个观察者(Observer)订阅一个可被观察序列(Observable)。观察者对可被观察序列发射的数据或数据序列作出响应。

举个简单的例子,当别人在跟你说话时,你就是那个观察者(Observer),别人就是那个(Observable),它有几个特点:

  • 可能会不断地跟你说话。(onNext
  • 可能会说错话。(onError
  • 结束说话。(onCompleted

你在听到对方说的话后,也可以有几种反应:

  • 根据说的话,做相应的事,比如对方让你借钱给他。(subscribe
  • 把对方说的话,加工下再传达给其他人,比如对方说小李好像不太舒服,你传达给其他人时就变成了小李失恋了。(map:
  • 参考其他人说的话再做处理,比如小李说某家店很好吃,小黄说某家店一般般,你需要结合两个人的意见再做定夺。(zip:

Observable - 可被观察的序列

Observable 的三种事件

  • next - 序列产生了一个新的元素
  • error - 创建序列时产生了一个错误,导致序列终止
  • completed - 序列的所有元素都已经成功产生,整个序列已经完成

基本创建方式

1
2
3
4
5
6
7
8
9
10
11
12
enum MyError: Error {
case anError
}
let message: Observable<String> = Observable<String>.create { (observer) -> Disposable in
observer.onNext("😄")
observer.onError(MyError.anError)
observer.onCompleted()
return Disposables.create()
};

封装好操作符创建方式

  • just - 将某一个元素转换为 Observable 并发出唯一的一个元素
1
2
3
4
5
6
7
8
9
let id = Observable.just(0)
相当于:
let id = Observable<Int>.create { observer in
observer.onNext(0)
observer.onCompleted()
return Disposables.create()
}
  • from - 将其他类型或者数据结构转换为 Observable
1
2
3
4
5
6
7
8
9
10
11
12
13
将一个数组转换为 Observable
let numbers = Observable.from([0, 1, 2])
相当于:
let numbers = Observable<Int>.create { observer in
observer.onNext(0)
observer.onNext(1)
observer.onNext(2)
observer.onCompleted()
return Disposables.create()
}

具体更多的操作符,大家可以看看这个文档的 如何选择操作符?。也可以看看官方示例里的 playgroundRx.playground

Observer - 观察者

基本创建方式

理解观察者的意思后,那我们如何创建呢?对应 Observable 一节中的 “基本创建方式”,我们可以为 message: Observable<String> 创建一个观察者:

1
2
3
4
5
6
7
message.subscribe(onNext: { str in
print("观察信息")
}, onError: { error in
print("发生错误")
}, onCompleted: {
print("完成")
}).dispose()

创建观察者最直接的方法就是在 Observablesubscribe 方法后面描述,事件发生时,需要如何做出响应。而观察者就是由后面的 onNextonErroronCompleted 的这些闭包构建出来的。

一些封装

RxSwift 也帮我们封装了很多许多常用的观察者(Observer),比如 button 的点击,通知以及代理等等。

我们先导入头文件:

1
import RxCocoa

原先监听按钮点击需要这么做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let btn = UIButton.init(frame: CGRect(x: 100, y: 100, width: 150, height: 50))
btn.backgroundColor = UIColor.brown
btn.addTarget(self, action: #selector(btnClick), for: .touchUpInside)
view.addSubview(btn)
}
// 回调监听
@objc func btnClick() {
print("btn click !")
}
}

用 RxSwift 我们可以这么做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ViewController: UIViewController {
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let btn = UIButton.init(frame: CGRect(x: 100, y: 100, width: 150, height: 50))
btn.backgroundColor = UIColor.brown
view.addSubview(btn)
// 回调监听
btn.rx.tap.subscribe(onNext: {
print("btn click !")
}).disposed(by: disposeBag)
}
}

再看看代理,原先我们需要这么做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ViewController: UIViewController {
@IBOutlet weak var textView: UITextView!
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 设置代理
textView.delegate = self
}
}
extension UIViewController: UITextViewDelegate {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("y坐标:\(offset.y)")
}
}

用 RxSwift 我们可以这么做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ViewController: UIViewController {
@IBOutlet weak var textView: UITextView!
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
textView.rx.contentOffset.subscribe(onNext: { offset in
print("y坐标:\(offset.y)")
}).disposed(by: disposeBag)
}
}

可以发现,RxSwift 使一些系统的方法,使用上变得更加方便。

Subjects - 既是可被监听的序列也是观察者

Subject 是 observable 和 Observer 之间的桥梁。一个 Subject 既是一个 Obserable 也是一个 Observer,既可以发出事件,也可以监听事件。

例如:UITextField 的当前文本。它可以看成是由用户输入,而产生的一个文本序列。也可以是由外部文本序列,来控制当前显示内容的观察者:

  • 作为可被监听的序列(observable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ViewController: UIViewController {
@IBOutlet weak var tf: UITextField!
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 作为可被监听的序列
let observable_tf = tf.rx.text
observable_tf.subscribe(onNext:{ text in
if let t = text {
DebugPrint(t)
}
}).disposed(by: disposeBag)
}
}
  • 作为观察者(Observer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ViewController: UIViewController {
@IBOutlet weak var tf: UITextField!
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 作为观察者
let observer_tf = tf.rx.text
let text: Observable<String?> = Observable.just("Atom.")
text.bind(to: observer_tf).disposed(by: disposeBag)
}
}

RxSwift 中也定义了一些辅助类型,它们既是可被监听的序列也是观察者。这里就不多讲了,具体可以看看这里 Observable & Observer 既是可被监听的序列也是观察者

Disposable - 可被清除的资源

当监听一个事件序列的时候,有消息事件来了,我们做某些事情。但是这个事件序列不再发出消息了,我们的监听也就没有什么存在价值了,为了不消耗内存,需要释放这些监听资源。

一个 Observable 被观察订阅后,就会产生一个 Disposable 实例,表示「可扔掉」的,怎么扔掉呢?有下面几种方式:

  • 调用 dispose() 显式释放
  • 通过 DisposeBag 也就是 disposed() 隐式释放。
  • 利用 takeUntil 操作符,隐式释放。

Dispose

我们直接看个 🌰 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let subscription = Observable<Int>.interval(1.0, scheduler: SerialDispatchQueueScheduler.init(internalSerialQueueName: "serial"))
.subscribe { event in
print("\(event)")
}
Thread.sleep(forTimeInterval: 4.0)
DebugPrint("手动释放")
subscription.dispose()
}
}
// 输出:
[2017-12-29 16:47:49 ViewController.swift viewDidLoad() 39]:next(0)
[2017-12-29 16:47:50 ViewController.swift viewDidLoad() 39]:next(1)
[2017-12-29 16:47:51 ViewController.swift viewDidLoad() 39]:next(2)
[2017-12-29 16:47:52 ViewController.swift viewDidLoad() 39]:next(3)
[2017-12-29 16:47:52 ViewController.swift viewDidLoad() 44]:手动释放

上面的例子类似定时器,每秒会调一次回调。我们 sleep 4 秒后,调下 dispose(),可以发现回调就停止了,因为这个订阅被释放了。

dispose() 这种显示释放资源的方式一般不推荐,下面介绍的 DisposeBag 是比较推荐的方式!

DisposeBag

DisposeBag 是比较推荐的方式。看名字也很好理解 -「处理袋」,把需要释放的 Disposable 实例,扔到袋子里。在袋子被回收(deinit)时,会顺便执行一下 Disposable.dispose(),之前创建 Disposable 时申请的资源就会被一并释放掉。听起来有点像 ARC。不过在创建这个「处理袋」时,我们需要保证它不那么快被释放掉,所以一般都是声明成实例的成员变量,和实例绑定起来,实例销毁,它也就销毁。我们具体来看个 🌰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ViewControllerTwo: UIViewController {
// 创建「处理袋,声明成实例的成员变量
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let subscription = Observable<Int>.interval(1.0, scheduler: SerialDispatchQueueScheduler.init(internalSerialQueueName: "haha"))
.subscribe { event in
DebugPrint("\(event)")
}
subscription.disposed(by: disposeBag)
}
deinit {
DebugPrint("释放了")
}
}
// 输出:
[2017-12-29 17:57:03 ViewControllerTwo.swift viewDidLoad() 23]:next(0)
[2017-12-29 17:57:04 ViewControllerTwo.swift viewDidLoad() 23]:next(1)
[2017-12-29 17:57:05 ViewControllerTwo.swift viewDidLoad() 23]:next(2)
[2017-12-29 17:57:06 ViewControllerTwo.swift viewDidLoad() 23]:next(3)
[2017-12-29 17:57:07 ViewControllerTwo.swift viewDidLoad() 23]:next(4)
[2017-12-29 17:57:08 ViewControllerTwo.swift deinit 29]:释放了

可以发现,在 ViewControllerTwo 释放(deinit)后,回调也停止了!

takeUntil

我们还可以利用 takeUntil 操作符,把订阅绑定在 deallocated,来实现自动清理。我们直接看 🌰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ViewControllerTwo: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
_ = Observable<Int>
.interval(1.0, scheduler: SerialDispatchQueueScheduler.init(internalSerialQueueName: "haha"))
.takeUntil(self.rx.deallocated) // 绑定 deallocated
.subscribe { event in
DebugPrint("\(event)")
}
}
deinit {
DebugPrint("释放了")
}
}
// 输出:
[2017-12-29 18:24:29 ViewControllerTwo.swift viewDidLoad() 22]:next(0)
[2017-12-29 18:24:30 ViewControllerTwo.swift viewDidLoad() 22]:next(1)
[2017-12-29 18:24:31 ViewControllerTwo.swift viewDidLoad() 22]:next(2)
[2017-12-29 18:24:32 ViewControllerTwo.swift viewDidLoad() 22]:next(3)
[2017-12-29 18:24:33 ViewControllerTwo.swift viewDidLoad() 22]:next(4)
[2017-12-29 18:24:34 ViewControllerTwo.swift viewDidLoad() 22]:next(5)
[2017-12-29 18:24:35 ViewControllerTwo.swift deinit 27]:释放了
[2017-12-29 18:24:35 ViewControllerTwo.swift viewDidLoad() 22]:completed

可以发现在 ViewControllerTwo 释放(deinit)后,回调也停止了!

Hot and Cold Observables

Cold Observables

只有在被订阅的时候才会发射事件。每次有新的订阅者都会把之前所有的事件都重新发射一遍,换句话说,每个订阅者都会独立的收到订阅者发射的数据。

举个 🌰 :

1
2
3
4
5
6
7
8
let intSequence = Observable<Int>.create { (observer) -> Disposable in
observer.onNext(1)
observer.onNext(2)
observer.onNext(3)
return Disposables.create()
}

上面的 Observable 在没订阅之前,create 是不会被执行的。当我们订阅时才会执行:

1
2
3
4
5
6
7
8
9
10
intSequence.subscribe(onNext:{ value in
DebugPrint("subscribe1 : \(value)");
}).disposed(by: disposeBag)
// 输出:
[2018-01-02 17:05:24 ViewController.swift viewDidLoad() 76]:subscribe1 : 1
[2018-01-02 17:05:24 ViewController.swift viewDidLoad() 76]:subscribe1 : 2
[2018-01-02 17:05:24 ViewController.swift viewDidLoad() 76]:subscribe1 : 3

如果我们接着上面的代码再多添加几个订阅:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
for i in 2...4 {
// 第三个订阅延迟两秒
if i == 3 {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2, execute: {
intSequence.subscribe(onNext:{ value in
DebugPrint("subscribe\(i) : \(value)")
}).disposed(by: self.disposeBag)
})
continue
}
intSequence.subscribe(onNext:{ value in
DebugPrint("subscribe\(i) : \(value)")
}).disposed(by: disposeBag)
}
// 输出:
[2018-01-02 17:05:24 ViewController.swift viewDidLoad() 76]:subscribe1 : 1
[2018-01-02 17:05:24 ViewController.swift viewDidLoad() 76]:subscribe1 : 2
[2018-01-02 17:05:24 ViewController.swift viewDidLoad() 76]:subscribe1 : 3
[2018-01-02 17:05:24 ViewController.swift viewDidLoad() 96]:subscribe2 : 1
[2018-01-02 17:05:24 ViewController.swift viewDidLoad() 96]:subscribe2 : 2
[2018-01-02 17:05:24 ViewController.swift viewDidLoad() 96]:subscribe2 : 3
[2018-01-02 17:05:24 ViewController.swift viewDidLoad() 96]:subscribe4 : 1
[2018-01-02 17:05:24 ViewController.swift viewDidLoad() 96]:subscribe4 : 2
[2018-01-02 17:05:24 ViewController.swift viewDidLoad() 96]:subscribe4 : 3
[2018-01-02 17:05:26 ViewController.swift viewDidLoad() 86]:subscribe3 : 1
[2018-01-02 17:05:26 ViewController.swift viewDidLoad() 86]:subscribe3 : 2
[2018-01-02 17:05:26 ViewController.swift viewDidLoad() 86]:subscribe3 : 3

可以发现每次数据都会重新发射一遍,是独立的,即 Observable 会为每个订阅者单独执行一次发射数据的代码。

Hot Observables

有新的事件它就发射,不考虑是否有订阅者订阅。而新的订阅者并不会接收到订阅前已经发射过的事件。

这里怎么理解呢?按钮的点击就是个经典的 🌰。屏幕上的一个按钮,不管有没有订阅者订阅它的点击事件,点击事件都会发生,我们都能通过手指点击屏幕上的按钮。当有订阅者订阅这个按钮后,我们就能收到按钮点击事件的反馈,但是没订阅之前的回调反馈是没有的。我们来看看代码:

1
2
3
4
5
6
7
8
9
10
let tap = button.rx.tap
tap.subscribe(onNext: {
DebugPrint("Tap 1")
}).disposed(by: disposeBag)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
tap.subscribe(onNext: {
DebugPrint("Tap 2")
}).disposed(by: self.disposeBag)
}

比如 3 秒前,我们点击了三次 Button ,此后又点击了两次,总共五次按钮的点击。输出结果如下:

1
2
3
4
5
6
7
[2018-01-02 18:22:51 ViewController.swift viewDidLoad() 45]:Tap 1
[2018-01-02 18:22:52 ViewController.swift viewDidLoad() 45]:Tap 1
[2018-01-02 18:22:52 ViewController.swift viewDidLoad() 45]:Tap 1
[2018-01-02 18:22:56 ViewController.swift viewDidLoad() 45]:Tap 1
[2018-01-02 18:22:56 ViewController.swift viewDidLoad() 50]:Tap 2
[2018-01-02 18:22:56 ViewController.swift viewDidLoad() 45]:Tap 1
[2018-01-02 18:22:56 ViewController.swift viewDidLoad() 50]:Tap 2

3 秒前第二个订阅者并没有订阅 tap ,故不会有 Tap 2 的输出。3 秒后第二个订阅者订阅了 tap ,此时点击 Button ,可以看到打印结果多了 Tap 2。但与 Cold Observable 不同的是,第二个订阅者不会收到之前三次的点击事件。

我们如果将 button.rx.tap 替换成 Cold Observables,可以看到打印结果有 5 个 Tap 2 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let tap = Observable<Int>.create { (observer) -> Disposable in
observer.onNext(1)
observer.onNext(2)
observer.onNext(3)
observer.onNext(4)
observer.onNext(5)
return Disposables.create()
}.map({_ in})
// 输出:
[2018-01-02 18:24:43 ViewController.swift viewDidLoad() 47]:Tap 1
[2018-01-02 18:24:43 ViewController.swift viewDidLoad() 47]:Tap 1
[2018-01-02 18:24:43 ViewController.swift viewDidLoad() 47]:Tap 1
[2018-01-02 18:24:43 ViewController.swift viewDidLoad() 47]:Tap 1
[2018-01-02 18:24:43 ViewController.swift viewDidLoad() 47]:Tap 1
[2018-01-02 18:24:46 ViewController.swift viewDidLoad() 52]:Tap 2
[2018-01-02 18:24:46 ViewController.swift viewDidLoad() 52]:Tap 2
[2018-01-02 18:24:46 ViewController.swift viewDidLoad() 52]:Tap 2
[2018-01-02 18:24:46 ViewController.swift viewDidLoad() 52]:Tap 2
[2018-01-02 18:24:46 ViewController.swift viewDidLoad() 52]:Tap 2

参考:

https://beeth0ven.github.io/RxSwift-Chinese-Documentation/
https://medium.com/@DianQK/hot-observable%E5%92%8Ccold-observable-c3ba8d07867b
https://juejin.im/entry/57f29c46da2f60004f68f663
https://github.com/ReactiveX/RxSwift/tree/master/Documentation
https://github.com/Joe0708/RxSwift-Tutorial