歡迎加入QQ討論群258996829
我心依舊 頭像
蘋果1袋
1
我心依舊

RxSwift 函數(shù)響應式編程

發(fā)布時間:2016-01-15 19:08  回復:0  查看:4336   最后回復:2016-01-15 19:08  

作者:Max Alexander

異步操作的代碼為什么會成為程序員的夢魘呢?函數(shù)響應式編程 (Functional Reactive Programming) 旨在簡化異步操作,讓您可以像操作變量一樣來操作閉包。RxSwift 是一個全新的第三方庫,讓您的事件驅(qū)動 (event-driven) 應用更容易進行管理,增強代碼的可讀性,從而減少錯誤的發(fā)生,讓您不再為此而煩惱。Max Alexander 為大家展示了這個庫的基本操作,并講述了如何使用函數(shù)響應式編程來達成我們的目的等內(nèi)容。

About the Speaker: Max Alexander

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

@mbalex99

概述 (0:00)

這幾年有很多人在討論著關于 Rx 的相關概念。Rx 通過 Observable<Element> 接口來表達計算型泛型抽象 (generic abstraction of computation) 的概念,而 RxSwift 是 Rx 的 Swift 版本。無疑,這個內(nèi)容非常龐大,所以我打算用一種稍微簡單點的介紹方式來講解這個框架。

如果有人寫出了下面這么一段代碼,相信肯定會有很多人感到非常不爽:

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 網(wǎng)絡請求方法。如果大家沒用過 Alamofire 的話,可以把它看作是 AFNetworking 框架在執(zhí)行 HTTP 請求操作。在這段代碼中你可以看到各種各樣的嵌套閉包代碼,每多一層嵌套,代碼縮進就會向右移動一層,你知道這段代碼是有問題的,因為你無法直觀地解釋這段代碼做了什么。在這段代碼中涉及到了不少網(wǎng)絡服務,而網(wǎng)絡很有可能會訪問失敗。我們需要為此添加錯誤處理,然而我們卻無法知道該在何處處理這些所有的錯誤異常。Rx 就應運而生,它可以幫助我們解決這個問題。

回到基礎 (2:26)

在處理不同事件的時候,無論如何你都會持有一個包含這些事件的集合。舉個例子,我們有這樣一個整數(shù)數(shù)組 [1, 2, 3, 4, 5, 6],如果你喜歡你也可以稱之為列表。當我需要執(zhí)行某些操作的時候,最符合 Swift 風格的操作就是使用 filter 方法了。

[1, 2, 3, 4, 5, 6].filter{ $0 % 2 == 0 }
那么如果我想要給這個數(shù)組中的所有元素都乘以 5 然后生成一個新數(shù)組呢?
[1, 2, 3, 4, 5, 6].map{ $0 * 5 }
那么執(zhí)行加法操作呢?
[1, 2, 3, 4, 5, 6].reduce(0, +)

這些操作非常簡便。我們沒有使用任何 for 循環(huán)方法,我們也不會持有并保持那些臨時的中間數(shù)。這看起來就非常像 Scala 或者 Haskel 的操作方式。然而,一個正常的應用程序很難只使用數(shù)組就可以完成的了。大家都希望使用網(wǎng)絡、下載圖片、網(wǎng)上聊天、添加好友等等。你需要大量使用 IO 操作。IO 意味著你需要讓內(nèi)存與用戶交互動作、用戶設備、相機、硬盤等等諸如此類的東西進行數(shù)據(jù)交換。這些操作都是異步的,隨時都可能會發(fā)生失敗,并且會發(fā)生很多奇怪的問題。

Rx 權利法案 (4:09)

我提出了一個關于 Rx 的權利法案,它規(guī)定:

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

觀察者 (4:25)

在 Rx 的世界里,讓我們用觀察者 (Observables) 的概念來代替數(shù)組。觀察者是一個類型安全的事件對象,可以長期對不同種類的數(shù)據(jù)值進行寫入和讀出。RxSwift 目前處于 Beta 3 的版本,安裝非常簡單。你所需要做的就是導入 RxSwift 即可。

