RxSwift 上手詳解 —— 入門篇(翻譯三)

前言


看了前幾篇關(guān)于RxSwift主要概念的文章格粪,會對RxSwift有個大致的了解躏吊,這篇文章會詳細(xì)講述如何使用RxSwift氛改,主要的語法,仔細(xì)地看完這篇文章就可以用RxSwift開始寫app了比伏。

相關(guān)文章鏈接:

  1. RxSwift中的Observable
  2. RxSwift中的Subject

正文



Observables(也可以理解成Sequences)


基礎(chǔ)


理解觀察者模式(observer pattern)和普通序列(sequence)本質(zhì)上是等價的是理解整個Rx思想很重要的一步胜卤。

每一個Observable序列其實都只是一個序列而已。Observable相比Swift自帶的SequenceType優(yōu)勢在于赁项,它可以異步地接收元素或者對象葛躏。這是整個RxSwift的核心,整個文檔講的內(nèi)容都是關(guān)于這個核心思想的延伸悠菜。

  • Observable(ObservableType)等價于SequenceType
  • ObservableType.subscribe方法等價于SequenceType.generate方法
  • Observer(也就是回調(diào)函數(shù))需要傳遞給ObservableType.subscribe方法來獲取序列里的元素舰攒,而不是對序列產(chǎn)生器直接調(diào)用next()方法

序列是一個簡單熟悉的概念,很容易對它可視化悔醋。

人類是一種視覺皮層很大的生物摩窃。如果把一個概念可視化,會比較容易地對它進(jìn)行分析和理解芬骄。

我們對于Rx的認(rèn)知偶芍,可以從模擬每一個Rx操作符內(nèi)部的單個事件的狀態(tài)提升到對整個序列的整體的操作。

如果我們不用Rx而去使用普通的異步系統(tǒng)德玫,那我們的代碼可能會充滿了各種狀態(tài)機(jī)和短暫的狀態(tài)匪蟀,我們需要模擬這些所有的狀態(tài),而不能把它們從一個更高的層面抽象出來宰僧。

列表和序列可能是很多數(shù)學(xué)家和程序員學(xué)習(xí)到的第一個概念材彪。

這是一個數(shù)字序列:

--1--2--3--4--5--6--| // 正常地中斷

字符序列

--a--b--a--a--a---d---X // 被一個error中斷

一些序列是有限的,一些序列式無限的琴儿,想一個按按鈕操作序列

---tap-tap-------tap--->

上面這些示意圖稱為彈子圖(marble diagram)段化,你可以在這個網(wǎng)站上看到很多類似的示意圖 rxmarbles.com

如果你你想把序列語法寫成一個正則表達(dá)式造成,那看起來可能是像這樣子的:
next (error | completed)?*

這個正則表達(dá)式的意思就是:

  • 序列有大于等于0個元素
  • 一旦接收到error 或者 completed 事件显熏,序列就不會在產(chǎn)生其他元素

Rx中的序列可以描述成是一種推送接口(push interface)(可以理解成回調(diào)函數(shù))

enum Event<Element>  {
    case next(Element)      // 序列的下一個元素
    case error(Swift.Error) // 序列由error中斷
    case completed          // 序列成功地
}

class Observable<Element> {
    func subscribe(_ observer: Observer<Element>) -> Disposable
}

protocol ObserverType {
    func on(_ event: Event<Element>)
}

但一個序列發(fā)送 completed 或者 error 事件時,所有內(nèi)部用來計算這個事件的資源都會被釋放

如果想要讓序列不再產(chǎn)生新的元素并且立即釋放所有資源晒屎,可以對返回的訂閱(subscription)調(diào)用dispose

如果一個序列是有限序列喘蟆,不調(diào)用dispose 或者 addDisposableTo(disposeBag) 不會引發(fā)永久的內(nèi)存泄漏。 但是直到這個序列完成或者被error中斷之前鼓鲁,這些資源都會處于使用狀態(tài)蕴轨。

如果那個序列因為某種原因一直不中斷,資源將會一直處于占用狀態(tài)骇吭,除非手動調(diào)用 dispose方法橙弱,使用disposeBagtakeUntil 或者其他的一些方法,也可以自動釋放序列占用的資源棘脐。

使用 dispose bags 或者 takeUntil 操作符是比較好的清理資源的的方法斜筐。我們推薦在開發(fā)產(chǎn)品過程中使用這兩種方法即使那些序列是有限序列。

清理(Disposing)


