走進 RxSwift 之觀察者模式

RxSwift簡介和吐槽

RxSwiftReactiveX 系列的 Swift 版本泣港,如果你之前用過 ReactiveCocoa(RAC) 的話,想必對 Functional Reactive Programming(FRP曹洽,函數(shù)響應式編程)這個概念不會陌生致燥,是的瓤檐,RxSwift 同樣是一個 FRP 框架赂韵。值得一提的是,RAC 的README 里有這么幾句話:

ReactiveCocoa was originally inspired, and therefore heavily influenced, by Microsoft’s Reactive Extensions (Rx) library.

Although ReactiveCocoa was started as an Objective-C framework, as of version 3.0, all major feature development is concentrated on the Swift API.

第一句是說挠蛉,RAC 是受微軟的 Reactive Extensions 啟發(fā)的祭示,所以也受了 Reactive Extensions 很大的影響(當然,之后它羅列了 RAC 跟 Rx 的一些差別谴古,并且安利 iOS 開發(fā)者來使用 RAC)质涛。第二句是說,雖然 RAC 是作為一個 OC 框架出道的掰担,但是從3.0版本開始汇陆,所有主要特性的開發(fā)都已經(jīng)以 Swift API 為重心了。也就是說带饱,今后不管是不是下雨天毡代,RAC 都跟 Swift 更配哦阅羹。

如果你是一個使用 Swift 的 iOS 開發(fā)者,并且對無處不在的 OOP 感到了厭倦教寂,想要打開新世界的大門看看的話捏鱼,這兩個框架都是可以選擇的。不過由于我感興趣的是框架的具體實現(xiàn)酪耕,相比于 OC 我又更喜歡 Swfit导梆,所以挑了純 Swift 實現(xiàn)的 RxSwift 來看。

An API for asynchronous programming
with observable streams

上面這句話來自 Rx 的官網(wǎng)迂烁,看到streams我立馬就想到了《 SICP》第三章的“流”看尼,加之 Swift 對函數(shù)式編程的支持又很好,所以我原以為 RxSwift 的內(nèi)部實現(xiàn)會用延遲的表來作為信號流盟步,用流來表示某個對象順序狀態(tài)的時間史狡忙,這樣一切都是函數(shù),沒有狀態(tài)變化址芯,也就不需要同步機制來保證線程安全了。事實證明我還是圖樣窜觉!RxSwift 內(nèi)部還是有各種類各種繼承谷炸,當然也有各種同步機制:自旋鎖、遞歸鎖禀挫、原子操作……說好的 functional 呢旬陡?只有暴露給使用者的 API 是functional 么?這一開始讓我有些失望语婴,不過后來發(fā)現(xiàn)整個框架還是用到了大量函數(shù)式特性的描孟,只是不像我所想的那么純粹(一個 pure functional 的框架大概也很難真正流行起來……)。

好了吐槽完畢砰左,我們再看一句官網(wǎng)的介紹:

ReactiveX is a combination of the best ideas from
the Observer pattern, the Iterator pattern, and functional programming

這句話的意思是說 Rx 是結(jié)合了觀察者模式匿醒、迭代器模式和函數(shù)式編程這三種最佳思維的產(chǎn)物。雖然它沒有如我所想用純函數(shù)式的代碼實現(xiàn)缠导,不過用到了“流”的思想倒也是實實在在的廉羔。目前,我只看了一小部分代碼僻造,大致清楚了觀察者模式部分的實現(xiàn)憋他,下面就跟大家分享一下。

Observable 和 Observer

RxSwift 項目內(nèi)部有個 Rx.playground髓削,在介紹頁面上有這么一句話:

The key to understanding RxSwift is in understanding the notion of Observables. Creating them, manipulating them, and subscribing to them in order to react to changes.

這句話是說竹挡,理解 RxSwfit 的關(guān)鍵是理解“被觀察者”這個概念,創(chuàng)造它們立膛,操縱它們揪罕,然后訂閱它們來響應變化。Observable的重要性可見一斑。讓我們來看一個使用Observable的實例:

empty

empty creates an empty sequence. The only message it sends is the .Completed message.

