深入理解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)
複製程式碼
這裡總是看到是Disposables
和DisposeBag
,那麼它們到底是什麼。
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 }
}
複製程式碼
-
NopDisposable
,在disposal時什麼都不做。 -
AnonymousDisposable
,建立時傳入一個public typealias DisposeAction = () -> Void
型別的閉包,在disposal時呼叫。 -
BinaryDisposable
,在建立時傳入兩個Disposable
型別的引數,在disposal時呼叫。 -
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。分別是BehaviorSubject
,PublishSubject
,ReplaySubject
,AsyncSubject
。
下列圖示中,圓代表next訊號,豎線表示complete,x表示error。
PublishSubject
就是一個熱訊號的Subject。
BehaviorSubject
是一個有初始值,快取數量為1的Subject。在5.0版本之前的RxSwift中,有一個叫做Variable
的屬性,在5.0之後,因為相同的特性,完全被BehaviorSubject
取代。
ReplaySubject
是一個可以自定義快取數量的Subject。當設定快取數量為0時,幾乎可以當做PublishSubject
使用,快取數量為1時,可以當做一個沒有初始值的BehaviorSubject
使用。
AsyncSubject
,只會在接收到Complete事件後,才會Subscribe最後一個訊號,如果沒有在onComplete之前沒有onNext事件,或者觸發了onError,則不會觸發Subscribe。
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")
}
複製程式碼
通過observeOn
與subscribeOn
可以控制處理訊號的執行緒,並且可以支援多次切換。
各種Scheduler
-
CurrentThreadScheduler
(序列)。指代當前執行緒,若沒有指定Schuduler,則預設使用。 -
MainScheduler
(序列)。主執行緒,通常使用進行UI操作。在subscribeOn時更應該用做過優化的ConcurrentMainScheduler
。 -
SerialDispatchQueueScheduler
(序列)。序列執行緒,主執行緒也是一種序列執行緒。 -
ConcurrentDispatchQueueScheduler
(併發)。在初始化時也可以傳入序列dispatch queue,也不會有任何問題。適用於需要在後臺的工作。 -
OperationQueueScheduler
(併發)。是NSOperationQueue
的一種抽象示例。適用與單個任務量大的任務並行處理的情況,同時希望設定maxConcurrentOperationCount
。
思考
- 為什麼說在subscribeOn時更應該用做過優化的
ConcurrentMainScheduler
? - 在閱讀文件時,經常看到在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
,則不會,減少了效能消耗。