RxSwift筆記 - 特徵序列 (Single/Completable/Driver)

特徵序列 可以看作是 Observable 的另外一個版本。它們之間的區別是:

  • Observable 是能夠用於任何上下文環境的通用序列
  • 而 特徵序列 可以幫助我們更準確的描述序列。同時它們還爲我們提供上下文含義、語法糖,讓我們能夠用更加優雅的方式書寫代碼

Single

它不像 Observable 可以發出多個元素,它要麼只能發出一個元素,要麼產生一個 error 事件。

  • 發出一個元素,或一個 error 事件
  • 不會共享狀態變化

Single 比較常見的例子就是執行 HTTP 請求,然後返回一個應答或錯誤

public enum SingleEvent<Element> {
    case success(Element)   // 裏面包含該 Single 的一個元素值
    case error(Swift.Error) // 用於包含錯誤
}
func getPlayList(_ channel: Sting) -> Single<[String, Any]> {
    return Single<[String, Any]>.create { single in
        let url = "https://www.xxx.com"
        let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _, error in
            if let error = error {
                single(.error(error))
                return
            }
            guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves),
                    let result = json as? [String, Any] else {
                single(.error(DataError))
                return
            }
            single(.success(result))
        }
        task.resume()
        return Disposable.create { task.cancel() }
    }
}

//與數據相關的錯誤類型
enum DataError: Error {
    case cantParseJSON
}

使用

let disposeBag =  DisposeBag()
// 使用技巧一
getPlayList("0").subscribe { event in
    switch event {
        case .success(let json):
            print("JSON結果: ", json)
        case .error(let error):
            print("錯誤: ", error)
    }
}.disposed(by: disposeBag)

// 使用技巧二
getPlayList("0").subscribe(onSuccess: { json in
    print("JSON結果: ", json)
}, onError: { error in
    print("錯誤: ", error)
}).disposed(by: disposeBag)

通過調用 Observable 序列的 .asSingle() 方法,將它轉換爲 Single

Observable.of("1").asSingle().subscribe({
    print($0)
}).disposed(by: disposeBag)

Completable

CompletableObservable 的另外一個版本。不像 Observable 可以發出多個元素,它要麼只能產生一個 completed 事件,要麼產生一個 error 事件

  • 不會發出任何元素
  • 只會發出一個 completed 事件或者一個 error 事件
  • 不會共享狀態變化

結果使用 CompletableEvent 枚舉值表示

  • .completed:用於產生完成事件
  • .error:用於產生一個錯誤
public enum CompletableEvent {
    case error(Swift.Error)
    case completed
}

Completable 和 Observable 有點類似。適用於那些只關心任務是否完成,而不需要在意任務返回值的情況

使用樣例

// 數據存儲
func cacheLocally() -> Completable {
    return Completable.create { completable in
        let success = (arc4random() % 2 == 0)

        guard success else {
            completable(.error(CacheError.failedCaching))
            return Disposable.create{ }
        }
        completable(.completed)
        return Disposable.create{ }
    }
}

// 與緩存相關的錯誤類型
enum CacheError: Error {
    case failedCaching
}

使用

cacheLocally()
    .subscribe { completable in
        switch completable {
        case .completed:
            print("保存成功!")
        case .error(let error):
            print("保存失敗: \(error.localizedDescription)")
        }
    }.disposed(by: disposeBag)

也可以使用 subscribe(onCompleted:onError:) 這種方式:

cacheLocally()
    .subscribe(onCompleted: {
         print("保存成功!")
    }, onError: { error in
        print("保存失敗: \(error.localizedDescription)")
    }).disposed(by: disposeBag)

Maybe

Maybe 同樣是 Observable 的另外一個版本。它介於 SingleCompletable 之間,它要麼只能發出一個元素,要麼產生一個 completed 事件,要麼產生一個 error 事件。

  • 發出一個元素、或者一個 completed 事件、或者一個 error 事件
  • 不會共享狀態變化

結果使用 MaybeEvent 枚舉值表示

  • .success:裏包含該 Maybe 的一個元素值
  • .completed:用於產生完成事件
  • .error:用於產生一個錯誤
public enum MaybeEvent<Element> {
    case success(Element)
    case error(Swift.Error)
    case completed
}

調用 Observable 序列的 .asMaybe() 方法,將它轉換爲 Maybe

Observable.of("1")
    .asMaybe()
    .subscribe({ print($0) })
    .disposed(by: disposeBag)

// success("1")

Driver

Driver 可以說是最複雜的特徵序列,它的目標是提供一種簡便的方式在 UI 層編寫響應式代碼。

