深入理解RxSwift

3,463 阅读6分钟

简介

本篇重点在于深入RxSwift的部分常用特性,所以希望读者在了解RxSwift官方的基本讲解与Demo之后再进行阅读。

RxSwift版本为5.0.0以上。

Dispose

在写代码的时候,我们经常会在很多情况下创建一个Observable

_ = Observable<String>.create { observerOfString -> Disposable in
    observerOfString.on(.next("😬"))
    observerOfString.on(.completed)
    return Disposables.create()
}

又或者是在监听Observable

let disposeBag = DisposeBag()

Observable<Int>.empty()
    .subscribe { event in
        print(event)
    }
    .disposed(by: disposeBag)

这里总是看到是DisposablesDisposeBag,那么它们到底是什么。

Disposables

Disposables是一个简单的结构体

public struct Disposables {
    private init() {}
}

通过extension生成create方法, 创建四种不同的Disposable。

Disposable定义:

public protocol Disposable {
    /// Dispose resource.
    func dispose()
}

Cancelable, 在Disposable的基础上添加了isDisposed属性,便于追踪Disposable的状态:

public protocol Cancelable : Disposable {
    /// Was resource disposed.
    var isDisposed: Bool { get }
}
  1. NopDisposable, 在disposal时什么都不做。
  2. AnonymousDisposable, 创建时传入一个public typealias DisposeAction = () -> Void类型的闭包,在disposal时调用。
  3. BinaryDisposable, 在创建时传入两个Disposable类型的参数,在disposal时调用。
  4. CompositeDisposable, 在创建时传入多个Disposable类型的参数,在disposal时调用。

除了这四种可以通过快速创建的,还有其他类型的Disposable

  • BooleanDisposable,主要用于追踪disposal的状态,初始化时传入Bool类型的参数,表示是否已经被Dispose。
  • SubscriptionDisposable,主要在Subject中使用。
  • RefCountDisposable,初始化时传入一个Disposable类型的参数,如同命名,内存采用了引用计数的管理方法,调用retain方式时计数+1,调用release时计数-1,引用计数不能小于0,当等于0时,如果没有调用过dispose方法(其实是将内部的_primaryDisposed属性标记为true),也不会触发dispose方法,同样如果引用计数不为0,调用dispose方法也不会触发内部的Disposable的dispose。
  • ScheduledDisposable,初始化时传入一个Disposable类型的参数与一个ImmediateSchedulerType,指定传入的Disposable在某个线程上执行dispose。
  • SerialDisposable: 可以手动替换内部的Disposable,如果在_isDisposed为false的状态,替换后会自动触发之前的Disposable的dispose方法,如果为true,则直接触发替换的Disposable的dispose方法。
  • SingleAssignmentDisposable:对内部的Disposable只能设定一次,若设定多次会报错,在没有设定的情况下触发dispose方法也会报错。

DisposeBag

DisposeBag如同名字,是一个Bag类型的数据结构,里面存放Disposable的数据。

每次看到Disposable类型调用disposed(by: disposeBag),其实是将Disposable放在DisposeBag中进行管理,当DisposeBag进行dispose时,对其中管理的Disposable分别dispose,但是DisposeBag不能主动调用,只能在deinit时自动释放,所以想要进行dispose操作,只能将disposeBag重新赋值,比如:

disposeBag = DisposeBag()

Subject

简介

Subject是在RX的某些实现中可用的桥接或代理。

通过继承了ObserverType,可以成为一个观察者,可以通过on(_ event: Event<Element>)发送事件。

通过继承了Observable,可以成为一个被观察者,可以通过subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element推送事件。

Subject都是通过Broadcasts的方式传播事件。

冷热信号

在RAC中通过Signal与SignalProducer区分冷热信号的概念,RxSwift其实也有冷热信号。

冷信号是指在被观察之后才会推送事件。

热型号是指就算没有被观察,也能推送事件。

可以理解为一个主动一个被动。

这是一个比较抽象的例子:

let subject = Subject()
subject.onNext("1")
subject.subcribeNext { value in
    print(value)
}
subject.onNext("2")
如果是冷信号,这时候会打印1 2,热信号则只会打印2.

各种Subject

在Rx中有四种Subject。分别是BehaviorSubjectPublishSubjectReplaySubjectAsyncSubject

下列图标中,圆代表next信号,竖线表示complete,x表示error。

PublishSubject就是一个热信号的Subject。

PublishSubject
PublishSubject

