美文网首页
RxSwift 核心之Observable - 可监听序列

RxSwift 核心之Observable - 可监听序列

作者: 一眼万年的星空 | 来源:发表于2022-01-16 17:53 被阅读0次
    Obervable

    所有的事物都是序列

    之前我们提到,Observable 可以用于描述元素异步产生的序列。这样我们生活中许多事物都可以通过它来表示,例如:

    • Observable<Double> 温度

      你可以将温度看作是一个序列,然后监测这个温度值,最后对这个值做出响应。例如:当室温高于 33 度时,打开空调降温。

    Obervable
    • Observable<OnePieceEpisode> 《海贼王》动漫

      你也可以把《海贼王》的动漫看作是一个序列。然后当《海贼王》更新一集时,我们就立即观看这一集。

    Obervable
    • Observable<JSON> JSON

      你可以把网络请求的返回的 JSON 看作是一个序列。然后当取到 JSON 时,将它打印出来。

      JSON
    • Observable<Void> 任务回调

      你可以把任务回调看作是一个序列。当任务结束后,提示用户任务已完成。

    Callback

    如何创建序列

    现在我们已经可以把生活中的许多事物看作是一个序列了。那么我们要怎么创建这些序列呢?

    实际上,框架已经帮我们创建好了许多常用的序列。例如:button的点击,textField的当前文本,switch的开关状态,slider的当前数值等等。

    另外,有一些自定义的序列是需要我们自己创建的。这里介绍一下创建序列最基本的方法,例如,我们创建一个 [0, 1, ... 8, 9] 的序列:

    Obervable
    let numbers: Observable<Int> = Observable.create { observer -> Disposable in
    
        observer.onNext(0)
        observer.onNext(1)
        observer.onNext(2)
        observer.onNext(3)
        observer.onNext(4)
        observer.onNext(5)
        observer.onNext(6)
        observer.onNext(7)
        observer.onNext(8)
        observer.onNext(9)
        observer.onCompleted()
    
        return Disposables.create()
    }
    
    

    创建序列最直接的方法就是调用 Observable.create,然后在构建函数里面描述元素的产生过程。observer.onNext(0) 就代表产生了一个元素,他的值是 0。后面又产生了 9 个元素分别是 1, 2, ... 8, 9 。最后,用 observer.onCompleted() 表示元素已经全部产生,没有更多元素了。

    你可以用这种方式来封装功能组件,例如,闭包回调:

    JSON
    typealias JSON = Any
    
    let json: Observable<JSON> = Observable.create { (observer) -> Disposable in
    
        let task = URLSession.shared.dataTask(with: ...) { data, _, error in
    
            guard error == nil else {
                observer.onError(error!)
                return
            }
    
            guard let data = data,
                let jsonObject = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
                else {
                observer.onError(DataError.cantParseJSON)
                return
            }
    
            observer.onNext(jsonObject)
            observer.onCompleted()
        }
    
        task.resume()
    
        return Disposables.create { task.cancel() }
    }
    
    

    在闭包回调中,如果任务失败,就调用 observer.onError(error!)。如果获取到目标元素,就调用 observer.onNext(jsonObject)。由于我们的这个序列只有一个元素,所以在成功获取到元素后,就直接调用 observer.onCompleted() 来表示任务结束。最后 Disposables.create { task.cancel() }说明如果数据绑定被清除(订阅被取消)的话,就取消网络请求。

    这样一来我们就将传统的闭包回调转换成序列了。然后可以用 subscribe 方法来响应这个请求的结果:

    json
        .subscribe(onNext: { json in
            print("取得 json 成功: \(json)")
        }, onError: { error in
            print("取得 json 失败 Error: \(error.localizedDescription)")
        }, onCompleted: {
            print("取得 json 任务成功完成")
        })
        .disposed(by: disposeBag)
    
    

    这里subscribe后面的onNext,onError, onCompleted 分别响应我们创建 json 时,构建函数里面的onNext,onError, onCompleted 事件。我们称这些事件为 Event:

    Event - 事件

    public enum Event<Element> {
        case next(Element)
        case error(Swift.Error)
        case completed
    }
    
    
    • next - 序列产生了一个新的元素
    • error - 创建序列时产生了一个错误,导致序列终止
    • completed - 序列的所有元素都已经成功产生,整个序列已经完成

    你可以合理的利用这些 Event 来实现业务逻辑。

    决策树

    现在我们知道如何用最基本的方法创建序列。你还可可以去学学 决策树来选择其他的方式创建序列。

    特征序列

    我们都知道 Swift 是一个强类型语言,而强类型语言相对于弱类型语言的一个优点是更加严谨。我们可以通过类型来判断出,实例有哪些特征。同样的在 RxSwift 里面 Observable 也存在一些特征序列,这些特征序列可以帮助我们更准确的描述序列。并且它们还可以给我们提供语法糖,让我们能够用更加优雅的方式书写代码,他们分别是:

    • Single
    • Completable
    • Maybe
    • Driver
    • Signal
    • ControlEvent

    Single

    SingleObservable 的另外一个版本。不像 Observable 可以发出多个元素,它要么只能发出一个元素,要么产生一个 error 事件。

    • 发出一个元素,或一个 error 事件
    • 不会共享附加作用

    一个比较常见的例子就是执行 HTTP 请求,然后返回一个应答错误。不过你也可以用 Single 来描述任何只有一个元素的序列。

    如何创建 Single

    创建 Single 和创建 Observable 非常相似:

    func getRepo(_ repo: String) -> Single<[String: Any]> {
    
        return Single<[String: Any]>.create { single in
            let url = URL(string: "https://api.github.com/repos/(repo)")!
            let task = URLSession.shared.dataTask(with: 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.cantParseJSON))
                    return
                }
    
                single(.success(result))
            }
    
            task.resume()
    
            return Disposables.create { task.cancel() }
        }
    }
    

    之后,你可以这样使用 Single

    getRepo("ReactiveX/RxSwift")
        .subscribe(onSuccess: { json in
            print("JSON: ", json)
        }, onError: { error in
            print("Error: ", error)
        })
        .disposed(by: disposeBag)
    

    订阅提供一个 SingleEvent 的枚举:

    public enum SingleEvent<Element> {
        case success(Element)
        case error(Swift.Error)
    }
    
    • success - 产生一个单独的元素
    • error - 产生一个错误

    你同样可以对 Observable 调用 .asSingle() 方法,将它转换为 Single

    Completable

    CompletableObservable 的另外一个版本。不像 Observable 可以发出多个元素,它要么只能产生一个 completed 事件,要么产生一个 error 事件。

    • 发出零个元素
    • 发出一个 completed 事件或者一个 error 事件
    • 不会共享附加作用

    Completable 适用于那种你只关心任务是否完成,而不需要在意任务返回值的情况。它和 Observable<Void> 有点相似。

    如何创建 Completable

    创建 Completable 和创建 Observable 非常相似:

    func cacheLocally() -> Completable {
        return Completable.create { completable in
           // Store some data locally
           ...
           ...
    
           guard success else {
               completable(.error(CacheError.failedCaching))
               return Disposables.create {}
           }
    
           completable(.completed)
           return Disposables.create {}
        }
    }
    

    之后,你可以这样使用 Completable

    cacheLocally()
        .subscribe(onCompleted: {
            print("Completed with no error")
        }, onError: { error in
            print("Completed with an error: (error.localizedDescription)")
         })
        .disposed(by: disposeBag)
    

    订阅提供一个 CompletableEvent 的枚举:

    public enum CompletableEvent {
        case error(Swift.Error)
        case completed
    }
    
    • completed - 产生完成事件
    • error - 产生一个错误

    May

    MaybeObservable 的另外一个版本。它介于 SingleCompletable 之间,它要么只能发出一个元素,要么产生一个 completed 事件,要么产生一个 error 事件。

    • 发出一个元素或者一个 completed 事件或者一个 error 事件
    • 不会共享附加作用

    如果你遇到那种可能需要发出一个元素,又可能不需要发出时,就可以使用 Maybe

    如何创建 Maybe

    创建 Maybe 和创建 Observable 非常相似:

    func generateString() -> Maybe<String> {
        return Maybe<String>.create { maybe in
            maybe(.success("RxSwift"))
    
            // OR
    
            maybe(.completed)
    
            // OR
    
            maybe(.error(error))
    
            return Disposables.create {}
        }
    }
    

    之后,你可以这样使用 Maybe

    generateString()
        .subscribe(onSuccess: { element in
            print("Completed with element (element)")
        }, onError: { error in
            print("Completed with an error (error.localizedDescription)")
        }, onCompleted: {
            print("Completed with no element")
        })
        .disposed(by: disposeBag)
    

    你同样可以对 Observable 调用 .asMaybe() 方法,将它转换为 Maybe

    Driver

    Driver(司机?) 是一个精心准备的特征序列。它主要是为了简化 UI 层的代码。不过如果你遇到的序列具有以下特征,你也可以使用它:

    • 不会产生 error 事件
    • 一定在 MainScheduler 监听(主线程监听)
    • 共享附加作用

    这些都是驱动 UI 的序列所具有的特征。

    为什么要使用 Driver ?

    我们举个例子来说明一下,为什么要使用 Driver

    这是文档简介页的例子:

    let results = query.rx.text
        .throttle(0.3, scheduler: MainScheduler.instance)
        .flatMapLatest { query in
            fetchAutoCompleteItems(query)
        }
    
    results
        .map { "($0.count)" }
        .bind(to: resultCount.rx.text)
        .disposed(by: disposeBag)
    
    results
        .bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) {
          (_, result, cell) in
            cell.textLabel?.text = "(result)"
        }
        .disposed(by: disposeBag)
    

    这段代码的主要目的是:

    • 取出用户输入稳定后的内容
    • 向服务器请求一组结果
    • 将返回的结果绑定到两个 UI 元素上:tableView 和 显示结果数量的label

    那么这里存在什么问题?

    • 如果 fetchAutoCompleteItems 的序列产生了一个错误(网络请求失败),这个错误将取消所有绑定,当用户输入一个新的关键字时,是无法发起新的网络请求。
    • 如果 fetchAutoCompleteItems 在后台返回序列,那么刷新页面也会在后台进行,这样就会出现异常崩溃。
    • 返回的结果被绑定到两个 UI 元素上。那就意味着,每次用户输入一个新的关键字时,就会分别为两个 UI 元素发起 HTTP 请求,这并不是我们想要的结果。

    一个更好的方案是这样的:

    let results = query.rx.text
        .throttle(0.3, scheduler: MainScheduler.instance)
        .flatMapLatest { query in
            fetchAutoCompleteItems(query)
                .observeOn(MainScheduler.instance)  // 结果在主线程返回
                .catchErrorJustReturn([])           // 错误被处理了,这样至少不会终止整个序列
        }
        .share(replay: 1)                             // HTTP 请求是被共享的
    
    results
        .map { "($0.count)" }
        .bind(to: resultCount.rx.text)
        .disposed(by: disposeBag)
    
    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: [])  // 仅仅提供发生错误时的备选返回值
        }
    
    results
        .map { "($0.count)" }
        .drive(resultCount.rx.text)               // 这里改用 `drive` 而不是 `bindTo`
        .disposed(by: disposeBag)                 // 这样可以确保必备条件都已经满足了
    
    results
        .drive(resultsTableView.rx.items(cellIdentifier: "Cell")) {
          (_, result, cell) in
            cell.textLabel?.text = "(result)"
        }
        .disposed(by: disposeBag)
    

    首先第一个 asDriver 方法将 ControlProperty 转换为 Driver

    然后第二个变化是:

    .asDriver(onErrorJustReturn: [])
    

    任何可监听序列都可以被转换为 Driver,只要他满足 3 个条件:

    • 不会产生 error 事件
    • 一定在 MainScheduler 监听(主线程监听)
    • 共享附加作用

    那么要如何确定条件都被满足?通过 Rx 操作符来进行转换。asDriver(onErrorJustReturn: []) 相当于以下代码:

    let safeSequence = xs
      .observeOn(MainScheduler.instance)       // 主线程监听
      .catchErrorJustReturn(onErrorJustReturn) // 无法产生错误
      .share(replay: 1, scope: .whileConnected)// 共享附加作用
    return Driver(raw: safeSequence)           // 封装
    

    最后使用 drive 而不是 bindTo

    drive 方法只能被 Driver 调用。这意味着,如果你发现代码所存在 drive,那么这个序列不会产生错误事件并且一定在主线程监听。这样你可以安全的绑定 UI 元素。

    Signal

    Signal 和 Driver 相似,唯一的区别是,Driver 对新观察者回放(重新发送)上一个元素,而 Signal 不会对新观察者回放上一个元素。

    他有如下特性:

    • 不会产生 error 事件
    • 一定在 MainScheduler 监听(主线程监听)
    • 共享附加作用

    现在,我们来看看以下代码是否合理:

    let textField: UITextField = ...
    let nameLabel: UILabel = ...
    let nameSizeLabel: UILabel = ...
    
    let state: Driver<String?> = textField.rx.text.asDriver()
    
    let observer = nameLabel.rx.text
    state.drive(observer)
    
    // ... 假设以下代码是在用户输入姓名后运行
    
    let newObserver = nameSizeLabel.rx.text
    state.map { $0?.count.description }.drive(newObserver)
    

    这个例子只是将用户输入的姓名绑定到对应的标签上。当用户输入姓名后,我们创建了一个新的观察者,用于订阅姓名的字数。那么问题来了,订阅时,展示字数的标签会立即更新吗?

    嗯、、、 因为 Driver 会对新观察者回放上一个元素(当前姓名),所以这里是会更新的。在对他进行订阅时,标签的默认文本会被刷新。这是合理的。

    那如果我们用 Driver 来描述点击事件呢,这样合理吗?

    let button: UIButton = ...
    let showAlert: (String) -> Void = ...
    
    let event: Driver<Void> = button.rx.tap.asDriver()
    
    let observer: () -> Void = { showAlert("弹出提示框1") }
    event.drive(onNext: observer)
    
    // ... 假设以下代码是在用户点击 button 后运行
    
    let newObserver: () -> Void = { showAlert("弹出提示框2") }
    event.drive(onNext: newObserver)
    

    当用户点击一个按钮后,我们创建一个新的观察者,来响应点击事件。此时会发生什么?Driver 会把上一次的点击事件回放给新观察者。所以,这里的 newObserver 在订阅时,就会接受到上次的点击事件,然后弹出提示框。这似乎不太合理。

    因此像这类型的事件序列,用 Driver 建模就不合适。于是我们就引入了 Signal:

    ...
    
    let event: Signal<Void> = button.rx.tap.asSignal()
    
    let observer: () -> Void = { showAlert("弹出提示框1") }
    event.emit(onNext: observer)
    
    // ... 假设以下代码是在用户点击 button 后运行
    
    let newObserver: () -> Void = { showAlert("弹出提示框2") }
    event.emit(onNext: newObserver)
    

    在同样的场景中,Signal 不会把上一次的点击事件回放给新观察者,而只会将订阅后产生的点击事件,发布给新观察者。这正是我们所需要的。

    结论

    一般情况下状态序列我们会选用 Driver 这个类型,事件序列我们会选用 Signal 这个类型。

    ControlEvent

    ControlEvent 专门用于描述 UI 控件所产生的事件,它具有以下特征:

    • 不会产生 error 事件
    • 一定在 MainScheduler 订阅(主线程订阅)
    • 一定在 MainScheduler 监听(主线程监听)

    青山不改,绿水长流,后会有期,感谢每一位佳人的支持!

    相关文章

      网友评论

          本文标题:RxSwift 核心之Observable - 可监听序列

          本文链接:https://www.haomeiwen.com/subject/mbgfhrtx.html