一個RxCocoa簡單示例及其實現原理
RxCocoa是ReactiveX/RxSwift" target="_blank" rel="nofollow,noindex">RxSwift 的一部分,主要是UI相關的Rx封裝。比如實現了很多元件的繫結功能,可以把值跟控制元件之間相互繫結,可以避免寫很多通知、修改資料等程式碼。也可以監聽delegate改變,無須把控制元件建立及delegate處理分開寫等。
RxCocoa裡面也定義了很多類,專門為UI處理提供的,比如ControlProperty、ControlEvent 、Driver 、Binder等。RxCocoa可以用好的話,可以極大簡化UI相關處理邏輯。但是,要想隨心所欲的使用,還是要對其實現要有一定的瞭解,否則就容易寫出不是那麼簡潔的程式碼。
比如前幾天在一篇部落格上,看到的一段程式碼:
textfield.rx.text .asObservable() .subscribe { print($0) } .disposed(by: disposeBag)
這段程式碼一眼看過去,是沒什麼問題的,執行起來看起來也是ok的。
但是,這段程式碼的問題就是存在冗餘。具體為什麼冗餘,稍後再分析。先從簡單的示例瞭解RxCocoa
簡單示例
ReactiveX/RxSwift" target="_blank" rel="nofollow,noindex">RxSwift 的原始碼裡面有附帶示例程式碼,原始碼clone下來之後,開啟Rx.xcworkspace,即可以選擇示例執行看效果。
現在就拿最簡單的Numbers例子看下,(也可以線上看下這個最簡單示例程式碼:ReactiveX/RxSwift/blob/master/RxExample/RxExample/Examples/Numbers/NumbersViewController.swift" target="_blank" rel="nofollow,noindex">Numbers )
核心程式碼就這一段:
Observable.combineLatest(number1.rx.text.orEmpty, number2.rx.text.orEmpty, number3.rx.text.orEmpty) { textValue1, textValue2, textValue3 -> Int in return (Int(textValue1) ?? 0) + (Int(textValue2) ?? 0) + (Int(textValue3) ?? 0) } .map { $0.description } .bind(to: result.rx.text) .disposed(by: disposeBag)
這個示例是將三個輸入框的內容加起來,繫結在Label上,上面值變化之後,下面的Label立即跟著變化。
為了讓例子更簡單,我們可以只把label的值繫結在第一個輸入框上:
number1.rx.text.orEmpty .map { $0.description } .bind(to: result.rx.text) .disposed(by: disposeBag)
現在我們逐個分析下,這裡面的 rx、text、orEmpty、bind到底是什麼
rx
number1.rx是表示什麼呢?我們根據原始碼來推導一下
先看下rx屬性的原始碼實現:
public var rx: Reactive<Self> { get { return Reactive(self) } set { // this enables using Reactive to "mutate" base object } }
實際上是建立了一個Reactive物件,所以:
number1.rx===>Reactive(number1)
Reactive原始碼又如下:
public struct Reactive<Base> { /// Base object to extend. public let base: Base /// Creates extensions with base object. /// /// - parameter base: Base object. public init(_ base: Base) { self.base = base } }
所以number1.rx最終會變成如下程式碼:
public struct Reactive { public let base: UITextField// 指向number1 }
所以number1.rx是變成了Reactive結構體 ,此時,Reactive的擴充套件方法,我們就可以使用了。
所以,這個.rx是進入Rx世界的入口,控制元件呼叫.rx屬性之後,後面的內容就表示進入了Rx的世界了。
text
上面分析了,number1.rx是一個Reactive的結構體,它後面就可以繼續呼叫Reactive及其擴充套件的屬性和方法了。
(實際上number1.rx只能呼叫不限定的擴充套件的方法或者限定Base是UITextField型別的擴充套件方法)
所以,text屬性就是Reactive的擴充套件的屬性。這個屬性定義在UITextField+Rx.swift檔案中,下面是其簡化後的實現:
extension Reactive where Base: UITextField { public var text: ControlProperty<String?> { return value } public var value: ControlProperty<String?> { // 預設事件支援了allEditingEvents、valueChanged return base.rx.controlPropertyWithDefaultEvents( getter: { textField in// 要發出事件時,所需的資料,即是從這兒獲取 textField.text }, setter: { textField, value in// 作為監聽者時,收到資料時會調到這兒 if textField.text != value { textField.text = value } } ) } }
text簡單的呼叫了value屬性,它們是ControlProperty型別的。
ControlProperty是表示控制元件屬性,即能監聽變化,又能發出通知,即同時實現了ObservableType和ObserverType協議,所以控制元件才能支援雙向繫結 。
上面程式碼中的getter塊是在作為ObservableType時所使用的,setter塊是作為ObserverType所需要的。
具體ControlProperty實現就不展開了,可以在UIControl+Rx.swift檢視具體實現。
orEmpty
上面程式碼中的一個細節:
public var text: ControlProperty<String?>
這個value值傳出來之後是String?的,是個可選值。在很多情況下,並不想要可選值,只希望如果為nil時,傳””空字元串出來即可。否則可選值出來之後,外面可能處理為nil的情況處理起來會比較繁瑣。
orEmpty就是這個作用。
orEmpty的實現如下:
public var orEmpty: ControlProperty<String> { let original: ControlProperty<String?> = self.asControlProperty() let values: Observable<String> = original._values.map { $0 ?? "" } let valueSink: AnyObserver<String> = original._valueSink.mapObserver { $0 } return ControlProperty<String>(values: values, valueSink: valueSink) }
會根據目前的values構造一個新的values,並最終構造一個新的ControlProperty,去除可選值的情況。
bind
bind即是用來將一個訊號傳送者和一個訊號監聽者繫結在一起,即有信號傳送,監聽者自動收到通知。聽起來是跟subscribe做的事情比較類似。
bind(to:)有幾個不同的實現,最簡單的實現版本如下:
public func bind<O: ObserverType>(to observer: O) -> Disposable where O.E == E { return self.subscribe(observer) }
其實就是訂閱的封裝。可以理解為為subscribe起了一個在繫結場景下比較容易理解的名字。
在很多情況下,我們的observer並不需要處理error、complete事件,並且處理邏輯需要在主執行緒中執行。所以RxCocoa幫我們封裝好了一個叫Binder的物件,我們使用這個物件時,不需要考慮太多。
RxCocoa提供的一些監聽屬性,比如UILabel的rx.text屬性即是Binder型別的:
extension Reactive where Base: UILabel { public var text: Binder<String?> { return Binder(self.base) { label, text in label.text = text// 收到事件就會呼叫到這兒 } } }
結尾
這樣這個簡單示例就串起來了,並且能夠很明確每次呼叫在做什麼事情。
現在再看最開始的那段程式碼:
textfield.rx.text .asObservable() .subscribe { print($0) } .disposed(by: disposeBag)
我們知道textfield.rx.text是ControlProperty的型別,並且這個型別本身就實現了Observable協議的,那麼冗餘的程式碼就很明確了,即asObservable()這一次呼叫是沒意義的。