前言
看了前幾篇關(guān)于RxSwift主要概念的文章格粪,會對RxSwift有個大致的了解躏吊,這篇文章會詳細(xì)講述如何使用RxSwift氛改,主要的語法,仔細(xì)地看完這篇文章就可以用RxSwift開始寫app了比伏。
相關(guān)文章鏈接:
正文
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
方法橙弱,使用disposeBag
, takeUntil
或者其他的一些方法,也可以自動釋放序列占用的資源棘脐。
使用 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)者 (Observable
s) 必須遵守的規(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.observeWeakly
比 rx.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
, CGSize
和 CGPoint
結(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中,為 UITableView
和 UICollectionView
實現(xiàn)的 datasource類
RxDataSources 的項目可以在 這里找到木人。
展示如何使用 RxDataSources 的項目RxExample信柿。