iOS探索RxSwift高阶函数

375 阅读7分钟

一、Driver核心逻辑

textField赋值问题,订阅2次原因。

_ = textFiled.rx.text.subscribe{ text in
            print("来了\(text)")
}
//1、创建序列
_ = Observable<String>.create({ observer in
    //3、发送信号
    observer.onNext("AAAA")
    observer.onCompleted()
    return Disposables.create()
    //2、订阅序列
}).subscribe {
    print("订阅到:\($0)")
}

image.png 问题:第一次赋值不打印:来了next(老弟) ,第二次点击才打印:来了next(老弟)

怎么解决呢?

    //按钮赋值
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("老弟")
        textFiled.text = "老弟"
        textFiled.sendActions(for: .allEditingEvents) //加所以事件
    }

image.png

//textField UI响应 ControlTarget eventHandler callBack(闭包/observe.on)
_ = textFiled.rx.text.skip(1).subscribe { text in
    print("来了\(text)")
    //UI处理
}

//1、创建序列
_ = Observable<String>.create({ observer in
    //3、发送信号
    observer.onNext("AAAA")
    observer.onCompleted()
    return Disposables.create()
    //2、订阅序列
}).subscribe {
    switch $0 {
    case .next(let value):
        print(value)
    case .error(let error):
        print(error)
    case .completed:
        print("completed")
    }
}

image.png 解决订阅2次原因

1、Driver用法

一个小例子:

let result = inputTF.rx.text.skip(1)
            .flatMap{ input in
                return self.dealWithData(inputText: input!)
            }//序列中的序列
            
_ = result.subscribe({ element in
            print("订阅到了\(element)")
        })

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

看运行结果: image.png 看到问题了:

问题1:发送之前的子线程在不停的变化,这个序列每次会创建一个新的子线程。

问题2:发现由于多次订阅,会请求多次网络。

问题3:错误事件的处理

怎么解决呢?我们这里用到RxSwift的高阶函数shareReplay image.png

let result = inputTF.rx.text.skip(1)
            .flatMap{ input in
                return self.dealWithData(inputText: input!)
            } //序列中的序列
            .share(replay: 1, scope: .whileConnected)//共享obervable

image.png

这样就解决问题2了。 怎么解决回到主线程刷新UI呢?

return self.dealWithData(inputText: input!).observe(on: MainScheduler())

image.png 这样就解决问题1了。怎么解决error事件处理呢?

self.dealWithData(inputText: input!).observe(on: MainScheduler())
                    .catchAndReturn("监测到错误事件")//error起死回生

image.png 现在我们解决了上面三个问题了。可以是说好的Driver没用到啊!!!偏题了,作文0分~

下面用Driver来解决上述问题:

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

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

这些都是驱动 UI 的序列所具有的特征。(摘:RxSwift中文文档)

注意:只要满足上面三点就可以使用Driver

let result = inputTF.rx.text
            .asDriver()
            .flatMap {
                return self.dealWithData(inputText: $0!)
                    .asDriver(onErrorJustReturn: "监测到错误事件")
            }

        _ = result.map{ "长度:\(($0 as! String).count)" }
        .drive(self.textLabel.rx.text) //订阅事件text

        _ = result.map { "\($0)" }
        .drive(self.btn.rx.title())

image.png 看到结果,直接把订阅内容绑定到UI上显示了。

2、Driver逻辑

Driver 面向 ControlProperty 进行封装。

public func asDriver() -> Driver<Element> {
        return self.asDriver { _ -> Driver<Element> in
            #if DEBUG
                rxFatalError("Somehow driver received error from a source that shouldn't fail.")
            #else
                return Driver.empty()
            #endif
        }
}

public typealias Driver<Element> = SharedSequence<DriverSharingStrategy, Element>

image.png

public protocol SharingStrategyProtocol {
    /**
     Scheduled on which all sequence events will be delivered.
    */
    static var scheduler: SchedulerType { get }
    /**
     Computation resources sharing strategy for multiple sequence observers.
     E.g. One can choose `share(replay:scope:)`
     as sequence event sharing strategies, but also do something more exotic, like
     implementing promises or lazy loading chains.
    */
    static func share<Element>(_ source: Observable<Element>) -> Observable<Element>
}

share是一个协议方法。 image.png SharingStrategy是一个关联类型 image.png 进入源码查看一下:

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)
    }
}

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

image.png .catch的时候onErrorRecover执行外部传进来的@escaping逃逸闭包最后返回Driver(source)->SharedSequence<DriverSharingStrategy, Element>