另外還有一個可以中斷序列的辦法蛀缝。但我們使用完一個序列之后我們想要釋放所有的資源去計算以后的元素奴艾,我們可以對訂閱(subscription)調(diào)用dispose

這里是一個關(guān)于 interval 操作符的例子:

let subscription = Observable<Int>.interval(0.3, scheduler: scheduler)
    .subscribe { event in
        print(event)
    }

NSThread.sleep(forTimeInterval: 2.0)

subscription.dispose()

This will print:

0
1
2
3
4
5

這僅僅是一個示例内斯,通常情況下你不用調(diào)用dispose,手動調(diào)用這個方法是一種不太好的代碼習(xí)慣像啼。更好的辦法是使用 DisposeBag俘闯, takeUntil 操作符或者一些其他機(jī)制。

dispose方法被執(zhí)行之后這段代碼還會繼續(xù)打印嗎忽冻?答案是:不一定真朗。

  • 如果 scheduler 是一個 serial scheduler (比如說 MainScheduler) 并且 dispose 在同一個 serial scheduler 被調(diào)用,答案是 不會僧诚。

  • 否則答案是會的遮婶。

你可以在這里了解更多有關(guān) Scheduler 的信息。

你有兩個處理在同時運行湖笨。

  • 一個在產(chǎn)生元素
  • 另一個在清理訂閱

當(dāng)這些運算在不同的scheduler里進(jìn)行的時候旗扑,“清理之后會繼續(xù)打印嗎?”這種問題是沒有意義的慈省。

再舉幾個類似的例子 (observeOn 在這里有比較詳細(xì)的解釋)

如果我們有這樣一段代碼:

let subscription = Observable<Int>.interval(0.3, scheduler: scheduler)
            .observeOn(MainScheduler.instance)
            .subscribe { event in
                print(event)
            }

// ....

subscription.dispose() // 在主線程調(diào)用這個函數(shù)

dispose調(diào)用返回之后臀防,就肯定不會再打印任何東西了

同樣的,在下面這個例子中:

let subscription = Observable<Int>.interval(0.3, scheduler: scheduler)
            .observeOn(serialScheduler)
            .subscribe { event in
                print(event)
            }

// ...

subscription.dispose() // 在同樣的`serialScheduler`執(zhí)行

dispose 調(diào)用返回之后边败,也不會再打印任何東西

清理袋(Dispose Bags)

Dispose bags 在Rx中的作用跟ARC類似袱衷。

當(dāng)一個 DisposeBag 被釋放時,它會對內(nèi)部每一個可以被清理的元素調(diào)用 dispose笑窜。

DisposeBag 沒有 dispose 方法致燥,所以不允許顯示地調(diào)用這個方法。如果你需要立即的清理資源排截,可以創(chuàng)建一個新的DisposeBag嫌蚤。

  self.disposeBag = DisposeBag()

這樣會清理舊的引用并且清理資源。

如果仍然需要手動調(diào)用清理断傲,可以用 CompositeDisposable. 它有著跟DisposeBag一樣的功能搬葬,但是一旦dispose方法被調(diào)用,這個方法將會立刻清理新加入的待清理的東西

Take until

另一種自動清理訂閱的辦法是用 takeUntil 操作符艳悔。

sequence
    .takeUntil(self.rx.deallocated)
    .subscribe {
        print($0)
    }

隱性的 Observable 規(guī)定

有一些所有序列生產(chǎn)者 (Observables) 必須遵守的規(guī)定急凰。

在哪一個線程產(chǎn)生元素并不重要,但如果一個元素產(chǎn)生并發(fā)送給observer observer.on(.next(nextElement)),直到observer.on 方法完成前一個操作才能發(fā)送下一個元素抡锈。

.next 事件沒有完成的情況下疾忍,發(fā)送者也不能發(fā)送 .completed 或者 .error 事件。

舉個例子來說:

someObservable
  .subscribe { (e: Event<Element>) in
      print("Event processing started")
      // processing
      print("Event processing ended")
  }

將會一直打哟踩:

Event processing started
Event processing ended
Event processing started
Event processing ended
Event processing started
Event processing ended

而不會打右徽帧:

Event processing started
Event processing started
Event processing ended
Event processing ended

創(chuàng)造你自己的 Observable (也被稱作 Observable Sequence)

理解 observables 有非常重要的一點。

當(dāng)一個observable剛剛被創(chuàng)建出來的時候撇簿,不做任何工作聂渊,因為observable已經(jīng)被創(chuàng)造了。

