RxSwift 入坑-需要知道的基础

4,289 阅读12分钟

首发地址:

github.com/leeeisok/Le…

环境:

Xcode 9.1

Swift 4.0

RxSwift 4.0

什么是 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 - 序列的所有元素都已经成功产生,整个序列已经完成

基本创建方式

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
将一个数组转换为 Observablelet 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> 创建一个观察者:

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

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

一些封装

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.cn/post/684490… github.com/ReactiveX/R… github.com/Joe0708/RxS…