新手讀的懂的RxSwift源碼解析(三)-- FlatMap

今天绰播,筆者與大家分析一下骄噪,RxSwift中另一個(gè)特別重要,甚至是最重要之一的操作符:flatMap幅垮。

如果大家沒有讀過本系列的第一篇腰池,建議先閱讀一下再來看本篇內(nèi)容。

在RxSwift進(jìn)行開發(fā)的過程中忙芒,有一個(gè)非常常見的場景:點(diǎn)擊某個(gè)按鈕示弓,然后發(fā)送請求,之后處理請求結(jié)果呵萨。代碼大致如下:

myBtn
  .rx
  .tap
  .flatMap { _ in
    performRequest()
  }
  .map {
    processResponse($0)
  }
  .subScribe(
    onNext: { ... }
  )
  .disposed(by: bag)

在這個(gè)場景中奏属,所要使用的最核心的一個(gè)操作符便是flatMap,它的作用是將myBtn產(chǎn)生的點(diǎn)擊事件轉(zhuǎn)換成另一個(gè)事件序列潮峦,并將該事件序列產(chǎn)生的事件向下傳遞囱皿。代碼如下:

extension ObservableType {
    public func flatMap<Source: ObservableConvertibleType>(_ selector: @escaping (Element) throws -> Source)
        -> Observable<Source.Element> {
            return FlatMap(source: self.asObservable(), selector: selector)
    }
}

該方法接收一個(gè)(Element) throws -> Source的closure勇婴,Element就是自身的元素類型,而返回值類型Source是一個(gè)泛型參數(shù)嘱腥,并且是遵循ObservableConvertibleType的耕渴。也就是說,這個(gè)closuer會(huì)把上游產(chǎn)生的事件轉(zhuǎn)換成一個(gè)事件序列齿兔。方法實(shí)現(xiàn)中返回了一個(gè)FlatMap類型的對象橱脸,這個(gè)對象持有了self.asObservable(),以及傳入的closure.
接下來分苇,咱們看一下這個(gè)FlatMap的源碼:

final private class FlatMap<SourceElement, SourceSequence: ObservableConvertibleType>: Producer<SourceSequence.Element> {
    typealias Selector = (SourceElement) throws -> SourceSequence

    private let source: Observable<SourceElement>
    
    private let selector: Selector

    init(source: Observable<SourceElement>, selector: @escaping Selector) {
        self.source = source
        self.selector = selector
    }
    
    override func run<Observer: ObserverType>(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == SourceSequence.Element {
        let sink = FlatMapSink(selector: self.selector, observer: observer, cancel: cancel)
        let subscription = sink.run(self.source)
        return (sink: sink, subscription: subscription)
    }
}

FlatMap是一個(gè)Producer的子類添诉,在它的run方法中通過self.selector和observer生成了一個(gè)FlatMapSink類的對象。這個(gè)selector就是在調(diào)用flatMap時(shí)傳入的closure, observer其實(shí)就是在subscribe方法中創(chuàng)建的observer(參見系列第一篇)医寿。然后調(diào)用了FlatMapSink的run方法栏赴。但是我們再FlatMapSink里并沒有看到run方法,因?yàn)镕latMapSink是MergeSink的子類靖秩,它的很多邏輯都在它的父類MergeSink里面:

private class MergeSink<SourceElement, SourceSequence: ObservableConvertibleType, Observer: ObserverType>
    : Sink<Observer>
    , ObserverType where Observer.Element == SourceSequence.Element {
    typealias ResultType = Observer.Element
    typealias Element = SourceElement

    let lock = RecursiveLock()

    var subscribeNext: Bool {
        true
    }

    // state
    let group = CompositeDisposable()
    let sourceSubscription = SingleAssignmentDisposable()

    var activeCount = 0
    var stopped = false

    ......    
    
  func run(_ source: Observable<SourceElement>) -> Disposable {
        _ = self.group.insert(self.sourceSubscription)

        let subscription = source.subscribe(self)
        self.sourceSubscription.setDisposable(subscription)
        
        return self.group
    }
}