pod 'RxSwift', '~> 2.0.0-beta.3'
import RxSwift
創(chuàng)建 觀察者 也很容易。最簡單的創(chuàng)建方式就是使用  just ,這是一個 RxSwift 內(nèi)建的函數(shù)。你可以將你所想要的變量放到其中,它就會返回一個包含相同類型的 觀察者 變量。
just(1)  //Observable<Int>
那么如果我們想要從數(shù)組中一個接一個的推出元素并執(zhí)行相關操作呢?
[1,2,3,4,5,6].toObservable()  //Observable<Int>

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

如果你在使用類似于上傳數(shù)據(jù)到 S3 或者向本地數(shù)據(jù)庫保存數(shù)據(jù)之類的 API,你可以這樣寫:

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


  return AnonymousDisposable {

  }
}

當你調(diào)用 create 的時候會返回一個閉包。這個閉包會給予一個觀察者 參數(shù),這意味著有某個東西正在對其進行觀察。目前你可以忽略 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()
  }
}

我可以進行日志記錄操作,也可以執(zhí)行一個 GET 請求,隨后我可以得到一個帶有結果和錯誤的回調(diào)函數(shù)。實際上我無法改變這個 API,因為這是由另一個客戶端 SDK 所提供的,但是我可以將其轉換為一個觀察者對象。當存在錯誤的時候,我會調(diào)用 observer.onError()方法。這意味著只要監(jiān)聽了這個對象的代碼都會接收到這個錯誤消息。當你得到可用的服務器回應時,調(diào)用observable.onNext() 方法。接著,如果處理結束的話就調(diào)用 onComplete()。這個時候我們就到了 AnonymousDisposable 這里了。AnonymousDisposable 是當你想要中止請求的時候被調(diào)用的操作。比如說你離開了當前視圖控制器或者應用已經(jīng)不再需要調(diào)用這個請求的時候,就可以使用這個方法了。這對視頻上傳等大文件操作是非常有用的。當你結束所有操作的時候,request.cancel() 可以清除所有的資源。無論是操作完成還是發(fā)生錯誤,這個方法都會被調(diào)用。

監(jiān)聽觀察者 (8:11)

現(xiàn)在我們知道如何創(chuàng)建觀察者了,那么就來看看如何對其建立監(jiān)聽吧!我們以數(shù)組為例,因為我們可以在很多對象中調(diào)用一個名為 toObservable() 的擴展方法。隨后,就可以編寫監(jiān)聽函數(shù)了:

[1,2,3,4,5,6]
  .toObservable()
  .subscribeNext {
    print($0)
  }
這看起來跟枚舉差不多。Subscribe 監(jiān)聽器事件基于失敗請求、下一步事件以及  onCompleted  操作,給你提供了各種各樣的信息。你可以有選擇性的建立相應的監(jiān)聽:
[1,2,3,4,5,6]
  .toObservable()
  .subscribe(onNext: { (intValue) -> Void in
    // 推出一個整數(shù)數(shù)據(jù)
  }, onError: { (error) -> Void in
    // 發(fā)生錯誤!
  }, onCompleted: { () -> Void in
    // 沒有更多的信號進行處理了
  }) { () -> Void in
    // 我們要清除這個監(jiān)聽器
  }

關聯(lián)觀察者 (9:14)

使用 Rx 的最好例子就是套接字 (socket) 服務了。假設我們有一個網(wǎng)絡套接字服務,它用來監(jiān)聽股票行情,然后顯示用戶的當前賬戶余額 UI 界面。由于股票行情對應了不同的事件,根據(jù)這些事件來決定用戶是否可以購買股票。如果賬戶余額過低的時候我們將禁止用戶購買,當股票票價在用戶的承擔范圍內(nèi)的時候允許用戶購買。

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  意味著當某個事件發(fā)生的時候,我們就將最近的兩個事件之間建立關聯(lián)。Redution 閉包是否會被回調(diào)取決于股票票價是否低于余額。如果被回調(diào),這就意味著用戶可以購買這只股票。這個操作允許你將兩個觀察者關聯(lián)起來,然后列出一個邏輯決定某些操作是否可以進行。這會返回一個  Bool  類型的觀察者。
rx_canBuy()
  .subscribeNext { (canBuy) -> Void in
    self.buyButton.enabled = canBuy
  }

