RxFeedback 粗略源码分析

在RxSwift文档中,介绍了它的常用框架,现在就来看看其中之一RxFeedback,至于用法与优势可以查看文档中的介绍,现在通过源码来分析其中状态与事件如何联系起来的。在这个框架中主要成分有State、Event、Mutation,然后通过reduce(加工程序)和feedback(连接管理)把它们串联起来形成一个反馈系统。一个反馈就是:事件(Event)发出指令(Mutation),通过加工程序处理,更新状态(State),反馈到状态的订阅者。事件序列的创建和状态的订阅在feedback中完成。为了方便,在下文中,面对于反馈系统进行的输入反馈处理的载体称为客户环境

先来看看系统创建的主要源码:

 public static func system<State, Mutation>(
            initialState: State,
            reduce: @escaping (State, Mutation) -> State,
            scheduler: ImmediateSchedulerType,
            scheduledFeedback: [Feedback<State, Mutation>]
        ) -> Observable<State> {
        return Observable<State>.deferred {
            let replaySubject = ReplaySubject<State>.create(bufferSize: 1)

            let asyncScheduler = scheduler.async
            
            let mutations: Observable<Mutation> = Observable.merge(scheduledFeedback.map { feedback in
                let state = ObservableSchedulerContext(source: replaySubject.asObservable(), scheduler: asyncScheduler)
                return feedback(state)
            })
            // This is protection from accidental ignoring of scheduler so
            // reentracy errors can be avoided
            .observeOn(CurrentThreadScheduler.instance)

            return mutations.scan(initialState, accumulator: reduce)
                .do(onNext: { output in
                    replaySubject.onNext(output)
                }, onSubscribed: {
                    replaySubject.onNext(initialState)
                })
                .subscribeOn(scheduler)
                .startWith(initialState)
                .observeOn(scheduler)
        }
    }

第一步,是创建State的可观察序列replaySubject,其后通过ObservableSchedulerContext进行包装为state。注意statesubscribe方法中是直接调用的自身source的订阅方法return self.source.subscribe(observer),所以后续订阅state对象就是直接订阅replaySubject

第二步, feedback(state),在这个方法中处理了两件事情,一个是通过这个方法传入state让其能够在客户环境中被订阅,二是在客户环境中创建包含指定指令事件序列mutations(在下文中用EM指代)。

第三步,建立EMreplaySubjectreduce的联系,mutations.scan(initialState, accumulator: reduce),首先使用提供的reduce加工处理状态State,然后在EM的Observer中将处理后的State传递到replaySubject在客户环境的订阅者replaySubject.onNext(output)

再来看看feedback的原型Feedback,它的定义为public typealias Feedback<State, Mutation> = (ObservableSchedulerContext<State>) -> Observable<Mutation>,输入为ObservableSchedulerContext<State>replaySubject),输出为Observable<Mutation>EM)的函数。在Feedbacks.swift文件中提供了多种创建方法,现在参考文档中Counter示例,分析其中的一种。示例主体代码为:

        typealias State = Int

        enum Mutation {
            case increment
            case decrement
        }

        Observable.system(
            initialState: 0,
            reduce: { (state, mutation) -> State in
                switch mutation {
                case .increment:
                    return state + 1
                case .decrement:
                    return state - 1
                }
        },
            scheduler: MainScheduler.instance,
            scheduledFeedback:
                // UI is user feedback
                bind(self) { me, state -> Bindings<Mutation> in
                    let subscriptions = [
                        state.map(String.init).bind(to: me.label!.rx.text)
                    ]

                    let mutations = [
                        me.plus!.rx.tap.map { Mutation.increment },
                        me.minus!.rx.tap.map { Mutation.decrement }
                    ]

                    return Bindings(subscriptions: subscriptions,
                                    mutations: mutations)
                }
            )
            .subscribe()
            .disposed(by: disposeBag)