在源碼的最后面须眷,我們看到了run方法,只有四行盆偿,我們需要關(guān)注的是第二行:

let subscription = source.subscribe(self)

也就是說讓self訂閱了傳入的Observable柒爸,而這個(gè)傳入的Observable就是FlatMap.source,也就是上游序列事扭。這樣一來大致流程就清楚了:
通過Obsevable.flatMap,讓FlatMap類持有了上游Obsevable乐横,并通過FlatMapSink訂閱了這個(gè)上游Obsevable求橄。接下來上游Obsevable的所有事件都會(huì)被傳遞給FlatMapSink的on方法(原理參見系列第一篇)。
接下來的重點(diǎn)葡公,我們需要看一下這個(gè)FlatMapSink的on方法做了什么罐农,還是在父類MergeSink里面。

    func on(_ event: Event<SourceElement>) {
        switch event {
        case .next(let element):
            if let value = self.nextElementArrived(element: element) {
                self.subscribeInner(value.asObservable())
            }
        case .error(let error):
            self.lock.performLocked {
                self.forwardOn(.error(error))
                self.dispose()
            }
        case .completed:
            self.lock.performLocked {
                self.stopped = true
                self.sourceSubscription.dispose()
                self.checkCompleted()
            }
        }
    }

error事件會(huì)直接調(diào)用forwardOn催什,將事件傳遞給下游observer涵亏,completed事件則是調(diào)用checkCompleted方法,在checkCompleted方法中判斷是否需要將completed事件傳遞下去蒲凶。這是因?yàn)镸ergeSink可能會(huì)訂閱多個(gè)事件序列气筋,對這些事件進(jìn)行一系列變換,直到所有的事件序列都complete之后才會(huì)傳遞旋圆,但這個(gè)不是今天的重點(diǎn)宠默,所以只要知道個(gè)大概就可以了,具體的咱們在后續(xù)的文章中再介紹灵巧。
接下來看next事件搀矫,在next事件中會(huì)調(diào)用self.nextElementArrived(element: element):

    final private func nextElementArrived(element: SourceElement) -> SourceSequence? {
        self.lock.performLocked {
            if !self.subscribeNext {
                return nil
            }

            do {
                let value = try self.performMap(element)
                self.activeCount += 1
                return value
            }
            catch let e {
                self.forwardOn(.error(e))
                self.dispose()
                return nil
            }
        }
    }

這個(gè)方法會(huì)首先加鎖抹沪,然后執(zhí)行self.performMap(element)方法,這個(gè)方法在子類(FlatMapSink)中有實(shí)現(xiàn),其實(shí)就是調(diào)用self.selector:

    override func performMap(_ element: SourceElement) throws -> SourceSequence {
        try self.selector(element)
    }

而這個(gè)selector其實(shí)就是在最初調(diào)用Observable.flatMap時(shí)傳入的closure瓤球,所以到這里融欧,myBtn的點(diǎn)擊事件就會(huì)被轉(zhuǎn)換成了一個(gè)事件序列。獲取這個(gè)返回值之后卦羡,on方法會(huì)調(diào)用subscribeInner方法:

    func subscribeInner(_ source: Observable<Observer.Element>) {
        let iterDisposable = SingleAssignmentDisposable()
        if let disposeKey = self.group.insert(iterDisposable) {
            let iter = MergeSinkIter(parent: self, disposeKey: disposeKey)
            let subscription = source.subscribe(iter)
            iterDisposable.setDisposable(subscription)
        }
    }

在這里蹬癌,主要是創(chuàng)建了一個(gè)MergeSinkIter對象,并讓這個(gè)MergeSinkIter對象訂閱了傳入的Observable虹茶。根據(jù)MergeSinkIter的on方法:

    func on(_ event: Event<Element>) {
        self.parent.lock.performLocked {
            switch event {
            case .next(let value):
                self.parent.forwardOn(.next(value))
            case .error(let error):
                self.parent.forwardOn(.error(error))
                self.parent.dispose()
            case .completed:
                self.parent.group.remove(for: self.disposeKey)
                self.parent.activeCount -= 1
                self.parent.checkCompleted()
            }
        }
    }

