RxSwift 函數響應式編程

Max 在 Boston 上學,在 San Francisco 工做,是一名軟件工程師及創業者。當他還在高中的時候就在一家創業公司工做了,他很是喜歡使用 iOS、Android 以及 JavaScript 框架來爲用戶編寫軟件。不過他偶爾也會抱怨創業、技術、書籍、電子遊戲等這些雜七雜八的東西。php

@mbalex99css

概述(0:00)

這幾年有不少人在討論着關於 Rx 的相關概念。Rx 經過 Observable<Element>接口來表達計算型泛型抽象 (generic abstraction of computation) 的概念,而 RxSwift 是 Rx 的 Swift 版本。無疑,這個內容很是龐大,因此我打算用一種稍微簡單點的介紹方式來說解這個框架。java

若是有人寫出了下面這麼一段代碼,相信確定會有不少人感到很是不爽:react

Alamofire.request(.POST, "login", parameters: ["username": "max", "password": "insanity"])
  .responseJSON(completionHandler: { (firedResponse) -> Void in
    Alamofire.request(.GET, "myUserInfo" + firedResponse.result.value)
      .responseJSON(completionHandler: { myUserInfoResponse in
        Alamofire.request(.GET, "friendList" + myUserInfoResponse.result.value)
          .responseJSON(completionHandler: { friendListResponse in
            Alamofire.request(.GET, "blockedUsers" + friendListResponse.result.value)
              .responseJSON(completionHandler: {

              })
            })
          })
    Alamofire.request(.GET, "myUserAcccount" + firedResponse.result.value)
      .responseJSON(completionHandler: {
      })
    })

這段代碼有什麼問題呢?這些都是 Alamofire 網絡請求方法。若是你們沒用過 Alamofire 的話,能夠把它看做是 AFNetworking 框架在執行 HTTP 請求操做。在這段代碼中你能夠看到各類各樣的嵌套閉包代碼,每多一層嵌套,代碼縮進就會向右移動一層,你知道這段代碼是有問題的,由於你沒法直觀地解釋這段代碼作了什麼。在這段代碼中涉及到了很多網絡服務,而網絡頗有可能會訪問失敗。咱們須要爲此添加錯誤處理,然而咱們卻沒法知道該在何到處理這些全部的錯誤異常。Rx 就應運而生,它能夠幫助咱們解決這個問題。ajax

回到基礎(2:26)

在處理不一樣事件的時候,不管如何你都會持有一個包含這些事件的集合。舉個例子,咱們有這樣一個整數數組 [1, 2, 3, 4, 5, 6] ,若是你喜歡你也能夠稱之爲列表。當我須要執行某些操做的時候,最符合 Swift 風格的操做就是使用 filter 方法了。數據庫

[1, 2, 3, 4, 5, 6].filter{ $0 % 2 == 0 }

那麼若是我想要給這個數組中的全部元素都乘以 5 而後生成一個新數組呢?json

[1, 2, 3, 4, 5, 6].map{ $0 * 5 }

那麼執行加法操做呢?數組

[1, 2, 3, 4, 5, 6].reduce(0, +)

這些操做很是簡便。咱們沒有使用任何 for 循環方法,咱們也不會持有並保持那些臨時的中間數。這看起來就很是像 Scala 或者 Haskel 的操做方式。然而,一個正常的應用程序很難只使用數組就能夠完成的了。你們都但願使用網絡、下載圖片、網上聊天、添加好友等等。你須要大量使用 IO 操做。IO 意味着你須要讓內存與用戶交互動做、用戶設備、相機、硬盤等等諸如此類的東西進行數據交換。這些操做都是異步的,隨時均可能會發生失敗,而且會發生不少奇怪的問題。安全

Rx 權利法案(4:09)

我提出了一個關於 Rx 的權利法案,它規定:ruby

咱們的開發者擁有像管理迭代集合 (iterable collections) 同樣管理異步事件的權利。

觀察者(4:25)