使用 subscribe 方法來操作剛剛我創(chuàng)建的那個會返回 Bool 值的 rx_canBuy 方法。然后你就可以根據(jù) canBuy 的返回值來決定 self.buyButton 的行為了。

讓我們來舉一個合并的例子。假設我有一個存有我所喜歡的股票的用戶界面應用。我通過 Apple、Google 以及 Johnson 來監(jiān)聽股票票價。所有這些股票行情都有不同的結果。當股票行情發(fā)生變化的時候我需要立刻知道并更新我的用戶界面。

let myFavoriteStocks : [Observable<StockPulse>]

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

這些參數(shù)的類型都是相同的 Observable<StockPulse> 類型。我需要知道它們何時被觸發(fā),我需要做的就是持有一個觀察者數(shù)組。里面存放了我需要進行監(jiān)聽的多個不同種類的股票行情,我可以在一個輸出流中將它們合并然后進行監(jiān)聽。

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

我使用 Rx 使用了很長時間。很遺憾,我仍然忘記了許許多多的操作,并且需要非常頻繁地回顧參考文檔。這個網(wǎng)站 rxmarbles.com 將會為我們展示所有這些操作的理論部分。

輕松實現(xiàn)后臺進程 (19:03)

借助 RxSwift 還有許多很贊的事情可以做。比如說你有一個視頻上傳操作,由于這個視頻文件太大了,因此你需要在后臺中進行。執(zhí)行這個操作的最好辦法就是使用 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
  }

視頻上傳操作需要給我當前完成度百分比的信號信息,這樣我才能知道這個操作是否完成。但是我并不打算在主線程中執(zhí)行這個操作,因為我可以在后臺執(zhí)行。當視頻上傳結束之后,我會得到一個返回的 JSON 數(shù)據(jù),它會告知我所上傳的 URL 地址這樣我就可以將其寫入到 UI 標簽當中了。因為我是在后臺進行監(jiān)聽的,因此這個操作不會在主線程上進行。我需要通知 UI 進行更新,這樣才能將信息傳遞給主線程。因此我們需要回去執(zhí)行observeOn(MainScheduler.SharedInstance) 方法,這個操作將會執(zhí)行 UI 更新操作。遺憾的是,和 Android 上的 RxJava 框架不同,在 Swift 中如果你在后臺進程中更新 UI 也是可以進行的(但不要這么做)。在 Android 中這么做會發(fā)生崩潰,而在 Swift 中則不會發(fā)生崩潰。

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

我展示了一些很酷的東西,通過將事件視為數(shù)組,你可以用 RxSwift 讓代碼更簡單、更好用。我知道 MVVM 是一個非常龐大的項目,它可以將視圖控制器從一個完整的個體變成一個個關聯(lián)的組織。RxSwift 有一個相似的名為 RxCocoa 的倉庫,可以有效地解決這個問題。基本上來說,它給所有的 Cocoa 類建立了擴展方法,從而可以讓 UI 視圖可以建立諸如 Rx-Text 或者名字文本框之類的東西。這樣你就可以少寫一點 subscribeNext 方法,從而在多個不同的視圖點中將值和觀察值之間建立關聯(lián)。

跨平臺 (22:49)

