
RxSwift
|
|-LICENSE.md
|-README.md
|-RxSwift --- 平臺核心
|-RxCocoa --- 對UI,NSURLSession,KVO...的擴(kuò)展
|-RxBlocking --- block 操作符集合,僅用于單元測試
|-RxExample ---
|-Rx.xcworkspace --- 包含所有project的workspace
閑來逛逛哦rxswift.slace.com
1. Why
寫出穩(wěn)定而又快速的代碼是何其的難,這個(gè)過程中有許多坑,這些坑足以讓你毀了你的所有的辛勤付出。
State 狀態(tài)
允許修改的語言很容易訪問全局狀態(tài)并修改它,未受控制的狀態(tài)的修改很可能會導(dǎo)致程序的混亂崩潰(combinatorial explosion),但是從另一方面來說,強(qiáng)類型語言能都寫出更高效的代碼,這中方式就是盡可能地保持狀態(tài)簡單,并使用單項(xiàng)數(shù)據(jù)流來模型化數(shù)據(jù),這就是Rx的閃光處
Bindings 綁定
當(dāng)你寫UI app的時(shí)候,理想情況就是用戶界面可以隨著狀態(tài)的改變而改變,并且不會出現(xiàn)不一致的情況,這就是所謂的binding。
observable.combineLatest(firstName.rx_text,lastName.rx_text) { $0 + " " + $1 }
.map { "Greeting \\($0)" }
.bindTo(greetingLabel.rx_text)
官方建議使用
.adddisposableTo(disposeBag)即使對于簡單地bindings 來說這并不是必要的
Retries
我們很期望APIs 不會失敗,但是這并不能如我們所愿,下面我們就來看一個(gè):
func doSomethingIncredible(forWho: String) throws -> IncredibleThing
這個(gè)函數(shù)如果執(zhí)行失敗很難再次retry,即使是retry了,這也會產(chǎn)生很多的transient states,這并不是我們想要的,Rx 實(shí)現(xiàn)起來就很簡單
doSomethingIncredible("me")
.retry(3)
Transient State
在寫 async 程序的時(shí)候會有許多的 瞬態(tài)問題,最典型的就是輸入框的自動搜索,先輸入ab ,發(fā)送一次請求,再輸入c,又會發(fā)送一次請求,之前的請求可能需要cancel掉,或者使用另外一個(gè)變量引用。
另外一個(gè)問題就是,如果請求失敗,就需要大量的retry 邏輯處理,如果成功,之前的retry 也需要清理。
如果我們在觸發(fā)請求之前能有一丟丟時(shí)間的間隔,那就會非常完美,畢竟有些輸入操作需要較長的時(shí)間。
還有一個(gè)問題就是在搜索請求執(zhí)行過程中,屏幕上顯示啥呢?失敗了,又要顯示啥?(公司產(chǎn)品要求,你懂得,處理起來極其繁瑣),下面就是Rx 大展拳腳的時(shí)候了
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 節(jié)流,在設(shè)定的時(shí)間內(nèi)多次發(fā)送請求,僅僅觸發(fā)最后一次
distinctUntilChanged 求異去同,比較前后兩個(gè)值,如果相同則不會觸發(fā),不同則觸發(fā)
startWith 開始設(shè)定的值,可以認(rèn)為是占位符或者placeHolder
整合網(wǎng)絡(luò)請求
你想一同發(fā)送兩個(gè)請求,當(dāng)兩個(gè)請求都成功后,將兩者的結(jié)果整合起來處理,這... 好傷腦筋?。。。?br>
沒關(guān)系,zip 幫你實(shí)現(xiàn)
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 將兩個(gè)信號合并成一個(gè)信號,并壓縮成一個(gè)元組返回,前提是兩個(gè)信號均成功
還有個(gè)問題是,這些請求是在后臺,綁定還沒有在主線程發(fā)生,這就要用到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
實(shí)現(xiàn)自己的observable,那真是太簡單了,(so easy,老板再也不擔(dān)心我寫不出代碼了)
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)
設(shè)想一下幾個(gè)場景: 在tableView上展示一個(gè)模糊的image,這個(gè)image首先需要獲取,然后解碼,然后模糊處理
- 在cell 退出了可顯示區(qū)域,整個(gè)操作可以取消
- 當(dāng)用戶快速滑動cell,cell 進(jìn)入可現(xiàn)實(shí)區(qū)域,不會立刻去獲取image。cell僅僅是曇花一現(xiàn),這需要發(fā)送很多的request 和 cancel 操作。
- 我們可以限制并發(fā)數(shù)量
以上場景如果可以優(yōu)化并滿足要求,改多好,是吧 !
讓我們看看 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)
代理一般用于回調(diào)和作為一種觀察機(jī)制。
傳統(tǒng)的delegate 在設(shè)置setter 方法時(shí),不會觸發(fā)初始值,因此你需要通過其他的途徑來讀取初始值。
RxCocoa 不僅提供了UIKit Class的封裝,而且還提供了一套通用機(jī)制-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:的參數(shù),第一個(gè)是Searchbar,第二個(gè)是值
return a[1] as? String ?? ""
}
.startWith(text)
}
}
}
RxSearchBarDelegateProxy 可在這里找到 here
下面是該API的使用
searchBar.rx_text
.subscribeNext { searchText in
print("Current search text '\\(searchText)'")
}
通知 (Notificatioins)
通知可以注冊過個(gè)觀察者,但是他們也是未知類型的,值需要從userInfo中提取。
冗余的形式:
let initialText = object.text
doSomething(initialText)
// ....
func controlTextDidChange(notification: NSNotification) {
doSomething(object.text)
}
你可以使用rx_notification 來創(chuàng)建一個(gè)觀察序列,減少邏輯和重復(fù)代碼的散播。
let subscription = notificationCenter.rx_notification("testNotification", object: targetObject)
.subscribeNext { n in
numberOfNotifications += 1
}
KVO
KVO 是一個(gè)很方便的觀察機(jī)制,但是也不是沒有缺點(diǎn),他最大的缺點(diǎn)就是讓人模糊的內(nèi)存管理。
在觀察一個(gè)對象的一個(gè)屬性時(shí),該對象必須要比注冊的KVO observer 活的時(shí)間長,都則會遇到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.
另外還有一套規(guī)則你還得遵守,否則結(jié)果有可能很詭異。還的實(shí)現(xiàn)一個(gè)笨拙的方法
-(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)")
}
(翻譯拙劣,如有問題,歡迎留言)