1. 程式人生 > IOS開發 >深入理解RxSwift

深入理解RxSwift

簡介

本篇重點在於深入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,則不會,減少了效能消耗。