Observable 會通過很多辦法產(chǎn)生元素四瘫。其中的一些會引起副作用汉嗽,一些會發(fā)生在已經(jīng)運行的程序里,想鼠標(biāo)點擊事件等等

但是如果你點用了一個方法并且返回了一個 Observable, 沒有序列會產(chǎn)生找蜜,也沒有副作用會產(chǎn)生饼暑。Observable 僅僅是一個定義,定義序列怎么生產(chǎn)的洗做,要用什么參數(shù)去產(chǎn)生元素弓叛。序列真正開始產(chǎn)生是在 subscribe 方法被調(diào)用之后。

比如:我們有一個方法有這樣的原型:

func searchWikipedia(searchTerm: String) -> Observable<Results> {}
let searchForMe = searchWikipedia("me")

// 此時不發(fā)送任何URL請求诚纸,不作任何工作

let cancel = searchForMe
  // 發(fā)送URL請求撰筷,開始產(chǎn)生序列
  .subscribe(onNext: { results in
      print(results)
  })

創(chuàng)建 Observable 序列有很多種方法。最簡單的方法可能是用create 函數(shù).

我們可以寫一個函數(shù)畦徘,這個函數(shù)創(chuàng)建一個返回一個被訂閱元素的序列闭专。 這個函數(shù)我們成為 'just' 函數(shù)。

這段代碼是真實的代碼

func myJust<E>(element: E) -> Observable<E> {
    return Observable.create { observer in
        observer.on(.next(element))
        observer.on(.completed)
        return Disposables.create()
    }
}

myJust(0)
    .subscribe(onNext: { n in
      print(n)
    })

會打泳缮铡:

0

那么什么是 create 函數(shù)呢影钉?

create 函數(shù)僅僅是一個用起來比較方便的函數(shù),它能夠簡單地用Swift的閉包實現(xiàn)訂閱 subscribe 方法掘剪。'subscribe' 方法接受一個 observer 參數(shù)平委,返回可清理的訂閱對象。

用這種方式實現(xiàn)的序列是同步的夺谁,這種序列產(chǎn)生元素并且中斷之后 subscribe 方法才會開始調(diào)用然后返回可清理的訂閱對象廉赔。因為這個原因,返回怎么樣的可清理對象并不重要匾鸥,產(chǎn)生元素的過程不能被打斷蜡塌。

對于同步序列來說,最常見的返回的disposable就是 NopDisposable 的signleton實例勿负。

讓我們創(chuàng)建一個可以返回數(shù)組中元素的 observable馏艾。

這段代碼是真實的代碼

func myFrom<E>(sequence: [E]) -> Observable<E> {
    return Observable.create { observer in
        for element in sequence {
            observer.on(.next(element))
        }

        observer.on(.completed)
        return Disposables.create()
    }
}

let stringCounter = myFrom(["first", "second"])

print("Started ----")

// first time
stringCounter
    .subscribe(onNext: { n in
        print(n)
    })

print("----")

// again
stringCounter
    .subscribe(onNext: { n in
        print(n)
    })

print("Ended ----")

會打印:

Started ----
first
second
----
first
second
Ended ----

創(chuàng)建一個可以工作的 Observable

事情變得越來越有趣了,讓我們寫一個之前的例子中用到的 interval 操作符琅摩。

這是用 dispatch queue 實現(xiàn)的等價的代碼

func myInterval(interval: NSTimeInterval) -> Observable<Int> {
    return Observable.create { observer in
        print("Subscribed")
        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
        let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)

        var next = 0

        dispatch_source_set_timer(timer, 0, UInt64(interval * Double(NSEC_PER_SEC)), 0)
        let cancel = Disposables.create {
            print("Disposed")
            dispatch_source_cancel(timer)
        }
        dispatch_source_set_event_handler(timer, {
            if cancel.isDisposed {
                return
            }
            observer.on(.next(next))
            next += 1
        })
        dispatch_resume(timer)

        return cancel
    }
}
let counter = myInterval(0.1)

print("Started ----")

let subscription = counter
    .subscribe(onNext: { n in
       print(n)
    })


NSThread.sleep(forTimeInterval: 0.5)

subscription.dispose()

print("Ended ----")

會打犹酢:

Started ----
Subscribed
0
1
2
3
4
Disposed
Ended ----

如果你寫成:

let counter = myInterval(0.1)

print("Started ----")

let subscription1 = counter
    .subscribe(onNext: { n in
       print("First \\(n)")
    })
let subscription2 = counter
    .subscribe(onNext: { n in
       print("Second \\(n)")
    })

NSThread.sleep(forTimeInterval: 0.5)

