走进 RxSwift 之观察者模式

RxSwift简介和吐槽

RxSwiftReactiveX 系列的 Swift 版本,如果你之前用过 ReactiveCocoa(RAC) 的话,想必对 Functional Reactive Programming(FRP,函数响应式编程)这个概念不会陌生,是的,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 启发的,所以也受了 Reactive Extensions 很大的影响(当然,之后它罗列了 RAC 跟 Rx 的一些差别,并且安利 iOS 开发者来使用 RAC)。第二句是说,虽然 RAC 是作为一个 OC 框架出道的,但是从3.0版本开始,所有主要特性的开发都已经以 Swift API 为重心了。也就是说,今后不管是不是下雨天,RAC 都跟 Swift 更配哦。

如果你是一个使用 Swift 的 iOS 开发者,并且对无处不在的 OOP 感到了厌倦,想要打开新世界的大门看看的话,这两个框架都是可以选择的。不过由于我感兴趣的是框架的具体实现,相比于 OC 我又更喜欢 Swfit,所以挑了纯 Swift 实现的 RxSwift 来看。

An API for asynchronous programming
with observable streams

上面这句话来自 Rx 的官网,看到streams我立马就想到了《 SICP》第三章的“流”,加之 Swift 对函数式编程的支持又很好,所以我原以为 RxSwift 的内部实现会用延迟的表来作为信号流,用流来表示某个对象顺序状态的时间史,这样一切都是函数,没有状态变化,也就不需要同步机制来保证线程安全了。事实证明我还是图样!RxSwift 内部还是有各种类各种继承,当然也有各种同步机制:自旋锁、递归锁、原子操作……说好的 functional 呢?只有暴露给使用者的 API 是functional 么?这一开始让我有些失望,不过后来发现整个框架还是用到了大量函数式特性的,只是不像我所想的那么纯粹(一个 pure functional 的框架大概也很难真正流行起来……)。

好了吐槽完毕,我们再看一句官网的介绍:

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

这句话的意思是说 Rx 是结合了观察者模式、迭代器模式和函数式编程这三种最佳思维的产物。虽然它没有如我所想用纯函数式的代码实现,不过用到了“流”的思想倒也是实实在在的。目前,我只看了一小部分代码,大致清楚了观察者模式部分的实现,下面就跟大家分享一下。

Observable 和 Observer

RxSwift 项目内部有个 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 的关键是理解“被观察者”这个概念,创造它们,操纵它们,然后订阅它们来响应变化。Observable的重要性可见一斑。让我们来看一个使用Observable的实例:

empty

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

介绍了一个 empty 函数,它会创建一个空的 sequence(翻译成序列的话总感觉会引起误会),这个 sequence 只会发送 .Completed 这一个消息,示例代码如下:

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

上述代码通过empty函数得到了一个Observable<Int>,好现在去看看empty

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

果然是 OOP 外面套 FP 的皮啊,没有这个 empty 函数我们照样可以直接let emptySequence = Empty<Int>()来得到一个Observable<Int>嘛,那现在就去看看这个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
    }
}

乍一看这个类还是比较容易懵的。这个空构造器是什么意思?好吧大概是为了初始化的时候避免调用父类构造器,就是确保什么都不做。然后下面这个 run 函数就令人费解了,这一堆参数,还有这个Disposable是什么?其实如果是写过 C# 的朋友,一定觉得这个Disposable非常熟悉,没错,它是一个协议(似乎微软系的接口比较喜欢用形容词,用able结尾的很多),跟 C# 中用来显式释放资源的IDisposable接口类似:

/**
类似 C# 中的 IDisposable 接口,用来释放资源。
由于 Swift 使用 ARC,所以 dispose 方法大部分时候只是取消对某个资源的引用,
譬如 resource = nil
*/
public protocol Disposable {
    /**
    Dispose resource.
    */
    func dispose()
}

由于这篇文章重点在于观察者模式,所以我想先放下Disposable相关的东西不谈,因为涉及资源的保存释放有一些线程相关的操作,挺麻烦的,但这些跟观察者模式并没有什么关系。基于此,我把 RxSwfit 中跟emptyjust相关的一些类稍微简化了一下,去掉了Disposable相关的一些内容,然后加了点注释,放到一起之后emptyjust这几个例子还是都能正常运行。

好的,简化后Empty类变成了这样:

class Empty<Element> : Producer<Element> {
    override func run<O : ObserverType where O.E == Element>(observer: O) {
        // 观察者订阅了一个完成信号
        observer.on(.Completed)
    }
}

好,我们已经得到一个Empty的实例,接下来我们要调用它的subscribe方法,这个subscribe方法的参数类型是(Event<E>) -> Void,是一个闭包类型。我们在ObservableType协议的扩展里找到了符合条件的subscribe方法:

extension ObservableType {
    func subscribe(on: (event: Event<E>) -> Void)  {
        // 构造一个匿名观察者,把参数 on 赋值给这个匿名观察者的 eventHandler,
        // 相当于 let observer = AnonymousObserver(on)
        let observer = AnonymousObserver { e in
            on(event: e)
        }
        self.subscribeSafe(observer)
    }

subscribe 方法接受了闭包之后,先构造了一个匿名观察者,event这个闭包作为构造器的参数传给了observer。看一下AnonymousObserver

class AnonymousObserver<ElementType> : ObserverBase<ElementType> {
    typealias Element = ElementType
    
    typealias EventHandler = Event<Element> -> Void
    
    private let eventHandler : EventHandler
    
    init(_ eventHandler: EventHandler) {
        // 资源情况追踪(为了开发期解决内存泄漏问题吧)
        #if TRACE_RESOURCES
            // 原子操作:resourceCount 加1
            OSAtomicIncrement32(&resourceCount)
        #endif
        self.eventHandler = eventHandler
    }
    // onCore 会被 on 调用(on 继承自父类)
    override func onCore(event: Event<Element>) {
        return self.eventHandler(event)
    }
    
    #if TRACE_RESOURCES
    deinit {
    // 原子操作:resourceCount 减1
    OSAtomicDecrement32(&resourceCount)
    }
    #endif
}

忽略追踪内存情况的代码不看,这个类主要就是在构造器中接受一个闭包,然后赋值给私有属性eventHandler,然后在onCore方法中,eventHandler会被调用。可是我们之前看Empty类的时候已经知道,观察者的on方法会在run中被调用,并不是这个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)
            }
        // 一旦出现一次 Error 或 Completed 事件,之后也不会再执行 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调用。回到subscribe中继续往下走,得到了observer这个实例之后,它将会一路被作为参数传递。先是调用self.subscribeSafe(observer)observer被传递给subscribeSafe方法,这个方法同样在ObserverTypeextension中:

func subscribeSafe<O: ObserverType where O.E == E>(observer: O) {
    // 会调用被子类实现的的 subscribe 方法
    self.subscribe(observer)
}

subscribeSafe中最后又会调用subscribe方法,不过这个subscribe的参数是ObserverType的实现类,不是闭包,所以这是一个重载方法。它的声明在协议ObservableType中:

protocol ObservableType {
    /**
    hack: 因为 Swift 中没有范型协议,只能在协议中声明一个别名,
    然后将实现类声明为范型类,再将传入的范型名命名为 E(如 typealias E = Element)
    在接受范型参数的地方这样使用:
    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)
}

我们发现这个方法没有出现在Empty类中,只能沿着Empty的继承树往上找,在Empty 的父类Producer中可以找到它的实现:

class Producer<Element> : Observable<Element> {
    // 会被 ObserverType 的 extension 方法 subscribeSafe 调用
    override func subscribe<O : ObserverType where O.E == E>(observer: O) {
        // 会有一些关于资源释放以及线程相关的操作
        // ……
        run(observer)
    }
    
    func run<O : ObserverType where O.E == Element>(observer: O) {
        abstractMethod()
    }    
}

subscribe方法会调用run方法,但是这个run方法里面调用了abstractMethod,我们来看看它是什么:

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

一旦调用这个方法就会触发致命错误fatalError,所以run必须被子类重写,否则程序会终止。我猜是因为 Swift 中没有抽象类和抽象方法的概念,不能在函数前加 abstract 强制子类重写该方法,只能用这种不重写就终止的方式来模拟抽象方法。既然这样我们就来看看子类中的run方法:

class Empty<Element> : Producer<Element> {
    // run 会在父类中被 subscribe 方法调用
    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实现,在Empty中,会调用传递过来的observeron方法一次,并将.Completed作为参数。我们知道这个on方法其实就是最开始我们调用subscribe方法时传入的那个闭包,即{event in print(event)},所以最后就会打印出.Completed。至于这个.Completed么,显然是个枚举,它是一个Event类型:

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

Just的初始化函数会接受一个值并将其赋值给实例属性element,然后调用run方法的时候,会调用传递过来的observeron方法两次,一次以.Next(element)为参数,一次以.Completed为参数表示结束。就像这样:

// MARK: - 调用
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 下来看一眼来得明白。

小结

因为代码只看了个开头,所以我暂时还不能理解 RxSwift 中继承层级这么多的必要性。主要这毕竟是个重型的框架,我必须读一点记录一点,不然看到后面就忘了前面。要说目前为止有什么收获么,大抵是如下几点:

  • 观察者模式的 Swift 实现。
  • 借助 typealias 模拟范型协议的具体做法。
  • 借助 fatalError 模拟抽象方法的具体做法。
  • 自旋锁、递归锁和两种原子操作的用法。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容