走进 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 模拟抽象方法的具体做法。
  • 自旋锁、递归锁和两种原子操作的用法。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏好好学java的技术栈

Java基于百度API的图片文字识别(支持中文,英文和中英文混合)

具体文档:http://ai.baidu.com/docs#/OCR-API/e1bd77f3

2582
来自专栏我就是马云飞

LruCache源码解析

 今天我们来聊聊缓存策略相关的内容,LruCache应该说是三级缓存策略会使用到的内存缓存策略。今天我们就来扒一扒这里面的原理,同时也温故温故我们的数据结构方面...

2277
来自专栏黄Java的地盘

[翻译]WebSocket协议第二章——Conformance Requirements

本文为WebSocket协议的第二章,本文翻译的主要内容为WebSocket协议中相关术语的介绍。

871
来自专栏程序猿DD

Spring框架中的设计模式(二)

在 上一篇 中我们在Spring中所谈到的设计模式涉及到了创建模式三剑客和1个行为模式(解释器模式)。这次我们会将眼光更多地关注在具有结构性和行为性的设计模式上...

4098
来自专栏iOS开发

iOS开发之 Method Swizzling 深入浅出

如果产品经理突然说:"在所有页面添加统计功能,也就是用户进入这个页面就统计一次"。我们会想到下面的一些方法:

4757
来自专栏恰同学骚年

.NET基础拾遗(4)委托、事件、反射与特性

  委托这个概念对C++程序员来说并不陌生,因为它和C++中的函数指针非常类似,很多码农也喜欢称委托为安全的函数指针。无论这一说法是否正确,委托的的确确实现了和...

892
来自专栏FreeBuf

浅析ReDoS的原理与实践

*本文原创作者:MyKings,本文属FreeBuf原创奖励计划,未经许可禁止转载 ReDoS(Regular expression Denial of Ser...

6425
来自专栏coolblog.xyz技术专栏

自己动手实现一个简单的JSON解析器

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。相对于另一种数据交换格式 XML,JSON 有着诸多优点。比如易读...

66419
来自专栏程序员的SOD蜜

.net访问PostgreSQL数据库发生“找不到函数名”的问题追踪

    PostgreSQL是一个使用广泛的免费开源的数据库,与MySQL比较,它更适合复杂的企业计算任务,而MySQL在互联网领域应用更为广泛,究其原因,可能...

3497
来自专栏程序员维他命

iOS 代码规范

花了一个月的时间结合几篇博客和书籍写了这套 iOS 代码规范(具体参考底部的参考文献部分)。这套代码规范除了有仅适用于 iOS 开发的部分,还有其他的比较通用性...

1912

扫码关注云+社区

领取腾讯云代金券