RXSwift在项目中实战总结: Subject, Driver, 高阶函数

1.Subject

Subject 既攻也受,它既可以作为序列,又可以作为观察者。下面我们看一下既攻也守的原理:

public protocol SubjectType : ObservableType {
    /// The type of the observer that represents this subject.
    ///
    /// Usually this type is type of subject itself, but it doesn't have to be.
    associatedtype Observer: ObserverType

    /// Returns observer interface for subject.
    ///
    /// - returns: Observer interface for subject.
    func asObserver() -> Observer
    
}

1)SubjectType继承了ObservableType,具有序列的特性

2)又通过associatedType关联了ObserverType,具有了观察者的特性

3)通过asObserver方法,可以直接从序列转化为观察者

1.最常用的Subject 1: BehaviorRelay

在5.0之前的版本最常用的是Variable,但是后来被废弃了,现在由BehaviorRelay代替,现在最常用的就是BehaviorRelay。BehaviorRelay的特性:

I. 可以存储一个信号(也就是我们常说的默认值)

II. 随时订阅响应(也就是BehaviorRelay的值一旦发生变化,就会立即被订阅到)

III. 注意:响应发送的时候使用的方法是accept方法: behaviorRelay.accept(300)

III.替换原来的Variable

示例代码:

func testBehaviorRelay() {
        let behaviorRelay = BehaviorRelay(value: 300)
        behaviorRelay.subscribe(onNext: { (num) in
            print("订阅\(num)")
        }).disposed(by: disposeBag)
        print("打印:\(behaviorRelay.value)")

        behaviorRelay.accept(3000)
 }


打印结果:
订阅300
打印300
订阅3000

 注意:初始化值300也是可以订阅到的

2.最常用的Subject 2: PublishSubject

PublishSubject 可以不需要初始来进行初始化(也就是可以为空),并且它只会向订阅者发送在订阅之后才接收到的元素。

func testPublishSubject() {
        // 1:初始化序列
        let publishSub = PublishSubject<Int>() //初始化一个PublishSubject 装着Int类型的序列
        // 2:发送响应序列
        publishSub.onNext(1)
        // 3:订阅序列
        publishSub.subscribe {
            print("订阅到了:",$0)
        }.disposed(by: disposeBag)
        // 再次发送响应
        publishSub.onNext(2)
        publishSub.onNext(3)
}

订阅到了: next(2)
订阅到了: next(3)

注意:信号1没有被订阅到,只接受订阅之后的响应


2.Driver

首先介绍两个概念,RXSwif的防抖与节流:比如在日常开发中, scroll、click、reload等高频率的触发事件,会过度损耗页面性能,导致页面卡顿,页面抖动。有时我们不希望在事件持续触发的过程中那么频繁地去执,此时防抖和节流是比较好的解决方案。

防抖:比如TextField一直输入时, 忽略 2 秒内的响应, 停止输入 2 秒后响应一次

userNameTextField.rx.text
            .debounce(RxTimeInterval.seconds(2), scheduler: MainScheduler.instance)
            .subscribe(onNext: { str in
                let date = Date()
                let format = DateFormatter()
                format.dateFormat = "HH:mm:ss"
                print("RX的debounce: \(format.string(from: date)) - \(str ?? "")")
            }).disposed(by: disposeBag)

输出:
RX的debounce: 17:53:22 - 
RX的debounce: 17:53:24 - 111

节流:比如TextField一直输入时, 每 2 秒触发一次

 // 节流 (TextField一直输入时, 每 2 秒触发一次)
        userName.rx.text
            .throttle(RxTimeInterval.seconds(2), scheduler: MainScheduler.instance)
            .subscribe(onNext: { str in
                let date = Date()
                let format = DateFormatter()
                format.dateFormat = "HH:mm:ss"
                print("throttle: \(format.string(from: date)) - \(str ?? "")")
            }).disposed(by: disposeBag)

输出:
throttle: 18:02:51 - Optional("")
throttle: 18:02:53 - Optional("1")
throttle: 18:02:55 - Optional("11111")

介绍Driver:

Driver 是一个精心准备的特征序列。它主要是为了简化 UI 层的代码,比如获取网络数据展示在UI界面上,今天我们就对这个功能来讲讲Driver的使用。因为它具有以下Driver的特征:

I. 不会产生 error 事件

II. 一定在 MainScheduler 监听(主线程监听)

III. 会共享附加作用

1. 网络请求代码

func dealWithData(inputText: String)-> Observable<Any> {
            print("请求网络:\(Thread.current)")
            return Observable<Any>.create({ ob -> Disposable in
                if inputText == "1234" {
                    ob.onError(NSError.init(domain: "LcrError", code: 10085, userInfo: nil))
                }
                DispatchQueue.global().async {
                    print("发送之前:\(Thread.current)")
                    ob.onNext("已经输入:\(inputText)")
                    ob.onCompleted()
                }
                return Disposables.create()
            })
        }

输出:
请求网络:<_NSMainThread: 0x6000027705c0>{number = 1, name = main}
发送之前:<NSThread: 0x600002752600>{number = 5, name = (null)}
next(已经输入:123456)
completed

接下来再介绍RXSwift的三个高阶函数:

I. RXSwift的skip函数:从源可观察序列发出元素,直到跳过skip设置的个数再开始响应订阅。这个应用非常频繁 ,比如textField被点击但是还未输入文字的时候,不使用skip(1),就会被订阅到空字符串,这是不需要的,还有其他一些可观察序列的初始化过程是需要要订阅到的。

func testSkipMethod() {
        Observable.of(1, 2, 3, 4, 5, 6)
            .skip(2)
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
    }

输出:
2
3
4
5
6

