RxSwift:ReactiveX for Swift 翻譯
RxSwift
|
|-LICENSE.md
|-README.md
|-RxSwift --- 平臺核心
|-RxCocoa --- 對UI,NSURLSession,KVO...的擴展
|-RxBlocking --- block 操作符集合,僅用於單元測試
|-RxExample ---
|-Rx.xcworkspace --- 包含所有project的workspace
閒來逛逛哦rxswift.slace.com
1. Why
寫出穩定而又快速的代碼是何其的難,這個過程中有許多坑,這些坑足以讓你毀了你的所有的辛勤付出。
State 狀態
允許修改的語言很容易訪問全局狀態並修改它,未受控制的狀態的修改很可能會導致程序的混亂崩潰(combinatorial explosion),但是從另一方面來說,強類型語言能都寫出更高效的代碼,這中方式就是儘可能地保持狀態簡單,並使用單項數據流來模型化數據,這就是Rx
的閃光處
Bindings 綁定
當你寫UI app的時候,理想情況就是用戶界面可以隨着狀態的改變而改變,並且不會出現不一致的情況,這就是所謂的binding。
observable.combineLatest(firstName.rx_text,lastName.rx_text) { $0 + " " + $1 }
.map { "Greeting \\($0)" }
.bindTo(greetingLabel.rx_text)
官方建議使用
.adddisposableTo(disposeBag)
即使對於簡單地bindings 來說這並不是必要的
Retries
我們很期望APIs 不會失敗,但是這並不能如我們所願,下面我們就來看一個:
func doSomethingIncredible(forWho: String) throws -> IncredibleThing
這個函數如果執行失敗很難再次retry,即使是retry了,這也會產生很多的transient states,這並不是我們想要的,Rx 實現起來就很簡單
doSomethingIncredible("me")
.retry(3)
Transient State
在寫 async
程序的時候會有許多的 瞬態問題,最典型的就是輸入框的自動搜索,先輸入ab
,發送一次請求,再輸入c
,又會發送一次請求,之前的請求可能需要cancel掉,或者使用另外一個變量引用。
另外一個問題就是,如果請求失敗,就需要大量的retry 邏輯處理,如果成功,之前的retry 也需要清理。
如果我們在觸發請求之前能有一丟丟時間的間隔,那就會非常完美,畢竟有些輸入操作需要較長的時間。
還有一個問題就是在搜索請求執行過程中,屏幕上顯示啥呢?失敗了,又要顯示啥?(公司產品要求,你懂得,處理起來極其繁瑣),下面就是Rx
大展拳腳的時候了
searchTextField.rx_text
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest { query in
API.getSearchResults(query)
.retry(3)
.startWith([]) // clears results on new search term
.catchErrorJustReturn([])
}
.subscribeNext { results in
// bind to ui
}
throttle
節流,在設定的時間內多次發送請求,僅僅觸發最後一次distinctUntilChanged
求異去同,比較前後兩個值,如果相同則不會觸發,不同則觸發startWith
開始設定的值,可以認爲是佔位符或者placeHolder
整合網絡請求
你想一同發送兩個請求,當兩個請求都成功後,將兩者的結果整合起來處理,這... 好傷腦筋啊!!!
沒關係,zip
幫你實現
let userRequest: Observable<User> = API.getUser("me")
let friendsRequest: Observable<Friends> = API.getFriends("me")
Observable.zip(userRequest, friendsRequest) { user, friends in
return (user, friends)
}
.subscribeNext { user, friends in
// bind them to user interface
}
zip
將兩個信號合併成一個信號,並壓縮成一個元組返回,前提是兩個信號均成功
還有個問題是,這些請求是在後臺,綁定還沒有在主線程發生,這就要用到observeOn
let userRequest: Observable<User> = API.getUser("me")
let friendsRequest: Observable<[Friend]> = API.getFriends("me")
Observable.zip(userRequest, friendsRequest) { user, friends in
return (user, friends)
}
.observeOn(MainScheduler.instance)
.subscribeNext { user, friends in
// bind them to user interface
}
輕鬆整合RX
實現自己的observable,那真是太簡單了,(so easy,老闆再也不擔心我寫不出代碼了)
extension NSURLSession {
public func rx_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 AnonymousDisposable {
task.cancel()
}
}
}
}
綜合處理 (Compositional disposal)
設想一下幾個場景: 在tableView上展示一個模糊的image,這個image首先需要獲取,然後解碼,然後模糊處理
- 在cell 退出了可顯示區域,整個操作可以取消
- 當用戶快速滑動cell,cell 進入可現實區域,不會立刻去獲取image。cell僅僅是曇花一現,這需要發送很多的request 和 cancel 操作。
- 我們可以限制併發數量
以上場景如果可以優化並滿足要求,改多好,是吧 !
讓我們看看 Rx
是怎麼做的
// this is conceptual solution
let imageSubscription = imageURLs
.throttle(0.2, scheduler: MainScheduler.instance)
.flatMapLatest { imageURL in
API.fetchImage(imageURL)
}
.observeOn(operationScheduler)
.map { imageData in
return decodeAndBlurImage(imageData)
}
.observeOn(MainScheduler.instance)
.subscribeNext { blurredImage in
imageView.image = blurredImage
}
.addDisposableTo(reuseDisposeBag)
代理(Delegates)
代理一般用於回調和作爲一種觀察機制。
傳統的delegate 在設置setter 方法時,不會觸發初始值,因此你需要通過其他的途徑來讀取初始值。RxCocoa
不僅提供了UIKit
Class
的封裝,而且還提供了一套通用機制-DelegateProxy
,使你能夠封裝你自己的
delegate 並作爲可觀察的Sequence 暴漏出來。
來看一下整合後的UISearchbar
It uses delegate as a notification mechanism to create an Observable<String> that immediately returns current search text upon subscription, and then emits changed search values.
extension UISearchBar {
public var rx_delegate: DelegateProxy {
return proxyForObject(RxSearchBarDelegateProxy.self, self)
}
public var rx_text: Observable<String> {
return defer { [weak self] in
let text = self?.text ?? ""
return self?.rx_delegate.observe("searchBar:textDidChange:") ?? empty()
.map { a in // a 包含了searchbar:textDidChange:的參數,第一個是Searchbar,第二個是值
return a[1] as? String ?? ""
}
.startWith(text)
}
}
}
RxSearchBarDelegateProxy
可在這裏找到 here
下面是該API的使用
searchBar.rx_text
.subscribeNext { searchText in
print("Current search text '\\(searchText)'")
}
通知 (Notificatioins)
通知可以註冊過個觀察者,但是他們也是未知類型的,值需要從userInfo
中提取。
冗餘的形式:
let initialText = object.text
doSomething(initialText)
// ....
func controlTextDidChange(notification: NSNotification) {
doSomething(object.text)
}
你可以使用rx_notification
來創建一個觀察序列,減少邏輯和重複代碼的散播。
let subscription = notificationCenter.rx_notification("testNotification", object: targetObject)
.subscribeNext { n in
numberOfNotifications += 1
}
KVO
KVO 是一個很方便的觀察機制,但是也不是沒有缺點,他最大的缺點就是讓人模糊的內存管理。
在觀察一個對象的一個屬性時,該對象必須要比註冊的KVO observer 活的時間長,都則會遇到crash
`TickTock` was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object.
另外還有一套規則你還得遵守,否則結果有可能很詭異。還的實現一個笨拙的方法
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
RxCocoa
提供了一套很方便的觀察序列--rx_observe
和 rx_observeWeakly
使用方法:
view.rx_observe(CGRect.self, "frame")
.subscribeNext { (frame: CGRect?) in
print("Got new frame \\(frame)")
}
or
someSuspiciousViewController.rx_observeWeakly(Bool.self, "behavingOk")
.subscribeNext { (behavingOk: Bool?) in
print("Cats can purr? \\(behavingOk)")
}