其中bind(self) {...调用的方法为:

public func bind<State, Mutation, WeakOwner>(_ owner: WeakOwner, _ bindings: @escaping (WeakOwner, ObservableSchedulerContext<State>) -> (Bindings<Mutation>))
    -> (ObservableSchedulerContext<State>) -> Observable<Mutation> where WeakOwner: AnyObject {
        return bind(bindingsStrongify(owner, bindings))
}

客户环境传入的闭包为bindings,是描述为 (WeakOwner, ObservableSchedulerContext<State>) -> (Bindings<Mutation>)的闭包,后面统称为闭包A
然后通过下面的代码bindingsStrongify(owner, bindings)处理返回一个(O) -> (Bindings<Mutation>)的闭包,后面统称为闭包B,这里的OObservableSchedulerContext<State>,相比于闭包A去除了owner。

private func bindingsStrongify<Mutation, O, WeakOwner>(_ owner: WeakOwner, _ bindings: @escaping (WeakOwner, O) -> (Bindings<Mutation>))
    -> (O) -> (Bindings<Mutation>) where WeakOwner: AnyObject {
    return { [weak owner] state -> Bindings<Mutation> in
        guard let strongOwner = owner else {
            return Bindings(subscriptions: [], mutations: [Observable<Mutation>]())
        }
        return bindings(strongOwner, state)
    }
}

最后通过下面代码,返回一个闭包(ObservableSchedulerContext<State>) -> Observable<Mutation>则是符合Feedback的闭包,称为闭包C,作为输入传入到Observable.system方法。

public func bind<State, Mutation>(_ bindings: @escaping (ObservableSchedulerContext<State>) -> (Bindings<Mutation>)) -> (ObservableSchedulerContext<State>) -> Observable<Mutation> {
    return { (state: ObservableSchedulerContext<State>) -> Observable<Mutation> in
        return Observable<Mutation>.using({ () -> Bindings<Mutation> in
            return bindings(state)
        }, observableFactory: { (bindings: Bindings<Mutation>) -> Observable<Mutation> in
            return Observable<Mutation>
                    .merge(bindings.mutations)
                    .concat(Observable.never())
                    .enqueue(state.scheduler)
        })
    }
}

调用过程,在系统生成的步骤二中,调用feedback(state),将前面的replaySubject传入到闭包C, 通过闭包C 中的bindings(state)replaySubject传入到闭包B闭包B将指定的的ownerreplaySubject通过bindings(strongOwner, state)传入到客户环境闭包A中。这就是State关联序列的传入到客户环境的过程。在闭包A中客户环境订阅State序列replaySubject接收反馈。再来分析EM的创建和订阅,通过客户环境闭包A的执行,创建了相关于Mutation的事件序列组,作为返回对象Bindings的成员,在闭包C中的Observable<Mutation>.using方法,将resourceFactory创建的Bindings对象(调用bindings(state)生成的)作为输入传入到observableFactory,observableFactory进行Mutation关联的事件序列组的merge处理Observable<Mutation>.merge(bindings.mutations)生成EM1EM1通过using处理生成EM2EM2返回到系统生成中,通过再次与其他feedback(state)生成的EMn进行merge处理生成了最终的EM,然后系统进入步骤三完成最后的关联。

另一些小积累:
Observable<Mutation>.using 的 resourceFactory输出作为observableFactory的输入在源码Using.swift

           let resource = try _parent._resourceFactory()
           disposable = resource
           let source = try _parent._observableFactory(resource)

,另外source.subscribe(self)将自己作为source的订阅者,分发source的事件。所以,using的订阅者获取的事件都是来自source。对于using中source序列完成后,摧毁resource可通过下面的代码可见:

···            
  return Disposables.create(
                source.subscribe(self),
                disposable
            )
···

  case .completed:
            forwardOn(.completed)
            dispose()

,对于在RxFeedback中Binding对象中replaySubject订阅返回的对象的摧毁,是由于返回的Binding对象是支持Disposable协议的,在它的实现方法中,对订阅返回对象进行摧毁,如下

 public func dispose() {
        for subscription in subscriptions {
            subscription.dispose()
        }
    }

,整个调用过程为:using中的resource发出完成命令,调用了dispose()进行释放,释放对象的disposable为Binding对象(disposable = resource),Binding对象调用dispose()方法,逐个调用订阅返回对象的摧毁方法。则也可以看出using如何摧毁resource的。(这里的resource为Binding对象)

Scan新的State状态的保存过程,源码在Scan.swift,注意_accumulate前面的&

     try _parent._accumulator(&_accumulate, element)
     forwardOn(.next(_accumulate))
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,590评论 18 139
  • Introduction: Creating and Subscribing to Observables: Th...
    loongod阅读 732评论 0 0
  • 本篇文章介主要绍RxJava中操作符是以函数作为基本单位,与响应式编程作为结合使用的,对什么是操作、操作符都有哪些...
    嘎啦果安卓兽阅读 2,830评论 0 10
  • 最近在学习RxSwift相关的内容,在这里记录一些基本的知识点,以便今后查阅。 Observable 在RxSwi...
    L_Zephyr阅读 1,739评论 1 4
  • 爱情中我们应该是学会一些小技巧,那样才会让我们得到自己喜欢的人。那么假如你喜欢一个人的时候你怕自己过于的主动,让对...
    秋舞阅读 226评论 0 0