介紹了一個 empty 函數(shù)耸序,它會創(chuàng)建一個空的 sequence(翻譯成序列的話總感覺會引起誤會)忍些,這個 sequence 只會發(fā)送 .Completed 這一個消息,示例代碼如下:

 let emptySequence: Observable<Int> = empty()
 let subscription = emptySequence
     .subscribe { event in
         print(event)
     }

上述代碼通過empty函數(shù)得到了一個Observable<Int>坎怪,好現(xiàn)在去看看empty

public func empty<E>() -> Observable<E> {
    return Empty<E>()
}

果然是 OOP 外面套 FP 的皮啊罢坝,沒有這個 empty 函數(shù)我們照樣可以直接let emptySequence = Empty<Int>()來得到一個Observable<Int>嘛,那現(xiàn)在就去看看這個Empty是個什么鬼:

class Empty<Element> : Producer<Element> {
    override init() {
        
    }

    override func run<O : ObserverType where O.E == Element>(observer: O, cancel: Disposable, setSink: (Disposable) -> Void) -> Disposable {
        observer.on(.Completed)
        return NopDisposable.instance
    }
}

乍一看這個類還是比較容易懵的搅窿。這個空構(gòu)造器是什么意思嘁酿?好吧大概是為了初始化的時候避免調(diào)用父類構(gòu)造器,就是確保什么都不做男应。然后下面這個 run 函數(shù)就令人費解了闹司,這一堆參數(shù),還有這個Disposable是什么沐飘?其實如果是寫過 C# 的朋友游桩,一定覺得這個Disposable非常熟悉,沒錯耐朴,它是一個協(xié)議(似乎微軟系的接口比較喜歡用形容詞借卧,用able結(jié)尾的很多),跟 C# 中用來顯式釋放資源的IDisposable接口類似:

/**
類似 C# 中的 IDisposable 接口筛峭,用來釋放資源铐刘。
由于 Swift 使用 ARC,所以 dispose 方法大部分時候只是取消對某個資源的引用,
譬如 resource = nil
*/
public protocol Disposable {
    /**
    Dispose resource.
    */
    func dispose()
}

由于這篇文章重點在于觀察者模式影晓,所以我想先放下Disposable相關(guān)的東西不談镰吵,因為涉及資源的保存釋放有一些線程相關(guān)的操作,挺麻煩的挂签,但這些跟觀察者模式并沒有什么關(guān)系疤祭。基于此饵婆,我把 RxSwfit 中跟empty画株、just相關(guān)的一些類稍微簡化了一下,去掉了Disposable相關(guān)的一些內(nèi)容啦辐,然后加了點注釋谓传,放到一起之后emptyjust這幾個例子還是都能正常運行芹关。

好的续挟,簡化后Empty類變成了這樣:

class Empty<Element> : Producer<Element> {
    override func run<O : ObserverType where O.E == Element>(observer: O) {
        // 觀察者訂閱了一個完成信號
        observer.on(.Completed)
    }
}

好,我們已經(jīng)得到一個Empty的實例侥衬,接下來我們要調(diào)用它的subscribe方法诗祸,這個subscribe方法的參數(shù)類型是(Event<E>) -> Void跑芳,是一個閉包類型。我們在ObservableType協(xié)議的擴展里找到了符合條件的subscribe方法:

extension ObservableType {
    func subscribe(on: (event: Event<E>) -> Void)  {
        // 構(gòu)造一個匿名觀察者直颅,把參數(shù) on 賦值給這個匿名觀察者的 eventHandler博个,
        // 相當于 let observer = AnonymousObserver(on)
        let observer = AnonymousObserver { e in
            on(event: e)
        }
        self.subscribeSafe(observer)
    }

subscribe 方法接受了閉包之后,先構(gòu)造了一個匿名觀察者功偿,event這個閉包作為構(gòu)造器的參數(shù)傳給了observer盆佣。看一下AnonymousObserver

class AnonymousObserver<ElementType> : ObserverBase<ElementType> {
    typealias Element = ElementType
    
    typealias EventHandler = Event<Element> -> Void
    
    private let eventHandler : EventHandler
    
    init(_ eventHandler: EventHandler) {
        // 資源情況追蹤(為了開發(fā)期解決內(nèi)存泄漏問題吧)
        #if TRACE_RESOURCES
            // 原子操作:resourceCount 加1
            OSAtomicIncrement32(&resourceCount)
        #endif
        self.eventHandler = eventHandler
    }
    // onCore 會被 on 調(diào)用(on 繼承自父類)
    override func onCore(event: Event<Element>) {
        return self.eventHandler(event)
    }
    
