是時(shí)候?qū)W習(xí) RxSwift 了

是時(shí)候?qū)W習(xí) RxSwift 了

相信在過去的一段時(shí)間里,對(duì) RxSwift 多少有過接觸或耳聞,或者已經(jīng)積累了不少實(shí)戰(zhàn)經(jīng)驗(yàn)。此文主要針對(duì)那些在門口徘徊,想進(jìn)又拍踩坑的同學(xué)。

為什么要學(xué)習(xí) RxSwift

當(dāng)決定做一件事情時(shí),至少要知道為什么。RxSwift 官網(wǎng)舉了幾個(gè)例子,比如可以統(tǒng)一處理 Delegate, KVO, Notification,可以綁定 UI,方便網(wǎng)絡(luò)請(qǐng)求的處理等等。但這些更多的是描述可以用 RxSwift 來做什么,跟為什么要使用 RxSwift 還是會(huì)有點(diǎn)不同。

我們先來分析下 GUI 編程的本質(zhì),我喜歡把它抽象為視圖和數(shù)據(jù)的結(jié)合。其中視圖負(fù)責(zé)兩件事:展示和交互,展示什么由數(shù)據(jù)決定。

image

其中單向數(shù)據(jù)流可以通過之前介紹的 ReSwift 完成??雌饋砗孟駴] RxSwift 什么事情,其實(shí)不然,RxSwift 可以在 UniDirectional Data Flow 的各個(gè)階段都發(fā)揮作用,從而讓 Data 的處理和流動(dòng)更加簡潔和清晰。

image
  1. 通過對(duì) RxCocoa 的各種回調(diào)進(jìn)行統(tǒng)一處理,方便了「Interact」的處理。
  2. 通過對(duì) Observable 的 transform 和 composite,方便了 Action 的生成(比如使用 throttle 來壓縮 Action)。
  3. 通過對(duì)網(wǎng)絡(luò)請(qǐng)求以及其他異步數(shù)據(jù)的獲取進(jìn)行 Observable 封裝,方便了異步數(shù)據(jù)的處理。
  4. 通過 RxCocoa 的 binding,方便了數(shù)據(jù)的渲染。

所以 ReSwift 規(guī)范了數(shù)據(jù)流,RxSwift 為數(shù)據(jù)的處理提供了方便,這兩個(gè)類庫的結(jié)合,可以產(chǎn)生清晰的架構(gòu)和易維護(hù)的代碼。

當(dāng)然,前提是對(duì)它們有足夠的了解,尤其是 RxSwift,也就是我們今天的主角。

什么是 RxSwift

在 GUI 編程中,我認(rèn)為比較復(fù)雜的有三個(gè)部分:

  1. 非原生 UI 效果的實(shí)現(xiàn)(比如產(chǎn)品經(jīng)理們經(jīng)常冒出來的各種想法)。
  2. 大量狀態(tài)的維護(hù)。
  3. 異步數(shù)據(jù)的處理。

1)不在這次的討論范疇(這里的學(xué)問也很多,比如流暢性和性能)。2) 可以通過單向數(shù)據(jù)流來解決(結(jié)合 Immutable Data)。3) 可以通過 RxSwift 來解決。那么 RxSwift 是如何處理異步數(shù)據(jù)的呢?

在說 RxSwift 之前,先來說下 Rx, ReactiveX 是一種編程模型,最初由微軟開發(fā),結(jié)合了觀察者模式、迭代器模式和函數(shù)式編程的精華,來更方便地處理異步數(shù)據(jù)流。其中最重要的一個(gè)概念是 Observable

