04. RxSwift源码解读:Subject

今天带大家解读下Subject相关类的源码。
在我们之前讲过的类中,有些类是观察者,有些是被观察者,今天要说的Subject比较特殊,它既是观察者,又是被观察者,兼具两者的特性。

Subject相关类包括:PublishSubject, BehaviorSubject, AsyncSubject, ReplaySubject

还是先了解基本用法,请看下面的例子:

    private let bag = DisposeBag()
    private let subject = PublishSubject<Int>()
    override func viewDidLoad() {
        super.viewDidLoad()
        subject.onNext(1)
        subject.subscribe(onNext: { ele in
            print("First ->",ele)
        })
        .disposed(by: bag)
        subject.onNext(2)
        subject.subscribe(onNext: { ele in
            print("Second ->",ele)
        })
        .disposed(by: bag)
        subject.onNext(3)
}

先定义一个subject = PublishSubject<Int>(), subject既可以发送序列,也可以订阅序列,它先后发送1,2,3,同时订阅两次,打印结果为:

First -> 2
First -> 3
Second -> 3

我们看到1没有打印,2打印一次 3打印两次,为什么呢?
PublishSubject只会接受订阅之后的元素,1在订阅之前发送,所以无法接受到元素。2在第一次订阅之后发送,所以收到第一次订阅的元素,3在两次订阅之后发送,所以两次订阅都接受到元素;我们通过源码分析下内部原理:

PublishSubject

因为Subject既是观察者又是被观察者,所以我们看看它的类的定义:

public final class PublishSubject<Element>
    : Observable<Element>
    , SubjectType
    , Cancelable
    , ObserverType
    , SynchronizedUnsubscribeType

其他3种Subject也是一样:首先继承了Observable,然后遵循了ObserverType,同时又遵循Cancelable,所以不是两者是三者兼具。另外还遵循了SubjectType,SubjectType继承了ObservableType,它有个协议方法asObserver(), 返回一个ObserverType, 一般返回自身对象:

    /// Returns observer interface for subject.
    public func asObserver() -> PublishSubject<Element> {
        self
    }

PublishSubject作为Observer,实现了on协议方法, 我们知道当observer调用onNext等方法时,会调用on方法:

    /// Notifies all subscribed observers about next event.
    ///
    /// - parameter event: Event to send to the observers.
    public func on(_ event: Event<Element>) {
        #if DEBUG
            self.synchronizationTracker.register(synchronizationErrorMessage: .default)
            defer { self.synchronizationTracker.unregister() }
        #endif
        dispatch(self.synchronized_on(event), event)
    }

synchronizationTracker.registersynchronizationTracker.unregister, 这两个是为了保证不会出现重入问题,即在当前on方法执行完成前,又执行了on方法,这会导致循环调用。
然后调用了synchronized_on:

    func synchronized_on(_ event: Event<Element>) -> Observers {
        self.lock.lock(); defer { self.lock.unlock() }
        switch event {
        case .next:
            if self.isDisposed || self.stopped {
                return Observers()
            }
            
            return self.observers
        case .completed, .error:
            if self.stoppedEvent == nil {
                self.stoppedEvent = event
                self.stopped = true
                let observers = self.observers
                self.observers.removeAll()
                return observers
            }

            return Observers()
        }
    }

这里是线程安全的,而且用的递归锁实现的,我想可能是为了保证锁能嵌套,因为可以会出现方法嵌套调用。
如果未释放资源,或者未停止,则直接返回一个Observers(),否则返回当前observers,这个Observers是重点,它其实是一个Bag:Bag<(Event<Element>) -> Void>, item类型是一个闭包。这个闭包是发送元素的闭包,Bag内部保存了这些闭包。Bag的内部实现其实是这样的:将第一个元素放在_key0 和 _value0中,key是通过自增的方式产生唯一的key。如果超过一个元素 则将其他元素存放在数组中,数组最大空间为30,超过30个元素则将多余的放在字典里,所以这个bag内部有一个数组和一个字典,这样设计的好处是当元素个数较少时(少于30),数组的查找效率不会低,同时空间使用率较高,超过30个数组查找效率会变低适合用字典,而字典的查找效率可以达到O1的效率,但空间使用率较低,所以才这么设计,大家可以自行查看Bag结构体的源码。我们可以把observers当成一个观察者容器,存放了所有观察者。