subscription1.dispose()

NSThread.sleep(forTimeInterval: 0.5)

subscription2.dispose()

print("Ended ----")

會打印:

Started ----
Subscribed
Subscribed
First 0
Second 0
First 1
Second 1
First 2
Second 2
First 3
Second 3
First 4
Second 4
Disposed
Second 5
Second 6
Second 7
Second 8
Second 9
Disposed
Ended ----

每一個訂閱者只訂閱跟它自己有關(guān)的元素序列房资。操作符默認(rèn)是沒有狀態(tài)的蜕劝。沒有狀態(tài)的操作符遠(yuǎn)遠(yuǎn)比有狀態(tài)的操作符要多。

分享訂閱和 shareReplay 操作符

但是如果你想要多個observers分享來自同一個訂閱的事件或者元素呢轰异?

有兩件事需要去定義岖沛。

  • 怎么處理在新的訂閱者開始觀察序列之前已經(jīng)接收到的元素(只重播最近的一個元素?重播之前所有的元素搭独?還是重播之前n個元素婴削?)
  • 怎么決定什么時候去開啟共享的訂閱(refCount, 手動開啟戳稽,或者別其他的算法)

常用的選擇是 replay(1).refCount() 的結(jié)合,也就是我們之前提到的 shareReplay().

let counter = myInterval(0.1)
    .shareReplay(1)

print("Started ----")

let subscription1 = counter
    .subscribe(onNext: { n in
       print("First \\(n)")
    })
let subscription2 = counter
    .subscribe(onNext: { n in
       print("Second \\(n)")
    })

NSThread.sleepForTimeInterval(0.5)

subscription1.dispose()

NSThread.sleepForTimeInterval(0.5)

subscription2.dispose()

print("Ended ----")

會打悠谏:

Started ----
Subscribed
First 0
Second 0
First 1
Second 1
First 2
Second 2
First 3
Second 3
First 4
Second 4
First 5
Second 5
Second 6
Second 7
Second 8
Second 9
Disposed
Ended ----

注意到與之前那個例子結(jié)果的變化惊奇,現(xiàn)在僅有一個 Subscribed 和一個 Disposed 事件。

URL observables 的行為也是相同的播赁。

這就是HTTP請求在Rx里被封裝的過程颂郎,模式和 interval 操作符非常像。

extension Reactive where Base: NSURLSession {
    public func response(request: NSURLRequest) -> Observable<(NSData, NSURLResponse)> {
        return Observable.create { observer in
            let task = self.dataTaskWithRequest(request) { (data, response, error) in
                guard let response = response, data = data else {
                    observer.on(.error(error ?? RxCocoaURLError.Unknown))
                    return
                }

                guard let httpResponse = response as? NSHTTPURLResponse else {
                    observer.on(.error(RxCocoaURLError.nonHTTPResponse(response: response)))
                    return
                }

                observer.on(.next(data, httpResponse))
                observer.on(.completed)
            }

            task.resume()

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

操作符

RxSwift實現(xiàn)了很多不同的操作符容为。完成的列表可以在這里找到乓序。

所有操作符的彈子示意圖可以在 ReactiveX找到。

幾乎所有操作符的使用演示都可以在 Playgrounds 找到坎背。

想要使用 playgrounds替劈,請先打開 Rx.xcworkspace, build RxSwift-OSX scheme 然后在 Rx.xcworkspace 的樹狀視圖里打開 playgrounds。

如果你需要一個操作符得滤,但不知道用哪個陨献,你可以用這張表幫你找到合適的操作符 decision tree of operators

Supported RxSwift operators 同樣也把函數(shù)以功能分類懂更,可以幫助你使用眨业。

自定義操作符

可以通過兩種方式創(chuàng)建自定義的操作符。

簡單的方法

所有內(nèi)部的代碼使用了操作符的高度優(yōu)化版本沮协,所以這些代碼不是很好的學(xué)習(xí)材料龄捡,我們推薦使用標(biāo)準(zhǔn)操作符。

幸運的是有一種更簡單地創(chuàng)建操作符的辦法慷暂。創(chuàng)建操作符本質(zhì)上就是創(chuàng)建 observable 的過程聘殖,前幾個段落中我們已經(jīng)描述了如何創(chuàng)建的過程。

讓我們看一下一個沒有優(yōu)化的 map 操作符是怎么實現(xiàn)的。

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
        }
    }
}

現(xiàn)在你可以用你自己的 map

let subscription = myInterval(0.1)
    .myMap { e in
        return "This is simply \\(e)"
    }
    .subscribe(onNext: { n in
        print(n)
    })