特徵

  • 不會產生 error 事件
  • 一定在主線程監聽(MainScheduler
  • 共享狀態變化(shareReplayLatestWhileConnected

Driver 最常使用的場景

  • 通過 CoreData 模型驅動 UI
  • 使用一個 UI 元素值(綁定)來驅動另一個 UI 元素值

使用樣例

根據一個輸入框的關鍵字,來請求數據,然後將獲取到的結果綁定到另一個 Label 和 TableView 中

(1) 初學者使用 Observable 序列加 bindTo 綁定來實現這個功能的話可能會這麼寫
let results = query.rx.text
    .throttle(0.3, scheduler: MainScheduler.instance) // 在主線程中操作,0.3秒內值若多次改變,取最後一次
    .flatMapLatest { query in // 篩選出空值, 拍平序列
        fetchAutoCompleteItems(query) // 向服務器請求一組結果
}

// 將返回的結果綁定到用於顯示結果數量的label上
results
    .map { "\($0.count)" }
    .bind(to: resultCount.rx.text)
    .disposed(by: disposeBag)

// 將返回的結果綁定到tableView上
results
    .bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\(result)"
    }
    .disposed(by: disposeBag)

這個代碼存在如下 3 個問題:

  • 如果 fetchAutoCompleteItems 的序列產生了一個錯誤(網絡請求失敗),這個錯誤將取消所有綁定。此後用戶再輸入一個新的關鍵字時,是無法發起新的網絡請求。
  • 如果 fetchAutoCompleteItems 在後臺返回序列,那麼刷新頁面也會在後臺進行,這樣就會出現異常崩潰。
  • 返回的結果被綁定到兩個 UI 元素上。那就意味着,每次用戶輸入一個新的關鍵字時,就會分別爲兩個 UI 元素髮起 HTTP 請求,這並不是我們想要的結果。
(2) 改進代碼
let results = query.rx.text
    .throttle(0.3, scheduler: MainScheduler.instance)// 在主線程中操作,0.3秒內值若多次改變,取最後一次
    .flatMapLatest { query in // 篩選出空值, 拍平序列
        fetchAutoCompleteItems(query)   // 向服務器請求一組結果
            .observeOn(MainScheduler.instance)  // 將返回結果切換到到主線程上
            .catchErrorJustReturn([])       // 錯誤被處理了,這樣至少不會終止整個序列
    }
    .shareReplay(1)                // HTTP 請求是被共享的

// 將返回的結果綁定到顯示結果數量的label上
results
    .map { "\($0.count)" }
    .bind(to: resultCount.rx.text)
    .disposed(by: disposeBag)

// 將返回的結果綁定到tableView上
results
    .bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\(result)"
    }
    .disposed(by: disposeBag)
如果我們使用 Driver 來實現的話就簡單了
let results = query.rx.text.asDriver()        // 將普通序列轉換爲 Driver
    .throttle(0.3, scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
            .asDriver(onErrorJustReturn: [])  // 僅僅提供發生錯誤時的備選返回值
    }

// 將返回的結果綁定到顯示結果數量的label上
results
    .map { "\($0.count)" }
    .drive(resultCount.rx.text) // 這裏使用 drive 而不是 bindTo
    .disposed(by: disposeBag)

// 將返回的結果綁定到tableView上
results
    .drive(resultsTableView.rx.items(cellIdentifier: "Cell")) { //  同樣使用 drive 而不是 bindTo
        (_, result, cell) in
        cell.textLabel?.text = "\(result)"
    }
    .disposed(by: disposeBag)

代碼講解:

  • 使用 asDriver 方法將 ControlProperty 轉換爲 Driver
  • 接着我們可以用 .asDriver(onErrorJustReturn: []) 方法將任何 Observable 序列都轉成 Driver,因爲我們知道序列轉換爲 Driver 要他滿足 3 個條件:

    • 不會產生 error 事件
    • 一定在主線程監聽(MainScheduler
    • 共享狀態變化(shareReplayLatestWhileConnected
  • 同時在 Driver 中,框架已經默認幫我們加上了 shareReplayLatestWhileConnected,所以我們也沒必要再加上"replay"相關的語句了

  • 最後使用 drive 而不是 bindTo

由於 drive 方法只能被 Driver 調用。這意味着,如果代碼存在 drive,那麼這個序列不會產生錯誤事件並且一定在主線程監聽。這樣我們就可以安全的綁定 UI 元素


ControlProperty

1 ControlProperty 是專門用來描述 UI 控件屬性,擁有該類型的屬性都是被觀察者(Observable

2 ControlProperty 具有以下特徵:

  • 不會產生 error 事件
  • 一定在 MainScheduler 訂閱(主線程訂閱)
  • 一定在 MainScheduler 監聽(主線程監聽)
  • 共享狀態變化

ControlProperty 主要運用於 UI 控件, 例如: UILabel UItextField


ControlEvent

1 ControlEvent 是專門用於描述 UI 所產生的事件,擁有該類型的屬性都是被觀察者(Observable

2 ControlEventControlProperty 一樣,都具有以下特徵:

  • 不會產生 error 事件
  • 一定在 MainScheduler 訂閱(主線程訂閱)
  • 一定在 MainScheduler 監聽(主線程監聽)
  • 共享狀態變化

ControlEvent 主要運用於 UIControl 控件, 例如: UIButton

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