舉個(gè)簡單的例子,當(dāng)別人在跟你說話時(shí),你就是那個(gè)觀察者,別人就是那個(gè) Observable,它有幾個(gè)特點(diǎn):

  1. 可能會(huì)不斷地跟你說話。(onNext:
  2. 可能會(huì)說錯(cuò)話。(onError:
  3. 結(jié)束會(huì)說話。(onCompleted

你在聽到對(duì)方說的話后,也可以有幾種反應(yīng):

  1. 根據(jù)說的話,做相應(yīng)的事,比如對(duì)方讓你借本書給他。(subscribe
  2. 把對(duì)方說的話,加工下再傳達(dá)給其他人,比如對(duì)方說小張好像不太舒服,你傳達(dá)給其他人時(shí)就變成了小張失戀了。(map:
  3. 參考其他人說的話再做處理,比如 A 說某家店很好吃,B 說某家店一般般,你需要結(jié)合兩個(gè)人的意見再做定奪。(zip:

所以,從生活中也能看到 Rx 的影子?!赣行┦虑榧辈坏茫愕玫人约菏臁?,異步,其實(shí)就是跟時(shí)間打交道,不同的時(shí)間,拿到的數(shù)據(jù)也會(huì)不一樣。可以在線感受下

image

這里的核心是當(dāng)數(shù)據(jù)有變化時(shí),能夠立刻知曉,并且通過組合和轉(zhuǎn)換后,可以即時(shí)作出響應(yīng)。有點(diǎn)像塔防,先在路上的各個(gè)節(jié)點(diǎn)埋好武器,然后等著小怪獸們過來。

RxSwift Workflow

大致分為這么幾個(gè)階段:先把 Native Object 變成 Observable,再通過 Observable 內(nèi)置的各種強(qiáng)大的轉(zhuǎn)換和組合能力變成新的 Observable,最后消費(fèi)新的 Observable 的數(shù)據(jù)。

image

Native Object -> Observable

.rx extension

假設(shè)需要處理點(diǎn)擊事件,正常的做法是給 Tap Gesture 添加一個(gè) Target-Action,然后在那里實(shí)現(xiàn)具體的邏輯,這樣的問題在于需要重新取名字,而且丟失了上下文。RxSwift (確切說是 RxCocoa) 給系統(tǒng)的諸多原生控件(包括像 URLSession)提供了 rx 擴(kuò)展,所以點(diǎn)擊的處理就變成了這樣:

let tapBackground = UITapGestureRecognizer()

tapBackground.rx.event
    .subscribe(onNext: { [weak self] _ in
        self?.view.endEditing(true)
    })
    .addDisposableTo(disposeBag)

view.addGestureRecognizer(tapBackground)

是不是簡潔了很多。

Observable.create

通過這個(gè)方法,可以將 Native 的 object 包裝成 Observable,比如對(duì)網(wǎng)絡(luò)請(qǐng)求的封裝:

public func response(_ request: URLRequest) -> Observable<(Data, HTTPURLResponse)> {
    return Observable.create { observer in
        let task = self.dataTaskWithRequest(request) { (data, response, error) in
            observer.on(.next(data, httpResponse))
            observer.on(.completed)
        }

        task.resume()

        return Disposables.create {
            task.cancel()
        }
    }
}

出于代碼的簡潔,略去了對(duì) error 的處理,使用姿勢類似

let disposeBag = DisposeBag()

response(aRequest)
  .subscribe(onNext: { data in
    print(data)
  })
  .addDisposableTo(disposeBag)

這里有兩個(gè)注意點(diǎn):

  1. Observerable 返回的是一個(gè) Disposable,表示「可扔掉」的,扔哪里呢,就扔到剛剛創(chuàng)建的袋子里,這樣當(dāng)袋子被回收(dealloc)時(shí),會(huì)順便執(zhí)行一下 Disposable.dispose(),之前創(chuàng)建 Disposable 時(shí)申請(qǐng)的資源就會(huì)被一并釋放掉。
  2. 如果有多個(gè) subscriber 來 subscribe response(aRequest) 那么會(huì)創(chuàng)建多個(gè)請(qǐng)求,從代碼也可以看得出來,來一個(gè) observer 就創(chuàng)建一個(gè) task,然后執(zhí)行。這很有可能不是我們想要的,如何讓多個(gè) subscriber 共享一個(gè)結(jié)果,這個(gè)后面會(huì)提到。
Variable()

Variable(value) 可以把 value 變成一個(gè) Observable,不過前提是使用新的賦值方式 aVariable.value = newValue,來看個(gè) Demo

let magicNumber = 42

let magicNumberVariable = Variable(magicNumber)
magicNumberVariable.asObservable().subscribe(onNext: {
    print("magic number is \($0)")
})

magicNumberVariable.value = 73

// output
// 
// magic number is 42
// magic number is 73

起初看到時(shí),覺得還蠻神奇的,跟進(jìn)去看了下,發(fā)現(xiàn)是通過 subject 來做的,大意是把 value 存到一個(gè)內(nèi)部變量 _value 里,當(dāng)調(diào)用 value 方法時(shí),先更新 _value 值,然后調(diào)用內(nèi)部的 _subject.on(.next(newValue)) 方法告知 subscriber。

Subject

Subject 簡單來說是一個(gè)可以主動(dòng)發(fā)射數(shù)據(jù)的 Observable,多了 onNext(value), onError(error), ‘onCompleted’ 方法,可謂全能型選手。

let disposeBag = DisposeBag()
let subject = PublishSubject<String>()

subject.addObserver("1").addDisposableTo(disposeBag)
subject.onNext("??")
subject.onNext("??")

subject.addObserver("2").addDisposableTo(disposeBag)
subject.onNext("???")
subject.onNext("???")

記得在 RAC 時(shí)代,subject 是一個(gè)不太推薦使用的功能,因?yàn)檫^于強(qiáng)大了,容易失控。RxSwift 里倒是沒有太提及,但還是少用為佳。

Observable -> New Observable

Observable 的強(qiáng)大不僅在于它能實(shí)時(shí)更新 value,還在于它能被修改/過濾/組合等,這樣就能隨心所欲地構(gòu)造自己想要的數(shù)據(jù),還不用擔(dān)心數(shù)據(jù)發(fā)生變化了卻不知道的情況。

Combine

Combine 就是把多個(gè) Observable 組合起來使用,比如 zip (小提示:如果對(duì)這些函數(shù)不太敏感,可以實(shí)際操作下,體會(huì)會(huì)更深些)

zip 對(duì)應(yīng)現(xiàn)實(shí)中的例子就是拉鏈,拉鏈需要兩個(gè)元素這樣才能拉上去,這里也一樣,只有當(dāng)兩個(gè) Observable 都有了新的值時(shí),subscribe 才會(huì)被觸發(fā)。

let stringSubject = PublishSubject<String>()
let intSubject = PublishSubject<Int>()

Observable.zip(stringSubject, intSubject) { stringElement, intElement in
    "\(stringElement) \(intElement)"
    }
    .subscribe(onNext: { print($0) })
    .addDisposableTo(disposeBag)

stringSubject.onNext("???")
stringSubject.onNext("???")

intSubject.onNext(1)
intSubject.onNext(2)

// output
//
// ??? 1
// ??? 2

如果這里 intSubject 始終沒有執(zhí)行 onNext,那么將不會(huì)有輸出,就像拉鏈掉了一邊的鏈子就拉不上了。

除了 zip,還有其他的 combine 的姿勢,比如 combineLatest / switchLatest 等。

Transform

這是最常見的操作了,對(duì)一個(gè) Observable 的數(shù)值做一些小改動(dòng),然后產(chǎn)出新的值,依舊是一個(gè) Observable。

let disposeBag = DisposeBag()
Observable.of(1, 2, 3)
    .map { $0 * $0 }
    .subscribe(onNext: { print($0) })
    .addDisposableTo(disposeBag)

這是大致的實(shí)現(xiàn)(摘自官網(wǎng))

extension ObservableType {
    func myMap<R>(transform: E -> R) -> Observable<R> {
        return Observable.create { observer in
            let subscription = self.subscribe { e in
                    switch e {
                    case .next(let value):
                        let result = transform(value)
                        observer.on(.next(result))
                    case .error(let error):
                        observer.on(.error(error))
                    case .completed:
                        observer.on(.completed)
                    }
                }

            return subscription
        }
    }
}

接受一個(gè) transform 閉包,然后返回一個(gè) Observable,因?yàn)榻酉聛硎褂谜邔?huì)對(duì) myMap 的結(jié)果進(jìn)行 subscribe,所以需要在 create 內(nèi)部 subscribe 一下,不然最開始的那個(gè) Observable 就是個(gè) Cold Observable,一個(gè) Cold Observable 是不會(huì)產(chǎn)生新的數(shù)據(jù)的。

Filter

Filter 的作用是對(duì) Observable 傳過來的數(shù)據(jù)進(jìn)行過濾,只有符合條件的才有資格被 subscribe。寫法上跟 map 差不多,就不贅述了。

Connect

這是挺有意思的一塊,在之前介紹 Observable.create 時(shí)有提到過,一個(gè) Observable 被多次 subscribe 就會(huì)被多次觸發(fā),如果一個(gè)網(wǎng)絡(luò)請(qǐng)求只想被觸發(fā)一次,同時(shí)支持多個(gè) subscriber,就可以使用 publish + connect 的組合。

當(dāng)一個(gè) Observable 使用了 publish() 方法后,正常的 subscribe 就不會(huì)觸發(fā)它了,除非 connect() 方法被調(diào)用。而且每次 subscribe 不會(huì)導(dǎo)致 Observable 重新針對(duì) observer 處理一遍??匆幌逻@張圖

image

有兩塊需要注意:

  1. connect() 之前的兩次 subscribe 并沒有產(chǎn)生新的 value。
  2. connect() 之后 subscribe 的,只是等待新的 value,同時(shí)新的 value 還會(huì)分發(fā)給之前的 subscriber。
  3. 即使所有的 subscriptiondispose, Observable 依舊處于 hot 狀態(tài),就好像還以為有人關(guān)心新的值一樣。(這可能不是想要的結(jié)果)

針對(duì)第 3 點(diǎn),可以使用 refcount() 來代替 connect(),前者會(huì)在沒有 subscriber 時(shí)自動(dòng)「冷」下來,不會(huì)再產(chǎn)生新的值。(Demo 取自這里

let myObservable = Observable<Int>.interval(1, scheduler: MainScheduler.instance).publish().refCount() // 1)

let mySubscription = myObservable.subscribe(onNext: {
    print("Next: \($0)")
})

delay(3) {
    print("Disposing at 3 seconds")
    mySubscription.dispose()
}

delay(6) {
    print("Subscribing again at 6 seconds")
    myObservable.subscribe(onNext: {
        print("Next: \($0)")
    })
}

輸出

Starting at 0 seconds
Next: 0
Next: 1
Next: 2
Disposing at 3 seconds
Subscribing again at 6 seconds
Next: 0
Next: 1

可以看到,3 秒后 subscription dispose,此時(shí)沒有任何 subscriber 還關(guān)心 Observable,因此就重置了,所以 6 秒后又回到了初始狀態(tài)(如果變成 connect 方法的話,會(huì)發(fā)現(xiàn) 6 秒后會(huì)輸出 Next: 6 / Next: 7

那如果后加入的 subscriber 想要之前的數(shù)據(jù)怎么辦?可以對(duì)原始的 Observable 設(shè)置 replay(n),表示最多返回 n 個(gè)元素給后加入的 subscriber。

Tips

上面介紹的是最基本的概念。順便提一下比較常見的幾個(gè)問題:

如何處理 Scheduler?

默認(rèn)代碼都是在當(dāng)前線程中執(zhí)行的,如果要手動(dòng)切換線程,可以使用 subsribeOnobserveOn 兩種方式,一般來說后者用得會(huì)多一些,那這兩者有什么區(qū)別呢?

subscribeOn 跟位置無關(guān),也就是無論在鏈?zhǔn)秸{(diào)用的什么地方,Observablesubscription 都會(huì)受影響;而 observeOn 則僅對(duì)之后的調(diào)用產(chǎn)生影響,看個(gè) Demo:

var observable = Observable<Int>.create { (observer: AnyObserver<Int>) -> Disposable in
    print("observable thread: \(Thread.current)")
    observer.onNext(1)
    observer.onCompleted()
    return Disposables.create()
}

let disposeBag = DisposeBag()

observable
    .map({ (e) -> Int in
        print("map1 thread: \(Thread.current)")
        return e + 1
    })
    .observeOn(ConcurrentDispatchQueueScheduler(qos: .userInteractive)) // 1
    .map({ (e) -> Int in
        print("map2 thread: \(Thread.current)")
        return e + 2
    })
    .subscribe(onNext:{ (e) -> Void in
        print("subscribe thread: \(Thread.current)")
    })
    .addDisposableTo(disposeBag)

如果 1) 是 observeOn,那么輸出如下

observable thread: <NSThread: 0x7f901cc0d510>{number = 1, name = main}
map1 thread: <NSThread: 0x7f901cc0d510>{number = 1, name = main}
map2 thread: <NSThread: 0x7f901ce15560>{number = 3, name = (null)}
subscribe thread: <NSThread: 0x7f901ce15560>{number = 3, name = (null)}

可以看到 observable thread 和 map1 thread 依舊保持當(dāng)前線程,但 observeOn 之后就變成了另一個(gè)線程。

如果 1) 是 subscribeOn,那么會(huì)輸出

observable thread: <NSThread: 0x7fbdf1e097a0>{number = 3, name = (null)}
map1 thread: <NSThread: 0x7fbdf1e097a0>{number = 3, name = (null)}
map2 thread: <NSThread: 0x7fbdf1e097a0>{number = 3, name = (null)}
subscribe thread: <NSThread: 0x7fbdf1e097a0>{number = 3, name = (null)}

可以看到全都變成了 subscribeOn 指定的 Queue。所以 subscribeOn 的感染力很強(qiáng),連 Observable 都能影響到。

Cold Observable 和 Hot Observable

Cold 相當(dāng)于 InActive,就像西部世界里,未被激活的機(jī)器人一樣;Hot 就是處于工作狀態(tài)的機(jī)器人。

Subscription 為什么要 Dispose?

因?yàn)橛辛?Subscriber 所以 Observable 被激活,然后內(nèi)部就會(huì)使用各種變量來保存資源,如果不 dispose 的話,這些資源就會(huì)一直被 keep,很容易造成內(nèi)存泄漏。

同時(shí)手動(dòng) dispose 又嫌麻煩,所以就有了 DisposeBag,當(dāng)這個(gè) Bag 被回收時(shí),Bag 里面的 subscription 會(huì)自動(dòng)被 dispose,相當(dāng)于從 MRC 變成了 ARC。

小結(jié)

RxSwift 如果概念上整理清楚了,會(huì)發(fā)現(xiàn)其實(shí)并不難,多從 Observable 的角度去思考問題,多想著轉(zhuǎn)換和組合,慢慢就會(huì)從命令式編程轉(zhuǎn)到聲明式編程,對(duì)于抽象能力和代碼的可讀性都會(huì)有提升。

--EOF--

轉(zhuǎn)自http://limboy.me/tech/2016/12/11/time-to-learn-rxswift.html

make the world a little better and easier

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容