RxSwift:ReactiveX for Swift 翻譯

圖片發(fā)自簡書App

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

  1. 在cell 退出了可顯示區(qū)域,整個(gè)操作可以取消
  2. 當(dāng)用戶快速滑動cell,cell 進(jìn)入可現(xiàn)實(shí)區(qū)域,不會立刻去獲取image。cell僅僅是曇花一現(xiàn),這需要發(fā)送很多的request 和 cancel 操作。
  3. 我們可以限制并發(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_observerx_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)")
    }

(翻譯拙劣,如有問題,歡迎留言)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評論 19 139
  • 前言 看了前幾篇關(guān)于RxSwift主要概念的文章,會對RxSwift有個(gè)大致的了解,這篇文章會詳細(xì)講述如何使用Rx...
    最Fly的Engine人閱讀 13,167評論 3 40
  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,579評論 30 472
  • 前言 在之前用Objective-C語言做項(xiàng)目的時(shí)候,我習(xí)慣性的會利用MVVM模式去架構(gòu)項(xiàng)目,在框架Reactiv...
    Tangentw閱讀 21,394評論 32 124
  • 本章將向你介紹另一個(gè)框架,它是原生RxSwift庫的一部分:RxCocoa。 RxCocoa全平臺通用。每個(gè)平臺有...
    大灰很閱讀 951評論 3 2

友情鏈接更多精彩內(nèi)容