會打泳徒铩:

Subscribed
This is simply 0
This is simply 1
This is simply 2
This is simply 3
This is simply 4
This is simply 5
This is simply 6
This is simply 7
This is simply 8
...

實際的運用

如果你覺得處理自定義操作符中的一些情況有些很難處理的情況悍募,你可以選擇先不用Rx monad
先回到命令式語言完成你想要完成的工作,然后再用 Subject 把結(jié)果返回到 Rx洋机。

這不是非常好的寫代碼的方法坠宴,不應(yīng)該經(jīng)常使用,但你可以這樣寫绷旗。

  let magicBeings: Observable<MagicBeing> = summonFromMiddleEarth()

  magicBeings
    .subscribe(onNext: { being in     // exit the Rx monad  
        self.doSomeStateMagic(being)
    })
    .addDisposableTo(disposeBag)

  //
  //  Mess
  //
  let kitten = globalParty(   // calculate something in messy world
    being,
    UIApplication.delegate.dataSomething.attendees
  )
  kittens.on(.next(kitten))   // send result back to rx
  //
  // Another mess
  //

  let kittens = Variable(firstKitten) // again back in Rx monad

  kittens.asObservable()
    .map { kitten in
      return kitten.purr()
    }
    // ....

當(dāng)你這樣寫代碼的時候喜鼓,別人可能在其他地方寫了這樣的代碼,所以盡量不要用上面的方式寫代碼衔肢。

  kittens
    .subscribe(onNext: { kitten in
      // so something with kitten
    })
    .addDisposableTo(disposeBag)

Playgrounds

如果你不確定有一些操作符到底怎么工作庄岖,playgrounds 里面包含了幾乎所有操作符的演示例子,展示每個操作符的行為角骤。

用 playground 的時候先打開 Rx.xcworkspace, build RxSwift-OSX scheme 然后在Rx.xcworkspace 樹狀視圖中打開 playground隅忿、

如果要看每個例子的結(jié)果,可以打開 Assistant Editor邦尊。點擊 View > Assistant Editor > Show Assistant Editor 可以打開 Assistant Editor背桐。

Error 處理

有兩種error機(jī)制:

observables中的異步error處理機(jī)制

Error處理非常直接。如果序列被一個error中斷蝉揍,那么所有與之相關(guān)的序列都會被這個error中斷链峭。這是常見的短路邏輯。

你可以用 catch 操作符從 observable 的失敗中恢復(fù)又沾。有各種各樣的重載可以讓你恢復(fù)很多序列的細(xì)節(jié)弊仪。

操作符可以在序列出現(xiàn) error 的情況下重新嘗試原來的操作。

Debugging 編譯錯誤

當(dāng)你寫好了優(yōu)雅的 RxSwift/RxCocoa 代碼杖刷,你可能會強(qiáng)烈依賴編譯器對于 Observable 的推斷類型励饵。這也是為什么有時候 Swift 很好用,而有時候卻讓人沮喪的原因滑燃。

images = word
    .filter { $0.containsString("important") }
    .flatMap { word in
        return self.api.loadFlickrFeed("karate")
            .catchError { error in
                return just(JSON(1))
            }
      }

如果編譯器報告這個表達(dá)式有問題曲横,我會建議先添加返回值的類型。

images = word
    .filter { s -> Bool in s.containsString("important") }
    .flatMap { word -> Observable<JSON> in
        return self.api.loadFlickrFeed("karate")
            .catchError { error -> Observable<JSON> in
                return just(JSON(1))
            }
      }

如果任然不工作不瓶,你可以繼續(xù)添加更多類型知道你確定了錯誤是什么禾嫉。

images = word
    .filter { (s: String) -> Bool in s.containsString("important") }
    .flatMap { (word: String) -> Observable<JSON> in
        return self.api.loadFlickrFeed("karate")
            .catchError { (error: NSError) -> Observable<JSON> in
                return just(JSON(1))
            }
      }

我建議先添加閉包的參數(shù)和返回值的類型。

通常來說當(dāng)你解決了錯誤之后蚊丐,你可以移除類型的注解讓你的代碼變得更干凈熙参。

Debugging

用 debugger 很有用,但用 debug 操作符會更加高效麦备。debug 操作符會把所有的事件打印到標(biāo)準(zhǔn)輸出孽椰,你也可以給這些事件加上標(biāo)注昭娩。

debug 的作用像探測器一樣。這里是一個例子:

let subscription = myInterval(0.1)
    .debug("my probe")
    .map { e in
        return "This is simply \\(e)"
    }
    .subscribe(onNext: { n in
        print(n)
    })

NSThread.sleepForTimeInterval(0.5)

subscription.dispose()

會打邮蜇摇:

[my probe] subscribed
Subscribed
[my probe] -> Event next(Box(0))
This is simply 0
[my probe] -> Event next(Box(1))
This is simply 1
[my probe] -> Event next(Box(2))
This is simply 2
[my probe] -> Event next(Box(3))
This is simply 3
[my probe] -> Event next(Box(4))
This is simply 4
[my probe] dispose
Disposed

你可以很容易地實現(xiàn)你自己的 debug 操作符栏渺。

extension ObservableType {
    public func myDebug(identifier: String) -> Observable<Self.E> {
        return Observable.create { observer in
            print("subscribed \\(identifier)")
            let subscription = self.subscribe { e in
                print("event \\(identifier)  \\(e)")
                switch e {
                case .next(let value):
                    observer.on(.next(value))

                case .error(let error):
                    observer.on(.error(error))

                case .completed:
                    observer.on(.completed)
                }
            }
            return Disposables.create {
                   print("disposing \\(identifier)")
                   subscription.dispose()
            }
        }
    }
 }

Debugging 內(nèi)存泄漏

在debug模式里,Rx用全局變量 resourceCount 追蹤所有被占用的資源锐涯。

如果你想檢測資源泄漏磕诊,最簡單的方法就是 RxSwift.resourceCount 打印出來。

    /* add somewhere in
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
    */
    _ = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
        .subscribe(onNext: { _ in
            print("Resource count \\(RxSwift.resourceCount)")
        })

檢測內(nèi)存泄漏最有效的方法就是:

  • 導(dǎo)航到你想要檢測的頁面并且使用那個頁面的功能
  • 導(dǎo)航返回
  • 觀察最初的資源計數(shù)
  • 第二次導(dǎo)航到那個頁面并且使用
  • 導(dǎo)航返回
  • 觀察最終的資源計數(shù)

如果這兩次的計數(shù)不一樣的話纹腌,那很有可能存在內(nèi)存泄漏霎终。

這種兩次導(dǎo)航的方法能有效地工作是因為第一次導(dǎo)航會強(qiáng)行加載 lazy resources。

變量

Variable 代表了一些 observable 的狀態(tài)升薯。 Variable 必須包含一個值莱褒,因為 Variable 的初始化需要一個初始值。

Variable 封裝了 Subject涎劈。更具體一點广凸,Variable 就是一個 BehaviorSubject。不同于 BehaviorSubject 的一點是蛛枚,它僅僅公開 value interface谅海,所以 variable 不能中斷或者失敗。

Variable 會在被訂閱之后立即廣播它現(xiàn)在的值坤候。

在 variable 被釋放之后胁赢,它會完成從 .asObservable() 返回的 observable 序列企蹭。

let variable = Variable(0)

print("Before first subscription ---")

_ = variable.asObservable()
    .subscribe(onNext: { n in
        print("First \\(n)")
    }, onCompleted: {
        print("Completed 1")
    })

print("Before send 1")

variable.value = 1

print("Before second subscription ---")

_ = variable.asObservable()
    .subscribe(onNext: { n in
        print("Second \\(n)")
    }, onCompleted: {
        print("Completed 2")
    })

variable.value = 2

print("End ---")

會打影壮铩:

Before first subscription ---
First 0
Before send 1
First 1
Before second subscription ---
Second 1
First 2
Second 2
End ---
Completed 1
Completed 2

KVO

KVO 一種 Objective-C 機(jī)制。那意味著 KVO 沒有一種類型安全機(jī)制谅摄,這個項目想要去解決其中的一些問題徒河。

在這個庫中有兩種內(nèi)置的支持 KVO 的方法。

// KVO
extension Reactive where Base: NSObject {
    public func observe<E>(type: E.Type, _ keyPath: String, options: NSKeyValueObservingOptions, retainSelf: Bool = true) -> Observable<E?> {}
}

#if !DISABLE_SWIZZLING
// KVO
extension Reactive where Base: NSObject {
    public func observeWeakly<E>(type: E.Type, _ keyPath: String, options: NSKeyValueObservingOptions) -> Observable<E?> {}
}
#endif

監(jiān)測 UIView 的 frame 例子:

警告:UIKit 不兼容 KVO送漠, 但這樣的代碼可以工作顽照。

view
  .rx.observe(CGRect.self, "frame")
  .subscribe(onNext: { frame in
    ...
  })