所以返回Observers(),相当于创建一个新Bag对象,所以内部没有元素。

当synchronized_on 返回observers后,接着执行dispatch方法:

func dispatch<Element>(_ bag: Bag<(Event<Element>) -> Void>, _ event: Event<Element>) {
    bag._value0?(event)

    if bag._onlyFastPath {
        return
    }

    let pairs = bag._pairs
    for i in 0 ..< pairs.count {
        pairs[i].value(event)
    }

    if let dictionary = bag._dictionary {
        for element in dictionary.values {
            element(event)
        }
    }
}

这个方法目的就取出所有闭包,并执行这些闭包,相当于发送序列。
这里先执行第一个元素的闭包(如果有的话),_onlyFastPath表示当元素只有一个时,则_onlyFastPath等于true,就是说当只有一个元素时,后面无需再执行了,有多个元素时还需要遍历数组和字典执行闭包,发出序列。
如果事件类型是.completed, .error, 那么将stopped 设置 true, stoppedEvent设置为event, 返回当前observers并清空observers,后续当然无法再接收事件.
stoppedEvent 设置完后,后面再订阅时会将这个event发出去,这个event一定是error或completed,我们可以看看订阅的代码:

      func synchronized_subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element {
        if let stoppedEvent = self.stoppedEvent {
            observer.on(stoppedEvent)
            return Disposables.create()
        }
        
        if self.isDisposed {
            observer.on(.error(RxError.disposed(object: self)))
            return Disposables.create()
        }
        
        let key = self.observers.insert(observer.on)
        return SubscriptionDisposable(owner: self, key: key)
    }

如果已经释放资源了,则发送error.
否则将observer的on方法放入observers,缓存起来。等到发送序列时再把所有缓存的Observer发出去;每订阅一次就会缓存一个。这就是为什么在订阅之前发送序列无效,因为没有在缓存中,只要在发送序列之前订阅了,怎么这些订阅都会收到这个序列。

然后再看看释放资源的代码:

    public func dispose() {
        self.lock.performLocked { self.synchronized_dispose() }
    }

    final func synchronized_dispose() {
        self.disposed = true
        self.observers.removeAll()
        self.stoppedEvent = nil
    }

很简单先加锁,然后移除所有observers, disposed= true, stoppedEvent = nil;
所以释放资源后,所有观察者无法在接收到序列了。

现在试着在例子中加入error和complete事件:

        subject.onNext(1)
        subject.subscribe(onNext: { ele in
            print("First ->",ele)
        }, onError: { error in
            print(error)
        }, onCompleted: {
            print("complete")
        })
        .disposed(by: bag)
        subject.onNext(2)
        subject.subscribe(onNext: { ele in
            print("Second ->",ele)
        }, onError: { error in
            print(error)
        }, onCompleted: {
            print("complete")
        }).disposed(by: bag)
        
        subject.onNext(3)
        subject.onError(RxError.unknown)
        subject.onCompleted()
        subject.subscribe(onNext: { ele in
            print("Third ->",ele)
        }, onError: { error in
            print(error)
        }, onCompleted: {
            print("complete")
        }).disposed(by: bag)

打印出:

First -> 2
First -> 3
Second -> 3
Unknown error occurred.
Unknown error occurred.
Unknown error occurred.

当订阅error时,也会将发送序列的闭包缓存到observers中,这样在调用onError时,两个观察者都会收到error事件,所以打印前两条error,之后在发送onCompleted事件时,因为在发送onError时会清空observers,所以这个时候观察者什么都收不到,之后又订阅了一次,这时因为stoppedEvent != nil, 所以直接调用on发送之前的error事件,所以会再打印一次error。

BehaviorSubject

BehaviorSubject的代码跟PublishSubject基本相同,只是BehaviorSubject会保存最后一次发送的元素。

 private var element: Element

BehaviorSubject初始化时,会给这个element初始化。然后订阅时插入observer.on的同时会把这个element发出去。

        let key = self.observers.insert(observer.on)
        observer.on(.next(self.element))