在 Rx 的世界裏,讓咱們用 觀察者 (Observables) 的概念來代替數組。 觀察者 是一個類型安全的事件對象,能夠長期對不一樣種類的數據值進行寫入和讀出。RxSwift 目前處於 Beta 3 的版本,安裝很是簡單。你所須要作的就是導入 RxSwift 便可。

pod 'RxSwift', '~> 2.0.0-beta.3'
import RxSwift

建立 觀察者 也很容易。最簡單的建立方式就是使用 just ,這是一個 RxSwift 內建的函數。你能夠將你所想要的變量放到其中,它就會返回一個包含相同類型的 觀察者 變量。

just(1)  //Observable<Int>

那麼若是咱們想要從數組中一個接一個的推出元素並執行相關操做呢?

[1,2,3,4,5,6].toObservable()  //Observable<Int>

這會返回一個 Observable<Int> 對象。

若是你在使用相似於上傳數據到 S3 或者向本地數據庫保存數據之類的 API,你能夠這樣寫:

create { (observer: AnyObserver<AuthResponse>) -> Disposable in return AnonymousDisposable { }
}

當你調用 create 的時候會返回一個閉包。這個閉包會給予一個 觀察者 參數,這意味着有某個東西正在對其進行觀察。目前你能夠忽略 AnonymousDisposable 這個東西。在下面兩行代碼中你將會看到你在何處將 API 代碼轉換爲了一個好用的 觀察者 對象。

下面這段代碼和 Alamofire 的使用方式相同:

