特徵序列 可以看作是 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
Completable
是 Observable
的另外一個版本。不像 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
的另外一個版本。它介於 Single
和 Completable
之間,它要麼只能發出一個元素,要麼產生一個 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 ControlEvent
和 ControlProperty
一樣,都具有以下特徵:
- 不會產生
error
事件 - 一定在
MainScheduler
訂閱(主線程訂閱) - 一定在
MainScheduler
監聽(主線程監聽) - 共享狀態變化
ControlEvent
主要運用於 UIControl 控件, 例如: UIButton