RxSwift原理2 - 按钮事件订阅

序章

在上一篇文章中,已经介绍了最基础的订阅与发布,那么按钮事件的订阅又是如何实现的呢,先看一下demo代码

let _ = addButton.rx.tap.subscribe { [weak self] in
    print("+号 next")
    self?.count += 1
    self?.countString.accept(String(self?.count ?? 0))
} onError: { _ in
    
} onCompleted: {
    print("+号 completed")
} onDisposed: {
    print("+号 订阅释放")
}

好了,我们现在来点击addButton看看会输出什么:

+号 next

正确执行了OnNext的闭包方法。细心的你可能会发现onCompleted、onDisposed并没有执行,这个后续写Disposed会详细说明,本文暂不涉及。

第一讲 - rx与tap

在深入了解他的实现之前,我们先需要认识一下rx与tap

走进rx

extension ReactiveCompatible {
    public static var rx: Reactive<Self>.Type {
        get { Reactive<Self>.self }
        set { }
    }

    public var rx: Reactive<Self> {
        get { Reactive(self) }
        set { }
    }
}

extension NSObject: ReactiveCompatible { }

看源码我们可以看到,rx是ReactiveCompatible协议中的方法,而NSObject遵循了这个协议,使得button可以调用rx方法。

而rx方法的返回的是一个Reactive对象,参数self即为button本身

public struct Reactive<Base> {
    public let base: Base
    public init(_ base: Base) {
        self.base = base
    }
}

Reactive就是一个结构体,而在本文中,button即被保存在base中

走进tap

tap方法的实现其实还是Reactive,看源码

extension Reactive where Base: UIButton {
    public var tap: ControlEvent<Void> {
        controlEvent(.touchUpInside)
    }
}

extension Reactive where Base: UIControl {
    public func controlEvent(_ controlEvents: UIControl.Event) -> ControlEvent<()> {
        //...省略
        return ControlEvent(events: source)
    }
}

可以看到tap方法实际调用的就是controlEvent方法, 并返回一个ControlEvent对象(ControlEvent遵循ObservableType协议)。
所以序章中的代码可以转换为以下代码:

//创建序列
let ob =  Reactive(addButton).controlEvent(.upInside) {
    //...省略
    return ControlEvent(events: source)
}
//订阅消息
let _ = ob.subscribe { [weak self] in
    print("+号 next")
    self?.count += 1
    self?.countString.accept(String(self?.count ?? 0))
} onError: { _ in
    
} onCompleted: {
    print("+号 completed")
} onDisposed: {
    print("+号 订阅释放")
}

第二讲 - 事件转移

你可能好奇为什么标题是事件转移,直接说结果,其实就是RxSwift截胡了addButton的点击事件并在内部实现,进而转换为OnNext事件。接下来我们一步一步看他的具体实现:

  1. 调用controlEvent方法,创建了source,也就是原理1种所说的创建序列,此时其尾随闭包还未调用,返回controlEvent对象
public func controlEvent(_ controlEvents: UIControl.Event) -> ControlEvent<()> {
    let source: Observable<Void> = Observable.create { [weak control = self.base] observer in
            let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) { _ in
                observer.on(.next(()))
            }
            return Disposables.create(with: controlTarget.dispose)
        }
        .take(until: deallocated)
    return ControlEvent(events: source)
}
  1. 执行tap.subscribe方法,也就是原理1所说的订阅消息,创建订阅者和订阅闭包
public func subscribe(
        onNext: ((Element) -> Void)? = nil,
        onError: ((Swift.Error) -> Void)? = nil,
        onCompleted: (() -> Void)? = nil,
        onDisposed: (() -> Void)? = nil
    ) -> Disposable {
            let disposable: Disposable
            //...省略代码
            let observer = AnonymousObserver<Element> { event in
                switch event {
                case .next(let value):
                    onNext?(value)
                //...省略代码
            }
            return Disposables.create(
                self.asObservable().subscribe(observer),
                disposable
            )
    }
  1. 紧接着执行self.asObservable().subscribe(observer)方法, 而此时的self即时是一个controlEvent对象,也就是执行controlEvent中的asObservable和subscribe方法
public struct ControlEvent<PropertyType> : ControlEventType {
    public typealias Element = PropertyType
    let events: Observable<PropertyType>
    //将第一步中的source与ConcurrentMainScheduler.instance关联,返回并保存关联后对象
    public init<Ev: ObservableType>(events: Ev) where Ev.Element == Element {
        self.events = events.subscribe(on: ConcurrentMainScheduler.instance)
    }
    public func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element {
        self.events.subscribe(observer)
    }
    public func asObservable() -> Observable<Element> {
        self.events
    }
}
  1. 也就是执行Producer中的subscirbe方法,即创建sink管道,保存第2步中的订阅者。
  2. 执行sink.run(self)方法,此处self即第1步创建的controlEvent对象,进而执行第1步中的尾随闭包
  3. 在ControlTarget中,实现了点击方法,并调用了callback,也就是触发了序章中的onNext方法
final class ControlTarget: RxTarget {
    typealias Callback = (Control) -> Void
    let selector: Selector = #selector(ControlTarget.eventHandler(_:))
    weak var control: Control?
    var callback: Callback?
    init(control: Control, controlEvents: UIControl.Event, callback: @escaping Callback) {
        MainScheduler.ensureRunningOnMainThread()
        self.control = control
        self.controlEvents = controlEvents
        self.callback = callback
        super.init()
        //内部实现了addTarget方法
        control.addTarget(self, action: selector, for: controlEvents)
        let method = self.method(for: selector)
        if method == nil {
            rxFatalError("Can't find method")
        }
    }
    @objc func eventHandler(_ sender: Control!) {
        //回调中触发callBack方法
        if let callback = self.callback, let control = self.control {
            callback(control)
        }
    }
}

到这里,整个逻辑就梳理完成了。

总结

最后画个图总结一下吧


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

推荐阅读更多精彩内容