create { (observer: AnyObserver<AuthResponse>) -> Disposable in

  let request = MyAPI.get(url, ( (result, error) -> {
    if let err = error {
      observer.onError(err);
    }
    else if let authResponse = result {
      observer.onNext(authResponse);
      observer.onComplete();
    }
  })
  return AnonymousDisposable {
    request.cancel()
  }
}

我能夠進行日誌記錄操做,也能夠執行一個 GET 請求,隨後我能夠獲得一個帶有結果和錯誤的回調函數。實際上我沒法改變這個 API,由於這是由另外一個客戶端 SDK 所提供的,可是我能夠將其轉換爲一個 觀察者 對象。當存在錯誤的時候,我會調用observer.onError() 方法。這意味着只要監聽了這個對象的代碼都會接收到這個錯誤消息。當你獲得可用的服務器迴應時,調用 observable.onNext() 方法。接着,若是處理結束的話就調用 onComplete() 。這個時候咱們就到了AnonymousDisposable 這裏了。 AnonymousDisposable 是當你想要停止請求的時候被調用的操做。好比說你離開了當前視圖控制器或者應用已經再也不須要調用這個請求的時候,就可使用這個方法了。這對視頻上傳等大文件操做是很是有用的。當你結束全部操做的時候, request.cancel() 能夠清除全部的資源。不管是操做完成仍是發生錯誤,這個方法都會被調用。

監聽觀察者(8:11)

如今咱們知道如何建立觀察者了,那麼就來看看如何對其創建監聽吧!咱們以數組爲例,由於咱們能夠在不少對象中調用一個名爲 toObservable() 的擴展方法。隨後,就能夠編寫監聽函數了:

[1,2,3,4,5,6]
  .toObservable()
  .subscribeNext {
    print($0)
  }

這看起來跟枚舉差很少。Subscribe 監聽器事件基於失敗請求、下一步事件以及onCompleted 操做,給你提供了各類各樣的信息。你能夠有選擇性的創建相應的監聽:

[1,2,3,4,5,6]
  .toObservable()
  .subscribe(onNext: { (intValue) -> Void in
    // 推出一個整數數據
  }, onError: { (error) -> Void in
    // 發生錯誤!
  }, onCompleted: { () -> Void in
    // 沒有更多的信號進行處理了
  }) { () -> Void in
    // 咱們要清除這個監聽器
  }

關聯觀察者(9:14)

使用 Rx 的最好例子就是套接字 (socket) 服務了。假設咱們有一個網絡套接字服務,它用來監聽股票行情,而後顯示用戶的當前帳戶餘額 UI 界面。因爲股票行情對應了不一樣的事件,根據這些事件來決定用戶是否能夠購買股票。若是帳戶餘額太低的時候咱們將禁止用戶購買,當股票票價在用戶的承擔範圍內的時候容許用戶購買。

func rx_canBuy() -> Observable<Bool> {
  let stockPulse : [Observable<StockPulse>]
  let accountBalance : Observable<Double>

  return combineLatest(stockPulse, accountBalance,
    resultSelector: { (pulse, bal) -> Bool in
    return pulse.price < bal }) }

combineLatest 意味着當某個事件發生的時候,咱們就將最近的兩個事件之間創建關聯。Redution 閉包是否會被回調取決於股票票價是否低於餘額。若是被回調,這就意味着用戶能夠購買這隻股票。這個操做容許你將兩個觀察者關聯起來,而後列出一個邏輯決定某些操做是否能夠進行。這會返回一個 Bool 類型的觀察者。

rx_canBuy()
  .subscribeNext { (canBuy) -> Void in
    self.buyButton.enabled = canBuy
  }

使用 subscribe 方法來操做剛剛我建立的那個會返回 Bool 值的 rx_canBuy 方法。而後你就能夠根據 canBuy 的返回值來決定 self.buyButton 的行爲了。

讓咱們來舉一個合併的例子。假設我有一個存有我所喜歡的股票的用戶界面應用。我經過 Apple、Google 以及 Johnson 來監聽股票票價。全部這些股票行情都有不一樣的結果。當股票行情發生變化的時候我須要馬上知道並更新個人用戶界面。

let myFavoriteStocks : [Observable<StockPulse>]

myFavoriteStocks.merge()
  .subscribeNext { (stockPulse) -> Void in
    print("\(stockPulse.symbol)/
      updated to \(stockPulse.price)/")
  }

這些參數的類型都是相同的 Observable<StockPulse> 類型。我須要知道它們什麼時候被觸發,我須要作的就是持有一個 觀察者 數組。裏面存放了我須要進行監聽的多個不一樣種類的股票行情,我能夠在一個輸出流中將它們合併而後進行監聽。

與 Rx 觀察者圖表進行交互(18:03)

我使用 Rx 使用了很長時間。很遺憾,我仍然忘記了許許多多的操做,而且須要很是頻繁地回顧參考文檔。這個網站 rxmarbles.com 將會爲咱們展現全部這些操做的理論部分。

輕鬆實現後臺進程(19:03)

藉助 RxSwift 還有許多很讚的事情能夠作。好比說你有一個視頻上傳操做,因爲這個視頻文件太大了,所以你須要在後臺中進行。執行這個操做的最好辦法就是使用observeOn 進行。

let operationQueue = NSOperationQueue()
  operationQueue.maxConcurrentOperationCount = 3
  operationQueue.qualityOfService = NSQualityOfService.UserInitiated
  let backgroundWorkScheduler
    = OperationQueueScheduler(operationQueue: operationQueue)

videoUpload
  .observeOn(backgroundWorkScheduler)
  .map({ json in
    return json["videoUrl"].stringValue
  })
  .observeOn(MainScheduler.sharedInstance)
  .subscribeNext{ url
    self.urlLabel.text = url
  }

視頻上傳操做須要給我當前完成度百分比的信號信息,這樣我才能知道這個操做是否完成。可是我並不打算在主線程中執行這個操做,由於我能夠在後臺執行。當視頻上傳結束以後,我會獲得一個返回的 JSON 數據,它會告知我所上傳的 URL 地址這樣我就能夠將其寫入到 UI 標籤當中了。由於我是在後臺進行監聽的,所以這個操做不會在主線程上進行。我須要通知 UI 進行更新,這樣才能將信息傳遞給主線程。所以咱們須要回去執行 observeOn(MainScheduler.SharedInstance) 方法,這個操做將會執行 UI 更新操做。遺憾的是,和 Android 上的 RxJava 框架不一樣,在 Swift 中若是你在後臺進程中更新 UI 也是能夠進行的(但不要這麼作)。在 Android 中這麼作會發生崩潰,而在 Swift 中則不會發生崩潰。

這只是 RxSwift 的皮毛(20:31)

我展現了一些很酷的東西,經過將事件視爲數組,你能夠用 RxSwift 讓代碼更簡單、更好用。我知道 MVVM 是一個很是龐大的項目,它能夠將視圖控制器從一個完整的個體變成一個個關聯的組織。RxSwift 有一個類似的名爲 RxCocoa 的倉庫,能夠有效地解決這個問題。基本上來講,它給全部的 Cocoa 類創建了擴展方法,從而可讓 UI 視圖能夠創建諸如 Rx-Text 或者名字文本框之類的東西。這樣你就能夠少寫一點 subscribeNext 方法,從而在多個不一樣的視圖點中將值和觀察值之間創建關聯。

跨平臺(22:49)

咱們生活在一個有多個平臺的世界。Rx 對我來講,最主要的吸引點就是 Rx 能夠忽略每個其餘客戶端 API 執行 IO 的方法。若是你在使用 Android 或者 JavaScript,你必需要學習如何異步管理這些不一樣的 IO 操做事件。Rx 是一個支持多平臺的輔助庫,你能夠在多個熱門語言中使用:.NET、JavaScript、Java,這三門是除了 Swift 以外最熱門的三個語言了。你可使用相同的操做、步驟和心態來編寫代碼。在全部的語言當中,Rx 看起來都是十分類似的。咱們以一個日誌功能爲例,首先是 Swift:

func rx_login(username: String, password: String) -> Observable<Any> {
  return create({ (observer) -> Disposable in
    let postBody = [
      "username": username,
      "password": password
    ]
    let request = Alamofire.request(.POST, "login", parameters: postBody)
      .responseJSON(completionHandler: { (firedResponse) -> Void in
        if let value = firedResponse.result.value {
          observer.onNext(value)
          observer.onCompleted()
        } else if let error = firedResponse.result.error {
          observer.onError(error)
        }
      })
    return AnonymousDisposable{
      request.cancel()
    }
  })
}

rx_login 函數能夠返回一個你所想要的觀察者對象。下面是 Kotlin 版本:

fun rx_login(username: String, password: String): Observable<JSONObject> {
  return Observable.create ({ subscriber ->
    val body = JSONObject()
    body.put("username", username)
    body.put("password", password)
    val listener = Response.Listener<JSONObject>({ response ->
      subscriber.onNext(response);
      subscriber.onCompleted()
    })
    val errListener = Response.ErrorListener { err ->
      subscriber.onError(err)
    }
    val request = JsonObjectRequest(Request.Method.POST, "login", listener, errListener);
    this.requestQueue.add(request)
  });
}

看起來基本差很少,下面是 TypeScript 版本:

rx_login = (username: string, password: string) : Observable<any> => {
  return Observable.create(observer => {
    let body = {
      username: username,
      password: password
    };
    let request = $.ajax({
      method: 'POST',
      url: url,
      data: body,
      error: (err) => {
        observer.onError(err);
      },
      success: (data) => {
        observer.onNext(data);
        observer.onCompleted();
      }
    });
    return () => {
      request.abort()
    }
  });
}

不仔細看這些代碼形式還真差很少。你能夠對全部這類事件隨意編寫測試用例。你能夠沒必要寫全部的客戶端代碼或 UI 代碼,由於它們都須要自身平臺的支持,可是基於服務的類能夠很容易地提取出相同的使用接收方式。幾乎相同的原理無處不在。

問與答(24:42)

問:您對 RxSwift 和 ReactiveCocoa 都有什麼見解嗎?

Max:我使用了三年的 Objective-C。ReactiveCocoa 是我早期的試用目標。當我換用 Swift 進行開發的時候,我安裝了 ReactiveCocoa 的早期版本,使用起來讓我感到很不愉快。我發現經過 Google 搜索我就能夠很快地上手 RxSwift。對我我的而言,RxSwift 和 ReactiveCocoa 我都用。人們總會說這兩個框架之間有着這樣那樣的差異,可是我歷來沒有聽人說過 RxSwift 毀了某人的童年,也沒據說過 ReactiveCocoa 讓某人妻離子散。沒有人曾經跟我談論過這些差別。所以使用哪個是你的自由。RxSwift 是 ReactiveX Github 倉庫下的一個項目,所以若是若是對你來講閱讀代碼和學習很輕鬆的話,那麼就直接使用 RxSwift 吧!若是你的公司只是關於 iOS 以及 Android 的話,那麼就使用 ReactiveCocoa 就行了,就不要再考慮別的了。可是若是你須要準備三個平臺的話,好比說有一個和 JS 應用一塊兒使用的電子應用,最好是可以將這個應用放到 Spotify 上面,而後給三個平臺分別複製服務而後建立一個監視器。你能夠在一夜完成這個任務。

問:關於自動完成功能速度慢的問題您有沒有建議或者解決方案呢?

Max:我打字速度是很快的,比自動完成功能要快得多了。我大多數時候只在敲下點的時候才查看自動完成列表。若是你輸入的速度過快,那麼你極可能就沒法獲得自動完成提示。這是此時 Xcode 面臨的實際問題。我在使用自動完成的時候並無碰到問題,而一般我使用自動完成的方法是 flatMap 、 merge 和 combineLatest

問:您提到了跨平臺。它能在 Linux 上運行嗎?

Max:我之因此提到跨平臺的特性,是由於它本質上是一個擁有 API 特性的庫。之因此這麼說是由於你可使用 Java 或者 TypeScript 來實現輔助庫的功能,這個庫本質上是獨立運行的。

問:我注意到這個框架導入的是 Foundation 框架,我好奇的是,若是要擺脫這個基礎庫的依賴的話,或者替換這個基礎庫的時候,會不會很是難?

Max:我不清楚。我會具體問下別人這個問題,以後再來回復你。

問:如何對 RxSwift 進行調試呢?

Max:有一個帶有閉包的調試函數的。RxSwift 有一個相似庫是專門處理閉包的。這個庫不會在正式產品中被使用。你實際使用以後你會發現它的功能真的十分好用。它會自行建立閉包調用,所以你無需對這個異步線程進行管理。所以你能夠在主線程上面對堆棧進行跟蹤。

問:我在想爲何您以爲專門挑選幾個特殊的例子來介紹 RxSwift 是一個好主意呢?或者說爲何不全用上 RxSwift 或者 Rearctive 來建立一個完整的應用程序呢?

Max:不少對 RxSwift 進行貢獻的人都說,你應當從一開始就在項目中使用 RxSwift。可是這很不現實,所以我決定有選擇性地進行介紹。我不認爲在場的絕大多數觀衆朋友們都可以有立馬開始新項目的機會。大家你們不少時候是在處理着各類各樣有五年或者六年曆史的代碼庫,所以有選擇性地進行介紹,決定是否使用就看你們是否喜歡了。

問:我已經用了好久的 ReactiveCocoa 了,其中一件 ReactiveCocoa 所要作的事就是他們想將全部事件都轉爲信號進行處理,這樣你就能夠將他們集中在一塊兒。在 Objective-C 中他們使用 Selector 來實現此功能。你是否清楚在 RxSwift 中,它是如何使用 Swift 來處理委託回調方法的呢?

Max:是的,若是你看一下在 RxCocoa 中這一部分代碼的話,它們會從新激活 (reactifying) 你的 UITableViewDelegates 和 UICollectionViewDelegates。它們會建立一個微妙的代理,這樣你就能夠經過閉包來開始接收事件,而後將其轉換爲 觀察者 ,以便建立屬於你本身的 觀察者 集,接着在委託層中接收信號。若是你查看 RxCocoa 庫的話你會發現這個操做作得也是很是完美的。

相關文章
相關標籤/搜索