或者

view
  .rx.observeWeakly(CGRect.self, "frame")
  .subscribe(onNext: { frame in
    ...
  })

rx.observe

rx.observe 更加高效因為它是一個 KVO 機(jī)制的簡單封裝,但它運用的場合比較少闽寡。

  • 它可以用來監(jiān)測由 self 或者父類開始的路徑 (retainSelf = false)
  • 它可以用來監(jiān)測有子類開始的路徑 (retainSelf = true)
  • the paths have to consist only of strong properties, otherwise you are risking crashing the system by not unregistering KVO observer before dealloc. 路徑只能包括 strong 屬性代兵,否則就會有系統(tǒng)崩潰的風(fēng)險,因為可能會在釋放之前仍然注冊著 KVO observer爷狈。

例如:

self.rx.observe(CGRect.self, "view.frame", retainSelf: false)

rx.observeWeakly

rx.observeWeaklyrx.observe 要慢一點因為它不得不處理對象的釋放防止弱引用植影。

它可以用在所有 rx.observe 可以用的地方。

  • 因為它不會保留被監(jiān)測的目標(biāo)涎永,它能夠去監(jiān)測任意的對象思币,即使在不知道所有權(quán)關(guān)系的情況下鹿响。
  • 它可以用來監(jiān)測 weak 屬性。

比如:

someSuspiciousViewController.rx.observeWeakly(Bool.self, "behavingOk")

監(jiān)測結(jié)構(gòu)體

KVO 是一個 Objective-C 機(jī)制所有它很依賴 NSValue谷饿。

RxCocoa 內(nèi)建支持監(jiān)測 CGRect, CGSizeCGPoint 結(jié)構(gòu)體.

當(dāng)監(jiān)測其他結(jié)構(gòu)體的時候惶我,必須手動從 NSValue 中提取結(jié)構(gòu)體。

這里 是一些展示如何通過實現(xiàn) KVORepresentable協(xié)議來 擴(kuò)展 KVO 監(jiān)測機(jī)制和 用rx.observe 方法監(jiān)測其他結(jié)構(gòu)體的例子博投。

關(guān)于 UI 層的建議

當(dāng)綁定 UIKit 控件的時候绸贡,你的 Observable 需要在UI 層滿足一些要求。

線程

Observable 需要在 MainScheduler(主線程 UIThread)發(fā)送元素贬堵。這其實也是 UIKit/Cocoa 的要求恃轩。

讓你的API在 MainScheduler 主線程返回結(jié)果始終是一個好主意。以防你想要從其他線程中綁定UI黎做,當(dāng)你在 Debug的時候叉跛, build RxCocoa 會拋出一個異常來提醒你。

為了解決這個問題蒸殿,你需要加入 observeOn(MainScheduler.instance)筷厘。

NSURLSession extensions 默認(rèn)不在 MainScheduler 返回結(jié)果。

Errors

你不能把失敗事件綁定到UIKit控件上因為那是沒有被定義過的行為宏所。

如果你不知道 Observable 是否會失敗酥艳,你可以用 catchErrorJustReturn(valueThatIsReturnedWhenErrorHappens)來確保不會失敗,但是在error發(fā)生之后爬骤,這個序列仍然會成功完成充石。

如果想要這個序列繼續(xù)產(chǎn)生元素霞玄,就需要用 retry 操作符骤铃。

分享訂閱

你常常會想要在UI 層分享訂閱。你不想要調(diào)用不同的 HTTP 請求來綁定相同的數(shù)據(jù)到不同的UI 元素坷剧。

如果你有下面這段代碼:

let searchResults = searchText
    .throttle(0.3, $.mainScheduler)
    .distinctUntilChanged
    .flatMapLatest { query in
        API.getSearchResults(query)
            .retry(3)
            .startWith([]) // clears results on new search term
            .catchErrorJustReturn([])
    }
    .shareReplay(1)              // <- notice the `shareReplay` operator

你想要在搜索結(jié)果被計算出來的時候立刻分享搜索結(jié)果惰爬。這就是 shareReplay 的意思。

在UI 層把 shareReplay 加到操作符鏈的結(jié)尾處通常是個很好的習(xí)慣惫企,因為你會想要分享計算出來的結(jié)果撕瞧。你不會想要為了得到相同的搜索數(shù)據(jù)而一次又一次地調(diào)用 HTTP 請求,然后綁定到UI 元素上狞尔。

