首发地址:
github.com/leeeisok/Le…
环境:
Xcode 9.1
Swift 4.0
RxSwift 4.0
什么是 RxSwift
RxSwift 是 ReactiveX 的 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 - 序列的所有元素都已经成功产生,整个序列已经完成
基本创建方式
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
并发出唯一的一个元素
let id = Observable.just(0)
相当于:
let id = Observable<Int>.create { observer in
observer.onNext(0)
observer.onCompleted()
return Disposables.create()
}
复制代码
- from - 将其他类型或者数据结构转换为
Observable
将一个数组转换为 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()
}
复制代码
具体更多的操作符,大家可以看看这个文档的 如何选择操作符?。也可以看看官方示例里的 playground
:Rx.playground
Observer - 观察者
基本创建方式
理解观察者的意思后,那我们如何创建呢?对应 Observable 一节中的 “基本创建方式”,我们可以为 message: Observable
创建一个观察者:
message.subscribe(onNext: { str in
print("观察信息")
}, onError: { error in
print("发生错误")
}, onCompleted: {
print("完成")
}).dispose()
复制代码
创建观察者最直接的方法就是在 Observable
的 subscribe
方法后面描述,事件发生时,需要如何做出响应。而观察者就是由后面的 onNext
,onError
,onCompleted
的这些闭包构建出来的。
一些封装
RxSwift 也帮我们封装了很多许多常用的观察者(Observer),比如 button
的点击,通知以及代理等等。
我们先导入头文件:
import RxCocoa
复制代码
原先监听按钮点击需要这么做:
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 我们可以这么做:
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)
}
}
复制代码
再看看代理,原先我们需要这么做:
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 我们可以这么做:
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
)
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
)
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
我们直接看个 :
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。不过在创建这个「处理袋」时,我们需要保证它不那么快被释放掉,所以一般都是声明成实例的成员变量,和实例绑定起来,实例销毁,它也就销毁。我们具体来看个 :
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
,来实现自动清理。我们直接看 :
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
只有在被订阅的时候才会发射事件。每次有新的订阅者都会把之前所有的事件都重新发射一遍,换句话说,每个订阅者都会独立的收到订阅者发射的数据。
举个 :
let intSequence = Observable<Int>.create { (observer) -> Disposable in
observer.onNext(1)
observer.onNext(2)
observer.onNext(3)
return Disposables.create()
}
复制代码
上面的 Observable
在没订阅之前,create
是不会被执行的。当我们订阅时才会执行:
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
复制代码
如果我们接着上面的代码再多添加几个订阅:
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
有新的事件它就发射,不考虑是否有订阅者订阅。而新的订阅者并不会接收到订阅前已经发射过的事件。
这里怎么理解呢?按钮的点击就是个经典的 。屏幕上的一个按钮,不管有没有订阅者订阅它的点击事件,点击事件都会发生,我们都能通过手指点击屏幕上的按钮。当有订阅者订阅这个按钮后,我们就能收到按钮点击事件的反馈,但是没订阅之前的回调反馈是没有的。我们来看看代码:
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 ,此后又点击了两次,总共五次按钮的点击。输出结果如下:
[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 :
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
复制代码
参考:
beeth0ven.github.io/RxSwift-Chi… medium.com/@DianQK/hot… juejin.im/entry/57f29… github.com/ReactiveX/R… github.com/Joe0708/RxS…