RxSwift笔记 - 特征序列 (Single/Completable/Driver)

57 篇文章 1 订阅
23 篇文章 0 订阅

特征序列 可以看作是 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值