看一下 Driver 單元丛版,這個單元被設(shè)計來透明地封裝 shareReply 的調(diào)用,確保元素在主線程被監(jiān)測偏序,并且沒有 error 會被綁定到 UI页畦。

發(fā)送 HTTP 請求

HTTP 請求是很多人第一件想做的事情。

首先你需要創(chuàng)建 NSURLRequest 對象禽车,它能代表你想要完成的工作寇漫。

Request 決定了這是一個 GET 請求還是 POST 請求刊殉, request body的內(nèi)容, query 參數(shù) ...

我們來演示怎么創(chuàng)建一個簡單的 GET 請求:

let request = NSURLRequest(URL: NSURL(string: "http://en.wikipedia.org/w/api.php?action=parse&page=Pizza&format=json")!)

如果你想要執(zhí)行請求并且與其他 observable 組合工作州胳,你需要學(xué)習(xí)下面的代碼记焊。

let responseJSON = NSURLSession.sharedSession().rx.JSON(request)

// 這個時候還沒有觸發(fā)請求
// `responseJSON` 只是一種如果進(jìn)行請求的描述。

let cancelRequest = responseJSON
    // this will fire the request
    .subscribe(onNext: { json in
        print(json)
    })

NSThread.sleep(forTimeInterval: 3.0)

// 如果你想在3秒鐘后取消這個請求栓撞,就調(diào)用 dispose 方法
cancelRequest.dispose()

**NSURLSession 擴(kuò)展默認(rèn)不在 MainScheduler 主線程返回結(jié)果遍膜。 **

如果你想要在更底層獲得 reponse,你可以用:

NSURLSession.shared.rx.response(myNSURLRequest)
    .debug("my request") // this will print out information to console
    .flatMap { (data: NSData!, response: NSURLResponse!) -> Observable<String> in
        if let response = response as? NSHTTPURLResponse {
            if 200 ..< 300 ~= response.statusCode {
                return just(transform(data))
            }
            else {
                return Observable.error(yourNSError)
            }
        }
        else {
            rxFatalError("response = nil")
            return Observable.error(yourNSError)
        }
    }
    .subscribe { event in
        print(event) // 如果 error 發(fā)生瓤湘,將會在控制臺打印出 error
    }

記錄 HTTP 通信信息

在debug模式中瓢颅,RxCocoa 會記錄所有 HTTP 的請求并打印到控制臺。如果你想改變這種設(shè)置弛说,請設(shè)置 Logging.URLRequests 濾波器挽懦。

// 讀你自己的配置文件
public struct Logging {
    public typealias LogURLRequest = (NSURLRequest) -> Bool

    public static var URLRequests: LogURLRequest =  { _ in
    #if DEBUG
        return true
    #else
        return false
    #endif
    }
}

RxDataSources

在Rx中,為 UITableViewUICollectionView 實現(xiàn)的 datasource類

RxDataSources 的項目可以在 這里找到木人。

展示如何使用 RxDataSources 的項目RxExample信柿。

原文鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市醒第,隨后出現(xiàn)的幾起案子渔嚷,更是在濱河造成了極大的恐慌,老刑警劉巖稠曼,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異霞幅,居然都是意外死亡漠吻,警方通過查閱死者的電腦和手機(jī)侥猩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門榔至,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抵赢,“玉大人,你說我怎么就攤上這事唧取∏穑” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵枫弟,是天一觀的道長邢享。 經(jīng)常有香客問我,道長淡诗,這世上最難降的妖魔是什么骇塘? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任伊履,我火速辦了婚禮,結(jié)果婚禮上款违,老公的妹妹穿的比我還像新娘唐瀑。我一直安慰自己,他們只是感情好插爹,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布哄辣。 她就那樣靜靜地躺著,像睡著了一般赠尾。 火紅的嫁衣襯著肌膚如雪力穗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天气嫁,我揣著相機(jī)與錄音当窗,去河邊找鬼。 笑死寸宵,一個胖子當(dāng)著我的面吹牛超全,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播邓馒,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嘶朱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了光酣?” 一聲冷哼從身側(cè)響起疏遏,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎救军,沒想到半個月后财异,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡唱遭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年戳寸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拷泽。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡疫鹊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出司致,到底是詐尸還是另有隱情拆吆,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布脂矫,位于F島的核電站枣耀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏庭再。R本人自食惡果不足惜捞奕,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一牺堰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颅围,春花似錦萌焰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至一疯,卻和暖如春撼玄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背墩邀。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工掌猛, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人眉睹。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓荔茬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親竹海。 傳聞我的和親對象是個殘疾皇子慕蔚,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容