进入断点调试查看: image.png 当前的self是 RxSwift.Observable<Any> 类型 image.png 所以Driver逻辑核心还是使用RxSwift的高阶函数shareReplay共享观察者实现封装的,对代码的理解和封装思维又增加了。

二、高级函数

高阶函数用法:

        // *** startWith : 在开始从可观察源发出元素之前,发出指定的元素序列
        print("***** startWith *****")
        Observable.of("1", "2", "3", "4")
            .startWith("A")
            .startWith("B")
            .startWith("C", "a", "b")
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
        //效果: CabBA1234
        // **** merge : 将源可观察序列中的元素组合成一个新的可观察序列,并将像每个源可观察序列发出元素一样发出每个元素
        print("***** merge *****")
        let subject1 = PublishSubject<String>()
        let subject2 = PublishSubject<String>()
        // merge subject1和subject2
        Observable.of(subject1, subject2)
            .merge()
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)

        subject1.onNext("Z")
        subject1.onNext("i")
        subject2.onNext("o")
        subject2.onNext("n")
        subject1.onNext("s")
        // Zions - 任何一个响应都会勾起新序列响应
        
        //  *** zip: 将多达8个源可观测序列组合成一个新的可观测序列,并将从组合的可观测序列中发射出对应索引处每个源可观测序列的元素

        print("***** zip *****")
        let stringSubject = PublishSubject<String>()
        let intSubject = PublishSubject<Int>()
        Observable.zip(stringSubject, intSubject) { stringElement, intElement in
            "\(stringElement) \(intElement)"
        }
        .subscribe(onNext: { print($0) })
        .disposed(by: disposeBag)
        
        stringSubject.onNext("A")
        stringSubject.onNext("B") // 到这里存储了 AB 但是不会响应,除非另一个响应
        intSubject.onNext(1) // 勾出一个
        intSubject.onNext(2) // 勾出另一个
        stringSubject.onNext("C") // 存一个
        intSubject.onNext(3) // 勾出一个
        // 结论: 只有两个序列同时有值的时候才会响应,否则存值

        //  *****  combineLatest:将8源可观测序列组合成一个新的观测序列,并将开始发出联合观测序列的每个源的最新元素可观测序列一旦所有排放源序列至少有一个元素,并且当源可观测序列发出的任何一个新元素
        print("***** combineLatest *****")
        let stringSub = PublishSubject<String>()
        let intSub = PublishSubject<Int>()
        Observable.combineLatest(stringSub, intSub) { strElement, intElement in
            "\(strElement) \(intElement)"
        }
        .subscribe(onNext: { print($0) })
        .disposed(by: disposeBag)

        stringSub.onNext("L") // 存一个 L
        stringSub.onNext("G") // 存了一个覆盖 - 和zip不一样
        intSub.onNext(1)      // 发现strOB也有G 响应 G 1
        intSub.onNext(2)      // 覆盖1 -> 2 发现strOB 有值G 响应 G 2
        stringSub.onNext("YYYYY")G -> YYYYY 发现intOB 有值2 响应 YYYYY 2
        // combineLatest 比较zip 会覆盖
        // 应用非常频繁: 比如账户和密码同时满足->才能登录,不关心账户密码怎么变化,只要查看最后有值就可以 loginEnable

        // switchLatest : 将可观察序列发出的元素转换为可观察序列,并从最近的内部可观察序列发出元素
        print("***** switchLatest *****")
        let switchLatestSub1 = BehaviorSubject(value: "L")
        let switchLatestSub2 = BehaviorSubject(value: "1")
        let switchLatestSub  = BehaviorSubject(value: switchLatestSub1)// 选择了 switchLatestSub1 就不会监听 switchLatestSub2
        switchLatestSub.asObservable()
            .switchLatest()
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
        switchLatestSub1.onNext("G")
        switchLatestSub1.onNext("_")
        switchLatestSub2.onNext("2")
        switchLatestSub2.onNext("3") // 2-3都不会监听,但是默认保存由 2覆盖1 3覆盖2
        switchLatestSub.onNext(switchLatestSub2) // 切换到 switchLatestSub2
        switchLatestSub1.onNext("*")
        switchLatestSub1.onNext("Cccccc")同上面 下面如果再次切换到 switchLatestSub1 会打印出 Cccccc
        switchLatestSub2.onNext("4")
    }
    
    
        // ***** map: 转换闭包应用于可观察序列发出的元素,并返回转换后的元素的新可观察序列。
        print("***** map *****")
        let ob = Observable.of(1, 2, 3, 4)
        ob.map { number -> Int in
            return number + 2
        }
        .subscribe { print($0) } //(onNext: { print($0) })
        .disposed(by: disposeBag)
        
        // *** flatMap and flatMapLatest
        // 将可观测序列发射的元素转换为可观测序列,并将两个可观测序列的发射合并为一个可观测序列。
        // 这也很有用,例如,当你有一个可观察的序列,它本身发出可观察的序列,你想能够对任何一个可观察序列的新发射做出反应(序列中序列:比如网络序列中还有模型序列)
        // flatMap和flatMapLatest的区别是,flatMapLatest只会从最近的内部可观测序列发射元素
        print("***** flatMap *****")
        let boy  = LGPlayer(score: 100)
        let girl = LGPlayer(score: 90)
        let player = BehaviorSubject(value: boy)
        player.asObservable()
            .flatMap { $0.score.asObservable() } // 本身score就是序列 模型就是序列中的序列
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
        boy.score.onNext(60)
        player.onNext(girl)
        boy.score.onNext(50)
        boy.score.onNext(40)//  如果切换到 flatMapLatest 就不会打印
        girl.score.onNext(10)
        girl.score.onNext(0)
        // flatMapLatest实际上是map和switchLatest操作符的组合。
        // ** scan: 从初始就带有一个默认值开始,然后对可观察序列发出的每个元素应用累加器闭包,并以单个元素可观察序列的形式返回每个中间结果
        print("***** scan *****")
        Observable.of(10, 100, 1000)
            .scan(2) { aggregateValue, newValue in
                aggregateValue + newValue // 10 + 2 , 100 + 10 + 2 , 1000 + 100 + 2
            }
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)

