RxSwift学习-07-老司机Driver

367 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

  • 本文主要介绍RxSwift中的Driver的使用和原理,对于driver本质上就是我们的序列进行封装,通常我们操作UI的序列会使用driver

1. 使用场景

我们模拟我们输入框输入进行搜索,实时变化搜索的场景,类似我们点外卖搜索
首先看下下面的代码,我们定义下模拟网络请求,同时我们添加逻辑判断当输入1234的时候发送错误事件,其他情况下我们在全局异步函数下发送我们模拟请求的数据


func dealwithData(inputText:String)-> Observable<Any>{

        print("请求网络了 \(Thread.current)") // data

        return Observable<Any>.create({ (ob) -> Disposable in

            if inputText == "1234" {

                ob.onError(NSError.init(domain: "密码过于简单", code: 400, userInfo: nil))

            }

            

            DispatchQueue.global().async {

                print("发送之前看看: \(Thread.current)")

                ob.onNext("已经输入:\(inputText)")

                ob.onCompleted()

            }

            return Disposables.create()

        })

    }

我们在输入框中添加rx源序列,我们想要实现的效果是输入的时候进行请求,把请求的结果进行展示。正常情况下我们这样操作

let result = inputTF.rx.text.orEmpty.skip(1)

            .flatMap { [ weak self](input) -> Observable<Any> in

                return (self?.dealwithData(inputText: input))!

            }

        

        result.subscribe(onNext:{(str) in

            print("订阅到:\(str)")

        }).disposed(by: disposeBag)

        result.subscribe(onNext:{(str) in

            print("订阅到:\(str)-\(Thread.current)")

        }).disposed(by: disposeBag)

我们订阅2次,实时监听我们的输入框的内容。打印结果如下

image.png

我们可以发现我们订阅的时候发现会进行多次请求,很明显不符合我们的要求,我们希望达到共享的请求数据。其次我们如果在订阅中更新UI线程会造成问题

image.png

我们因此需要进行修复下,我们的源序列在转换后需要调度在主线程,同时我们也要使用throttle来添加限制条件。throttle:指定了0.5,所以在0.5以内,只接收了第一条和最新的数据

let result = inputTF.rx.text.orEmpty.skip(1)

            .throttle(DispatchTimeInterval.milliseconds(500), scheduler: MainScheduler())

            .flatMap { [ weak self](input) -> Observable<Any> in

                return (self?.dealwithData(inputText: input))!

                    .observe(on: MainScheduler())

            }

打印结果: image.png

我们继续解决关于共享的问题

let result = inputTF.rx.text.orEmpty.skip(1)

            .throttle(DispatchTimeInterval.milliseconds(500), scheduler: MainScheduler())

            .flatMap { [ weak self](input) -> Observable<Any> in

                return (self?.dealwithData(inputText: input))!

                    .observe(on: MainScheduler())

            }.share()

image.png

我们继续处理下错误事件

let result = inputTF.rx.text.orEmpty.skip(1)

            .throttle(DispatchTimeInterval.milliseconds(500), scheduler: MainScheduler())

            .flatMap { [ weak self](input) -> Observable<Any> in

                return (self?.dealwithData(inputText: input))!

                    .observe(on: MainScheduler())

                    .catchAndReturn("检测到了错误事件")

            }.share()

我们自己可以处理错误事件 image.png

但是我们监测到错误后无法继续了,发送了dispose事件。我们可以使用share(replay: 1, scope: .whileConnected)来解决,其中replay表示缓冲区的最大元素计数,这里表示一次error事件。scope表示共享范围

image.png

2. 使用Driver

我们使用Driver进行处理

let result  = inputTF.rx.text.orEmpty

            .asDriver()

            .flatMap {

                return self.dealwithData(inputText: $0)

                    .asDriver(onErrorJustReturn: "检测到了错误事件")

            }

        result.map { "长度: \(($0 as! String).count)"}

              .drive(self.textLabel.rx.text)

              .disposed(by: disposeBag)
              
        result.map { "\($0 as! String)"}

            .drive(self.btn.rx.title())

            .disposed(by: disposeBag)
    

         result.drive(onNext:{(str) in

                    print("1订阅到:\(str)")

                    self.textLabel.text = (str as! String)

                }).disposed(by: disposeBag)

                result.drive(onNext:{(str) in

                    print("2订阅到:\(str)-\(Thread.current)")

                }).disposed(by: disposeBag)

这里我们把序列转化为Driver序列,在订阅的时候我们使用drive替代subscribe,打印结果。

image.png

我们可以发现解决了我们的问题,我们它请求一次网络达到了共享序列的效果,其次我们可以发现UI的事件是在主线程上,不用我们手动调度;最后我们的error事件发送后还可以继续接受事件

result.map { "长度: \(($0 as! String).count)"}

              .drive(self.textLabel.rx.text)

              .disposed(by: disposeBag)

        result.map { "\($0 as! String)"}

            .drive(self.btn.rx.title())

            .disposed(by: disposeBag)

我们可以使用类似bind的效果使用drive进行绑定。

3. 原理解析

首先我们看下为何可以调用

image.png

可以发现对于ControlPropertyControlEventBehaviorRelay的拓展可以转换为Driver。
Driver源码image.png

通过描述我们可以知道具有下面功能

  • 不会失败
  • 处理回调在主线程MainScheduler.instance
  • 具有share策略

我们依次分析下它具有这些能力的原因,我们通常会把序列通过asDriver()来变成Driver<Element>序列。因此我们看下这个方法

3.1 ObservableConvertibleType拓展

我们看下ObservableConvertibleType的拓展关于driver的一些便捷方法

image.png

主要分为3种,主要是通过高阶函数组合的方式把我们的错误事件进行处理,因此对于driver序列不会失败也是可以解释了。因为它已经帮我们处理了error的事件

对于我们没有指定错误处理,会走最后一个方法。可以看到对我们原有的源做了处理,让它订阅在主线程上,同时捕获处理错误事件。之后根据源初始化Driver,调用Driver(source)

image.png

类型是SharedSequence类型,查看其初始化

image.png

3.2 主线程调度

DriverSharingStrategy
对于结构体它具有scheduler属性,会生成一个默认主线程的调度环境

image.png 继续看下MainScheduler的init

image.png 关于主线后面会在调度的时候分析。

3.3 订阅

相比我们普通的序列使用subscribe进行订阅,这里是对其进行了一层封装。使用drive

image.png

之后在调用我们普通序列的事件回调。对于我们绑定事件的话则是类似我们序列的bind

image.png

查看其实现:

image.png

4. 总结

对于Dirver我们可以看到它时对普通的序列进行一些高阶函数的封装,使它具备了共享序列源错误处理主线程调度的功能。这些很好的为我们关于UI方面的操作,因此作者也是基于对ObservableConvertibleType协议作出了拓展。以下为driver的大概流程:

老司机Dirver.jpg