BehaviorSubject是一个有初始值,缓存数量为1的Subject。在5.0版本之前的RxSwift中,有一个叫做Variable的属性,在5.0之后,因为相同的特性,完全被BehaviorSubject取代。

BehaviorSubject
BehaviorSubject

ReplaySubject是一个可以自定义缓存数量的Subject。当设定缓存数量为0时,几乎可以当做PublishSubject使用,缓存数量为1时,可以当做一个没有初始值的BehaviorSubject使用。

ReplaySubject

AsyncSubject,只会在接收到Complete事件后,才会Subscribe最后一个信号,如果没有在onComplete之前没有onNext事件,或者触发了onError,则不会触发Subscribe。

AsyncSubject
AsyncSubject

Demo

简单的按钮处理,当用户名长度大于4位且密码长度大于6位时,登录按钮才能点击:

private var disposeBag = DisposeBag()
private let nameSubject = BehaviorSubject<String>(value: "")
private let passwordSubject = BehaviorSubject<String>(value: "")
private let loginSubject = BehaviorSubject<Bool>(value: false)
private let nameTextField = UITextField()
private let passwordTextField = UITextField()
private let loginButton = UIButton()
......
//中间内容省略
......
nameTextField.rx.text.orEmpty.bind(to: nameSubject).disposed(by: disposeBag)
passwordTextField.rx.text.orEmpty.bind(to: passwordSubject).disposed(by: disposeBag)
Observable<Bool>.combineLatest(nameSubject, passwordSubject) { (name, password) -> Bool in
    return name.count > 4 && password.count > 6
}.bind(to: loginButton.rx.isEnabled).disposed(by: disposeBag)

通过某个实时持续的交互,每秒刷新UI:

private let socketSubject = ReplaySubject<String>.create(bufferSize: 1)
......
DoSomething { 
    socketSubject.onNext(value)
}

Observable<Int>
    .interval(.seconds(1), scheduler: MainScheduler.instance)
    .withLatestFrom(socketSubject)
    .distinctUntilChanged()
    .subscribe(onNext: { (value) in
        print(value)
    }).disposed(by: disposeBag)

思考

如果ReplaySubject的bufferSize为1,是否与BehaviorSubject相同?

如果ReplaySubject的bufferSize为0,是否与PublishSubject相同?

Schedulers

Schedulers是RxSwift中的调度机制。

主要运算符只有两个observeOn以及subscribeOn

sequence1
  .observeOn(backgroundScheduler)
  .map { n in
      print("This is performed on the background scheduler")
  }
  .observeOn(MainScheduler.instance)
  .map { n in
      print("This is performed on the main scheduler")
  }
  .subscribeOn(subscribeScheduler)
  .subscribe { _ in
      print("This is performed on the subscribeScheduler")
    }

通过observeOnsubscribeOn可以控制处理信号的线程,并且可以支持多次切换。

各种Scheduler

  • CurrentThreadScheduler(串行)。指代当前线程,若没有指定Schuduler,则默认使用。
  • MainScheduler(串行)。主线程,通常使用进行UI操作。在subscribeOn时更应该用做过优化的ConcurrentMainScheduler
  • SerialDispatchQueueScheduler(串行)。串行线程,主线程也是一种串行线程。
  • ConcurrentDispatchQueueScheduler(并发)。在初始化时也可以传入串行dispatch queue,也不会有任何问题。适用于需要在后台的工作。
  • OperationQueueScheduler(并发)。是NSOperationQueue的一种抽象示例。适用与单个任务量大的任务并行处理的情况,同时希望设定maxConcurrentOperationCount

思考

  1. 为什么说在subscribeOn时更应该用做过优化的ConcurrentMainScheduler
  2. 在阅读文档时,经常看到在observeOn时,如果指定了使用SerialDispatchQueueScheduler,会有所优化,具体是怎么优化的?

对于第二个问题,在阅读代码时,发现在observeOn时做了单独的处理

public func observeOn(_ scheduler: ImmediateSchedulerType)
    -> Observable<Element> {
        if let scheduler = scheduler as? SerialDispatchQueueScheduler {
            return ObserveOnSerialDispatchQueue(source: self.asObservable(), scheduler: scheduler)
        }
        else {
            return ObserveOn(source: self.asObservable(), scheduler: scheduler)
        }
}

对于非SerialDispatchQueueScheduler,会通过将事件以队列的方式以及加递归锁的方式存储。 而SerialDispatchQueueScheduler,则不会,减少了性能消耗。