我們生活在一個有多個平臺的世界。Rx 對我來說,最主要的吸引點就是 Rx 可以忽略每一個其他客戶端 API 執(zhí)行 IO 的方法。如果你在使用 Android 或者 JavaScript,你必須要學習如何異步管理這些不同的 IO 操作事件。Rx 是一個支持多平臺的輔助庫,你可以在多個熱門語言中使用:.NET、JavaScript、Java,這三門是除了 Swift 之外最熱門的三個語言了。你可以使用相同的操作、步驟和心態(tài)來編寫代碼。在所有的語言當中,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  函數(shù)可以返回一個你所想要的觀察者對象。下面是 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 進行開發(fā)的時候,我安裝了 ReactiveCocoa 的早期版本,使用起來讓我感到很不愉快。我發(fā)現(xiàn)通過 Google 搜索我就可以很快地上手 RxSwift。對我個人而言,RxSwift 和 ReactiveCocoa 我都用。人們總會說這兩個框架之間有著這樣那樣的差別,但是我從來沒有聽人說過 RxSwift 毀了某人的童年,也沒聽說過 ReactiveCocoa 讓某人妻離子散。沒有人曾經(jīng)跟我談論過這些差異。因此使用哪一個是你的自由。RxSwift 是 ReactiveX Github 倉庫下的一個項目,因此如果如果對你來說閱讀代碼和學習很輕松的話,那么就直接使用 RxSwift 吧!如果你的公司只是關于 iOS 以及 Android 的話,那么就使用 ReactiveCocoa 就好了,就不要再考慮別的了。但是如果你需要準備三個平臺的話,比如說有一個和 JS 應用一起使用的電子應用,最好是能夠?qū)⑦@個應用放到 Spotify 上面,然后給三個平臺分別復制服務然后創(chuàng)建一個監(jiān)視器。你可以在一晚上完成這個任務。

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

Max:我打字速度是很快的,比自動完成功能要快得多了。我大多數(shù)時候只在敲下點的時候才查看自動完成列表。如果你輸入的速度過快,那么你很可能就無法得到自動完成提示。這是此時 Xcode 面臨的實際問題。我在使用自動完成的時候并沒有碰到問題,而通常我使用自動完成的方法是 flatMap、merge 和 combineLatest。

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

Max:我之所以提到跨平臺的特性,是因為它本質(zhì)上是一個擁有 API 特性的庫。之所以這么說是因為你可以使用 Java 或者 TypeScript 來實現(xiàn)輔助庫的功能,這個庫本質(zhì)上是獨立運行的。

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

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

問:如何對 RxSwift 進行調(diào)試呢?

Max:有一個帶有閉包的調(diào)試函數(shù)的。RxSwift 有一個類似庫是專門處理閉包的。這個庫不會在正式產(chǎn)品中被使用。你實際使用之后你會發(fā)現(xiàn)它的功能真的十分好用。它會自行創(chuàng)建閉包調(diào)用,因此你無需對這個異步線程進行管理。因此你可以在主線程上面對堆棧進行跟蹤。

問:我在想為什么您覺得專門挑選幾個特殊的例子來介紹 RxSwift 是一個好主意呢?或者說為什么不全用上 RxSwift 或者 Rearctive 來創(chuàng)建一個完整的應用程序呢?

Max:很多對 RxSwift 進行貢獻的人都說,你應當從一開始就在項目中使用 RxSwift。但是這很不現(xiàn)實,因此我決定有選擇性地進行介紹。我不認為在場的絕大多數(shù)觀眾朋友們都能夠有立馬開始新項目的機會。你們大家很多時候是在處理著各種各樣有五年或者六年歷史的代碼庫,因此有選擇性地進行介紹,決定是否使用就看大家是否喜歡了。

問:我已經(jīng)用了很久的 ReactiveCocoa 了,其中一件 ReactiveCocoa 所要做的事就是他們想將所有事件都轉為信號進行處理,這樣你就可以將他們集中在一起。在 Objective-C 中他們使用 Selector 來實現(xiàn)此功能。你是否清楚在 RxSwift 中,它是如何使用 Swift 來處理委托回調(diào)方法的呢?

Max:是的,如果你看一下在 RxCocoa 中這一部分代碼的話,它們會重新激活 (reactifying) 你的UITableViewDelegates 和 UICollectionViewDelegates。它們會創(chuàng)建一個微妙的代理,這樣你就可以通過閉包來開始接收事件,然后將其轉換為觀察者,以便創(chuàng)建屬于你自己的觀察者集,接著在委托層中接收信號。如果你查看 RxCocoa 庫的話你會發(fā)現(xiàn)這個操作做得也是非常完美的。


轉自https://realm.io/cn/news/slug-max-alexander-functional-reactive-rxswift/
您還未登錄,請先登錄

熱門帖子

最新帖子

?