    #if TRACE_RESOURCES
    deinit {
    // 原子操作:resourceCount 減1
    OSAtomicDecrement32(&resourceCount)
    }
    #endif
}

忽略追蹤內(nèi)存情況的代碼不看械荷,這個類主要就是在構(gòu)造器中接受一個閉包共耍,然后賦值給私有屬性eventHandler,然后在onCore方法中吨瞎,eventHandler會被調(diào)用痹兜。可是我們之前看Empty類的時候已經(jīng)知道颤诀,觀察者的on方法會在run中被調(diào)用字旭,并不是這個onCore啊,看來還得到父類ObserverBase中看看:

class ObserverBase<ElementType>: ObserverType {
    typealias E = ElementType
    
    var isStopped: Int32 = 0
    
    init() {
    }
    
    func on(event: Event<E>) {
        switch event {
        case .Next:
            if isStopped == 0 {
                onCore(event)
            }
        // 一旦出現(xiàn)一次 Error 或 Completed 事件崖叫,之后也不會再執(zhí)行 onCore 了
        case .Error, .Completed:
            // OSAtomicCompareAndSwap32:比較和交換的原子操作谐算,如果 isStopped == 0,則 isStoppend = 1,返回 true,否則返回 false
            if !OSAtomicCompareAndSwap32(0, 1, &isStopped) {
                return
            }
            
            onCore(event)
        }
    }
    // 會在子類中重寫
    func onCore(event: Event<E>) {
        abstractMethod()
    }
}

好了归露,這下清楚了,onCore會被on調(diào)用斤儿【绨回到subscribe中繼續(xù)往下走,得到了observer這個實例之后往果,它將會一路被作為參數(shù)傳遞疆液。先是調(diào)用self.subscribeSafe(observer)observer被傳遞給subscribeSafe方法陕贮,這個方法同樣在ObserverTypeextension中:

func subscribeSafe<O: ObserverType where O.E == E>(observer: O) {
    // 會調(diào)用被子類實現(xiàn)的的 subscribe 方法
    self.subscribe(observer)
}

subscribeSafe中最后又會調(diào)用subscribe方法堕油,不過這個subscribe的參數(shù)是ObserverType的實現(xiàn)類,不是閉包肮之,所以這是一個重載方法掉缺。它的聲明在協(xié)議ObservableType中:

protocol ObservableType {
    /**
    hack: 因為 Swift 中沒有范型協(xié)議,只能在協(xié)議中聲明一個別名戈擒,
    然后將實現(xiàn)類聲明為范型類眶明,再將傳入的范型名命名為 E(如 typealias E = Element)
    在接受范型參數(shù)的地方這樣使用:
    func demo<O : ObservableType where O.E == Int>(ob: O)
    大致與 C# 中 void demo(ObservableType<int> ob) 作用相同
    */
    typealias E
    
    func subscribe<O: ObserverType where O.E == E>(observer: O)
}

我們發(fā)現(xiàn)這個方法沒有出現(xiàn)在Empty類中,只能沿著Empty的繼承樹往上找筐高,在Empty 的父類Producer中可以找到它的實現(xiàn):

class Producer<Element> : Observable<Element> {
    // 會被 ObserverType 的 extension 方法 subscribeSafe 調(diào)用
    override func subscribe<O : ObserverType where O.E == E>(observer: O) {
        // 會有一些關(guān)于資源釋放以及線程相關(guān)的操作
        // ……
        run(observer)
    }
    
    func run<O : ObserverType where O.E == Element>(observer: O) {
        abstractMethod()
    }    
}

subscribe方法會調(diào)用run方法搜囱,但是這個run方法里面調(diào)用了abstractMethod丑瞧,我們來看看它是什么:

@noreturn func abstractMethod() -> Void {
    fatalError("Abstract method")
}

一旦調(diào)用這個方法就會觸發(fā)致命錯誤fatalError,所以run必須被子類重寫蜀肘,否則程序會終止绊汹。我猜是因為 Swift 中沒有抽象類和抽象方法的概念,不能在函數(shù)前加 abstract 強制子類重寫該方法扮宠,只能用這種不重寫就終止的方式來模擬抽象方法西乖。既然這樣我們就來看看子類中的run方法:

class Empty<Element> : Producer<Element> {
    // run 會在父類中被 subscribe 方法調(diào)用
    override func run<O : ObserverType where O.E == Element>(observer: O) {
        // 觀察者訂閱了一個完成信號
        observer.on(.Completed)
    }
}

class Just<Element>: Producer<Element> {
    let element: Element
    
