RxSwift:ReactiveX for Swift 翻譯

RxSwift:ReactiveX for Swift 翻譯

字數1787 閱讀269 評論3 

圖片發自簡書App

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首先需要獲取,然後解碼,然後模糊處理

  1. 在cell 退出了可顯示區域,整個操作可以取消
  2. 當用戶快速滑動cell,cell 進入可現實區域,不會立刻去獲取image。cell僅僅是曇花一現,這需要發送很多的request 和 cancel 操作。
  3. 我們可以限制併發數量

以上場景如果可以優化並滿足要求,改多好,是吧 !
讓我們看看 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)")
    }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章