发送onNext时会更新这个element。

         case .next(let element):
            self.element = element

AsyncSubject

AsyncSubject会发出Observable发出的最后一个值(也仅是最后一个值),并且只有在这个Observable完成之后;(如果Observable没有发出任何值,那么AsyncSubject也会在没有发出任何值的情况下完成。),完成事件发出后,后续无法再接受事件,error事件也算完成事件。
考虑下面的例子:

subject.onNext(1)
        subject.subscribe(onNext: { ele in
            print("First ->",ele)
        }, onError: { error in
            print("First ->", error)
        }, onCompleted: {
            print("complete")
        })
        .disposed(by: bag)
        subject.onNext(2)
        subject.subscribe(onNext: { ele in
            print("Second ->",ele)
        }, onError: { error in
            print("Second ->", error)
        }, onCompleted: {
            print("complete")
        }).disposed(by: bag)
        
        subject.onNext(3)
//        subject.onError(RxError.unknown)
        subject.onCompleted()
        
        subject.subscribe(onNext: { ele in
            print("Third ->",ele)
        }, onError: { error in
            print("Third ->", error)
        }, onCompleted: {
            print("complete")
        }).disposed(by: bag)

这时候只会发出3,同时complete事件也会发出,打印结果:

First -> 3
Second -> 3
complete
complete
Third -> 3
complete

onCompleted之后再次订阅,会收到最后的元素和完成事件。
如果打开// subject.onError(RxError.unknown)的注释,则只会打印error事件:

First -> Unknown error occurred.
Second -> Unknown error occurred.
Third -> Unknown error occurred.

error之后,事件已经完成,再发送onCompleted则无效,所以不会打印complete。
有一点跟BehaviorSubject很像就是在发出完成事件之后再订阅依然可以收到最后一个元素和完成事件。
在源码中subject保存了最后一个元素(lastElement),发出error时,会立刻向所有观察者发出error事件,同时将stoppedEvent赋值为error事件,之后再也无法发出事件:

        if self.isStopped {
            return (Observers(), .completed)
        }

如果没有发出error, 而是发出了complete事件,怎么判断是否有lastElement,如果有则向所有观察者发出最后一个元素和complete事件,如果没有lastElement,怎么只发出complete事件,之后清空观察者。stoppedEvent赋值为最后一个next事件。
在订阅的代码中:

    func synchronized_subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element {
        if let stoppedEvent = self.stoppedEvent {
            switch stoppedEvent {
            case .next:
                observer.on(stoppedEvent)
                observer.on(.completed)
            case .completed:
                observer.on(stoppedEvent)
            case .error:
                observer.on(stoppedEvent)
            }
            return Disposables.create()
        }

        let key = self.observers.insert(observer.on)

        return SubscriptionDisposable(owner: self, key: key)
    }

如果已经完成(前面已经发出了error或complete),则发出最后一个error,或最后一个元素和complete,或者只发出complete。
否则将观察者插入到observers中。

ReplaySubject

我们把PublishSubject理解为没有缓存的序列,BehaviorSuject缓存一个元素,而ReplaySubject可以缓存任意个元素,根据缓存策略,每项通知都会广播给所有已订阅和未来的观察者。
实现方式是:ReplayOne缓存一个元素,ReplayManyBase缓存多个元素,通过队列维护这些元素,发出缓存元素时,遍历队列发送元素。(先进先出)

    override func replayBuffer<Observer: ObserverType>(_ observer: Observer) where Observer.Element == Element {
        for item in self.queue {
            observer.on(.next(item))
        }
    }

当buffer的数量超出设置的数量时,进行出队操作

    override func trim() {
        while self.queue.count > self.bufferSize {
            _ = self.queue.dequeue()
        }
    }

ReplayManyBase继承自ReplayBufferBase,ReplayBufferBase中封装了核心逻辑,发送数据时先缓存元素,再向观察者发出元素,订阅时先重放缓存的元素,再保存观察者。

总结

Suject因为既是Observable,ObserverType,同时又时Cancelable,核心逻辑基本都在一个类中,相对来说代码比较简单。它实现了一个典型的观察者模式(先保存所有的观察者,发送事件时通知所有观察者)。

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

推荐阅读更多精彩内容