    init(element: Element) {
        self.element = element
    }
    
    override func run<O : ObserverType where O.E == Element>(observer: O) {
        observer.on(.Next(element))
        observer.on(.Completed)
    }
}

如上是EmptyJust的兩個run實現(xiàn),在Empty中涵卵,會調(diào)用傳遞過來的observeron方法一次浴栽,并將.Completed作為參數(shù)。我們知道這個on方法其實就是最開始我們調(diào)用subscribe方法時傳入的那個閉包轿偎,即{event in print(event)}典鸡,所以最后就會打印出.Completed。至于這個.Completed么坏晦,顯然是個枚舉萝玷,它是一個Event類型:

enum Event<Element> {
    case Next(Element)
    case Error(ErrorType)
    case Completed
}

Just的初始化函數(shù)會接受一個值并將其賦值給實例屬性element,然后調(diào)用run方法的時候昆婿,會調(diào)用傳遞過來的observeron方法兩次球碉,一次以.Next(element)為參數(shù),一次以.Completed為參數(shù)表示結(jié)束仓蛆。就像這樣:

// MARK: - 調(diào)用
print("just observable demo:")
let justObservable = just(1)
justObservable.subscribe { event in
    print(event)
}

print("----")

print("empty observable demo:")
let emptyObservable: Observable<Int> = empty()
emptyObservable.subscribe { event in
    print(event)
}

輸出:
just observable demo:
Next(1)
Completed
----
empty observable demo:
Completed

有點繞對不對睁冬,主要是因為繼承太多,很多方法都要到父類中去找看疙。我簡化后的版本在這里豆拨,可能我說這么多還不如大家自己 clone 下來看一眼來得明白。

小結(jié)

因為代碼只看了個開頭能庆,所以我暫時還不能理解 RxSwift 中繼承層級這么多的必要性施禾。主要這畢竟是個重型的框架,我必須讀一點記錄一點搁胆,不然看到后面就忘了前面弥搞。要說目前為止有什么收獲么,大抵是如下幾點:

  • 觀察者模式的 Swift 實現(xiàn)渠旁。
  • 借助 typealias 模擬范型協(xié)議的具體做法攀例。
  • 借助 fatalError 模擬抽象方法的具體做法。
  • 自旋鎖顾腊、遞歸鎖和兩種原子操作的用法肛度。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市投慈,隨后出現(xiàn)的幾起案子承耿,更是在濱河造成了極大的恐慌冠骄,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件加袋,死亡現(xiàn)場離奇詭異凛辣,居然都是意外死亡,警方通過查閱死者的電腦和手機职烧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門扁誓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蚀之,你說我怎么就攤上這事蝗敢。” “怎么了足删?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵寿谴,是天一觀的道長。 經(jīng)常有香客問我失受,道長讶泰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任拂到,我火速辦了婚禮痪署,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘兄旬。我一直安慰自己狼犯,他們只是感情好,可當我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布领铐。 她就那樣靜靜地躺著悯森,像睡著了一般。 火紅的嫁衣襯著肌膚如雪罐孝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天肥缔,我揣著相機與錄音莲兢,去河邊找鬼。 笑死续膳,一個胖子當著我的面吹牛改艇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坟岔,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼谒兄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了社付?” 一聲冷哼從身側(cè)響起承疲,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤邻耕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后燕鸽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兄世,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年啊研,在試婚紗的時候發(fā)現(xiàn)自己被綠了御滩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡党远,死狀恐怖削解,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沟娱,我是刑警寧澤氛驮,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站花沉,受9級特大地震影響柳爽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜碱屁,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一磷脯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧娩脾,春花似錦赵誓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至碰声,卻和暖如春喉刘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诗舰。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工杠纵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瞻颂。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓豺谈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贡这。 傳聞我的和親對象是個殘疾皇子茬末,可洞房花燭夜當晚...
    茶點故事閱讀 45,455評論 2 359

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