我們可以看見它實(shí)際就是將事件傳遞給了parent逝薪,也就是MergeSink。但是注意蝴罪,這里是直接調(diào)用的forwardOn方法董济,所以會(huì)直接把事件傳遞給下游observer,而不會(huì)觸發(fā)performMap方法(當(dāng)然也沒法直接調(diào)用on方法要门,因?yàn)槎呓邮盏膮?shù)泛型通常是不一樣的)虏肾。

這樣一來,flatMap的完整流程就結(jié)束了欢搜。

總結(jié)一下:
1.通過flatMap創(chuàng)建了一個(gè)FlatMap對象封豪,并通過FlatMapSink訂閱了上游Observable
2.當(dāng)上游Observable產(chǎn)生一個(gè)事件時(shí)FlatMapSink通過調(diào)用FlatMap.performMap方法觸發(fā)傳入的closure,產(chǎn)生了新的事件序列炒瘟。
3.通過MergeSinkIter將新的事件序列的事件傳遞個(gè)下游的observer吹埠,從而實(shí)現(xiàn)了Event->Observable->Event的傳遞。

它的流程和RxSwift中大部分操作符的核心邏輯差不多疮装,都是通過持有上游observable缘琅,創(chuàng)建一個(gè)Sink的子類去訂閱這個(gè)上游observable,然后將事件傳遞個(gè)下游的observer廓推。只不過因?yàn)檫@里產(chǎn)生的是事件序列刷袍,所以又多出了一個(gè)MergeSinkIter來處理這個(gè)事件序列的事件并進(jìn)行傳遞。
當(dāng)然樊展,今天只是介紹了FlatMap的具體流程呻纹,MergeSink的相關(guān)細(xì)節(jié)我們都忽略了,這個(gè)我們后面再說专缠。

碼字不易雷酪,若有錯(cuò)漏,歡迎指正藤肢。若有助益太闺,煩請點(diǎn)贊。^ _ ^

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嘁圈,一起剝皮案震驚了整個(gè)濱河市省骂,隨后出現(xiàn)的幾起案子蟀淮,更是在濱河造成了極大的恐慌,老刑警劉巖钞澳,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怠惶,死亡現(xiàn)場離奇詭異,居然都是意外死亡轧粟,警方通過查閱死者的電腦和手機(jī)策治,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兰吟,“玉大人通惫,你說我怎么就攤上這事』彀” “怎么了履腋?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長惭嚣。 經(jīng)常有香客問我遵湖,道長,這世上最難降的妖魔是什么晚吞? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任延旧,我火速辦了婚禮,結(jié)果婚禮上槽地,老公的妹妹穿的比我還像新娘迁沫。我一直安慰自己,他們只是感情好闷盔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布弯洗。 她就那樣靜靜地躺著,像睡著了一般逢勾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上藐吮,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天溺拱,我揣著相機(jī)與錄音,去河邊找鬼谣辞。 笑死迫摔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泥从。 我是一名探鬼主播句占,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼躯嫉!你這毒婦竟也來了纱烘?” 一聲冷哼從身側(cè)響起杨拐,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎擂啥,沒想到半個(gè)月后哄陶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哺壶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年屋吨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片山宾。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡至扰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出资锰,到底是詐尸還是另有隱情敢课,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布台妆,位于F島的核電站翎猛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏接剩。R本人自食惡果不足惜切厘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望懊缺。 院中可真熱鬧疫稿,春花似錦、人聲如沸鹃两。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俊扳。三九已至途蒋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間馋记,已是汗流浹背号坡。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梯醒,地道東北人宽堆。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像茸习,于是被迫代替她去往敵國和親畜隶。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容