II. RXSwift的map函数:map函数闭包应用于可观察序列发出的元素,并返回转换后的元素组成的新可观察序列。

let ob = Observable.of(1,2,3,4)
        ob.map { (number) -> Int in
            return number+2
            }.subscribe{
                print("\($0)")
            }.disposed(by: disposeBag)

输出:
next(3)
next(4)
next(5)
next(6)
completed

III. RXSwift的flatMap函数:将可观察序列发射的元素转换为可观察序列,并将两个可观察序列的发射合并为一个可观察序列。比如此操作符会对源Observable的每一个元素应用一个转换方法,将它们转换成Observables。然后将这些Observables的元素合并后再发送出来,即又将其合成一个Observable序列。比如当Observable的元素本身拥有其他的Observable时,我们可以将所有子Observables的元素发送出来。

let result = userNameTextField.rx.text.skip(1).flatMap { input in
                        return self.dealWithData(inputText: input!)
              }
        result.subscribe({ element in
               print("订阅到了\(element)")
        }).disposed(by: disposeBag)
        result.subscribe({ element in
               print("订阅到了\(element) - \(Thread.current)")
               //更新UI
        }).disposed(by: disposeBag)

输出:
请求网络:<_NSMainThread: 0x6000030140c0>{number = 1, name = main}
请求网络:<_NSMainThread: 0x6000030140c0>{number = 1, name = main}
发送之前:<NSThread: 0x6000030b1300>{number = 10, name = (null)}
订阅到了next(已经输入:11)
发送之前:<NSThread: 0x6000030377c0>{number = 11, name = (null)}
订阅到了next(已经输入:11) - <NSThread: 0x6000030377c0>{number = 11, name = (null)}

由上面输出结果可以看出分别进行了两次网络请求,并且两次都有返回,网络请求在主线程,发送之前是在子线程发出去的,然后打印结果也是在子线程,那么我们就能发现如下几个问题:

I. 当订阅几次时,会进行几次网络请求,但数据都是一样的,会造成网络资源的浪费

II. 如果我们在最后返回时候如果要进行UI刷新的话,在子线程就会报错甚至崩溃

III. 错误事件的处理,当输入1234时,会返回错误,后续订阅就失效,订阅就断开,再输入其他都没用了。

​​​​​​​2.进一步优化后的代码:

let result = userName.rx.text.skip(1)
                    .flatMap { input in
                        return self.dealWithData(inputText: input!)
                            .observe(on: MainScheduler())
                            .catchAndReturn("监测到错误事件")
                    }
                    .share(replay: 1, scope: .whileConnected)

        result.subscribe({ element in
               print("订阅到了\(element)")
        }).disposed(by: disposeBag)
        result.subscribe({ element in
               print("订阅到了\(element) - \(Thread.current)")
               //更新UI
        }).disposed(by: disposeBag)

输出:
请求网络:<_NSMainThread: 0x600001ccc040>{number = 1, name = main}
发送之前:<NSThread: 0x600001c55780>{number = 8, name = (null)}
订阅到了next(已经输入:11)
订阅到了next(已经输入:11) - <_NSMainThread: 0x600001ccc040>{number = 1, name = main}

优化后的代码问题得到明显的解决,多个订阅只有一次网络请求,订阅后收到信号是在主线程,更新UI不会发生错误,错误信号返回后,订阅不会中断,还可以继续运行。

I. share(replay: 1, scope: .whileConnected) 控制多次订阅网络请求只有一次,达到共享网络数据目的

II. observe(on: MainScheduler()) 控制信号返回是在主线程,这样更新UI等要求在主线程的操作不会发生错误

III. catchAndReturn("监测到错误事件") 处理error信号, 保证在遇到错误情况后不会停止订阅信号。

3.关于这个问题的最优解:Driver

let result = userName.rx.text
                    .asDriver()
                    .flatMap {
                        return self.dealWithData(inputText: $0!)
                            .asDriver(onErrorJustReturn: "监测到错误事件")
                    }
            result.map { "输入字符串长度:\(($0 as! String).count)" }
            .drive(userNameTip.rx.text).disposed(by: disposeBag)

            result.map { "\($0)" }
            .drive(passwordTip.rx.text).disposed(by: disposeBag)

我们可以看到,使用Driver简洁高效的完成了上面的功能,接下来我们看一下Driver的底层原理

1)点击.asDriver(onErrorJustReturn: "监测到错误事件")进入底层源码

public func asDriver(onErrorJustReturn: Element) -> Driver<Element> {
        let source = self
            .asObservable()
            .observe(on:DriverSharingStrategy.scheduler)
            .catchAndReturn(onErrorJustReturn)
        return Driver(source)
    }

2)我们可以看到这段代码里实现了 catchAndReturn(onErrorJustReturn) 错误处理方法,再点击 .observe(on:DriverSharingStrategy.scheduler) 里的 DriverSharingStrategy.scheduler 进入源码

public struct DriverSharingStrategy: SharingStrategyProtocol {
    public static var scheduler: SchedulerType { SharingScheduler.make() }
    public static func share<Element>(_ source: Observable<Element>) -> Observable<Element> {
        source.share(replay: 1, scope: .whileConnected)
    }
}

3)再点击 SharingScheduler.make() 进入源码

public enum SharingScheduler {
    /// Default scheduler used in SharedSequence based traits.
    public private(set) static var make: () -> SchedulerType = { MainScheduler() }

    ......
}

从上面我们可以看到,Driver订阅信号的操作是在 MainScheduler 主线程。


结语:

RXSwift在项目中经常使用的类和方法已经大体介绍完了,如果感觉有用的话,点个Star吧!

猜你喜欢

转载自blog.csdn.net/weixin_42433480/article/details/129778942