1、map

如上面例子:

    let ob = Observable.of(1, 2, 3, 4)
    ob.map { number -> Int in
            return number + 2
        }

image.png 进入代码,我们看到 map是ObservableType 协议的扩展方法,主要调用了Map (observable继承了ObservableType) image.png 根据我们之前的思路,AnonymousObservableSink 管道生产者->MapSink观察者->保存序列源(map闭包)

final private class MapSink<SourceType, Observer: ObserverType>: Sink<Observer>, ObserverType {

    typealias Transform = (SourceType) throws -> ResultType  
/****************省略****************/
    private let transform: Transform
        case .next(let element):
            do {
                let mappedElement = try self.transform(element) //在收到next消息回调前调用transform ->map
                self.forwardOn(.next(mappedElement))
            }
}         

最终.next消息接受分流前进行Transform(外部闭包) ->处理在到onCore中回调函数。

self.source = Observable.of(1, 2, 3, 4) 然后self.source.subscribe(sink) 源序列进行订阅。如下图: image.png 我们通过调试验证了,结果在 mapsink的on方法中进行Map闭包处理+2在执行回调函数中mappedElement=3 打印next(3) image.png

2、combineLatest & multicast

combineLatest

先看例子:

        let stringSub = PublishSubject<String>()
        let intSub = PublishSubject<Int>()
        Observable.combineLatest(stringSub, intSub) { strElement, intElement in
            "\(strElement) \(intElement)"
        }
        .subscribe(onNext: { print($0) })
        .disposed(by: disposeBag)

进入代码查看: image.png 在进入CombineLatest2: image.png 看到这里好像和MapSink的代码差不多,继续跟踪到run

func run() -> Disposable {
        let subscription1 = SingleAssignmentDisposable()
        let subscription2 = SingleAssignmentDisposable()
        let observer1 = CombineLatestObserver(lock: self.lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self.latestElement1 = e }, this: subscription1)
        let observer2 = CombineLatestObserver(lock: self.lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self.latestElement2 = e }, this: subscription2)
         subscription1.setDisposable(self.parent.source1.subscribe(observer1))
         subscription2.setDisposable(self.parent.source2.subscribe(observer2))

        return Disposables.create([
                subscription1,
                subscription2
        ])
    }

看代码初步了解:SingleAssignmentDisposable 创建了两个单独的销毁,然后把 self.parent.source1.subscribe(observer1)的dispose加入到销毁者中。然后我们在进入 CombineLatestObserver 查看源代码:

final class CombineLatestObserver<Element>
    : ObserverType
    , LockOwnerType
    , SynchronizedOnType {
    typealias ValueSetter = (Element) -> Void
    private let parent: CombineLatestProtocol
    let lock: RecursiveLock
    private let index: Int
    private let this: Disposable
    private let setLatestValue: ValueSetter

    init(lock: RecursiveLock, parent: CombineLatestProtocol, index: Int, setLatestValue: @escaping ValueSetter, this: Disposable) {
        self.lock = lock //递归锁
        self.parent = parent //序列
        self.index = index //索引
        self.this = this
        self.setLatestValue = setLatestValue //就是闭包 Element 取了别名
    }
    
    func on(_ event: Event<Element>) {
        self.synchronizedOn(event)
    }
    //大概意思带锁的 on方法 
    func synchronized_on(_ event: Event<Element>) {
        switch event {
        case .next(let value):
            self.setLatestValue(value) //执行闭包
            self.parent.next(self.index) //传递给父类的.next处理
        case .error(let error):
            self.this.dispose()
            self.parent.fail(error)
        case .completed:
            self.this.dispose()
            self.parent.done(self.index)
        }
    }
}

发现这里并没有处理的过程,组合Subject也没出现。发现CombineLatestSink2_继承CombineLatestSink我们在断点调试跟踪下: image.png

multicast

上例子:

        let subject = PublishSubject<Any>()
        subject.subscribe{ print("00:\($0)") }
            .disposed(by: disposeBag)
        let netOB = Observable<Any>.create { observer -> Disposable in
            sleep(2)// 模拟网络延迟
            print("我开始请求网络了")
            observer.onNext("请求到的网络数据")
            observer.onNext("请求到的本地")
            observer.onCompleted()
            return Disposables.create {
                print("销毁回调了")
            }
        }.publish()
        netOB.subscribe(onNext: { anything in
            print("订阅1:", anything)
        })
        .disposed(by: disposeBag)

        netOB.subscribe(onNext: { anything in
            print("订阅2:", anything)
        })

        .disposed(by: disposeBag)
        _ = netOB.connect()

运行效果: image.png 我们发现订阅两次,网络请求 并没有执行两次,可订阅回调两个都能收到。是我们希望看到的情况,这是怎么做到的,RxSwift底层做了什么?

看源码: image.png 继续跟踪:

final private class ConnectableObservableAdapter<Subject: SubjectType>
    : ConnectableObservable<Subject.Element> {
    typealias ConnectionType = Connection<Subject>
    private let source: Observable<Subject.Observer.Element>
    private let makeSubject: () -> Subject
    fileprivate let lock = RecursiveLock()
    fileprivate var subject: Subject?
    // state
    fileprivate var connection: ConnectionType?
    //省略初始化代码
    override func connect() -> Disposable {
        return self.lock.performLocked { //锁 保证数据安全
            if let connection = self.connection {
                return connection
            }
            let singleAssignmentDisposable = SingleAssignmentDisposable()
            let connection = Connection(parent: self, subjectObserver: self.lazySubject.asObserver(), lock: self.lock, subscription: singleAssignmentDisposable) //获取lazySubject 的
            self.connection = connection
            let subscription = self.source.subscribe(connection)//源序列-订阅,传入connection
            singleAssignmentDisposable.setDisposable(subscription)
            return connection
        }
    }

    //懒加载只创建一次
    private var lazySubject: Subject {
        if let subject = self.subject {
            return subject
        }

        let subject = self.makeSubject()
        self.subject = subject
        return subject
    }
    //重写订阅方法
    override func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Subject.Element {
        self.lazySubject.subscribe(observer) //订阅lazySubject序列
    }
}

ConnectableObservableAdapter 重写了subscribe的实现。调用了lazySubject:Subject的订阅, 然后我们看lazySubject我们先把它当源序列,只初始化一次赋值一次。

在跟踪到Connectionimage.pngConnection的时候on出发到同一个观察者上在lazySubject内部统一调度。 image.png 我们看到高阶函数shareReplay的底层也是用multicast实现的共享observable序列。

总结

  1. Driver核心逻辑:只要满足(1.不会产生 error 事件,2.一定在 MainScheduler 监听,3.共享附加作用)就可以使用Driver。核心逻辑是源序列.shared调用multicast来实现共享序列的。
  2. map函数AnonymousObservableSink 管道生产者->MapSink观察者->保存序列源(map闭包)->on函数前处理(闭包)->结果forwardOn到回调函数。
  3. combineLatest:CombineLatest2保存了两组源序列->创建CombineLatestSink2_继承CombineLatestSink->在sink中next函数遍历处理组源序列,等一组源序列处理完成了->forwardOn到回调函数。
  4. multicast:publish->multicast->ConnectableObservableAdapter->重写了subscribe(懒加载lazySubject)只执行一次的,我们称为调度者->Connection(在线程安全的环境下)->on回调。最终实现shareReplay共享序列。