RxSwift 源码解析01:RxSwift初体验

本文主要是RxSwift Demo的体验

RxSwift 介绍

  • RxSwift = ReactiveX + Swift

    • ReactiveX(简称 Rx):是一个可以帮助我们简化异步编程的框架
    • Swift:表示是Rx的swift版本
  • RxSwift 作为一个响应式编程框架,有以下两点好处:

    • 聚合逻辑:即通过RxSwift提供的各种操作符实现逻辑的聚合
    • 响应式编程:即将所有事件都描述成一个可监听的序列,并提供大量功能各异的操作符,通过声明式的语句来完成数据的获取,转换,结合及绑定

为什么要使用RxSwift?

  • 复合 - Rx 就是复合的代名词
  • 复用 - 因为它易复合
  • 清晰 - 因为声明都是不可变更的
  • 易用 - 因为它抽象了异步编程,使我们统一了代码风格
  • 稳定 - 因为 Rx 是完全通过单元测试的
  • 高大上 - 代码档次比原生高很多

如果对于响应式编程还不了解的同学,可以先阅读# iOS 底层原理37:链式编程 中对响应式编程的介绍

初体验

通过 Cocoapods 导入

    pod 'RxSwift', '6.5.0'
    pod 'RxCocoa', '6.5.0'

RxSwift 的使用步骤分为以下几步:

  • 创建序列(万物皆可 rx
  • 订阅信号
  • 发送信号
  • 输出结果

主要从以下几方面来体验

  • KVO
  • Target-Action:UIbutton
  • 代理:delegate
  • 手势
  • 通知:Notification
  • timer
  • 网络请求:URLSession
  • 多个任务间有依赖关系
  • 等待多个并发任务完成后处理结果

KVO

在Swift中实现 OC 的KVO

  • 传统实现

KVO 三部曲

  • 监听
  • 响应
  • 销毁
class Person: NSObject {
    //由于swift是静态语言,而KVO是运行时发生的,所以需要加 dynamic 关键字
    @objc dynamic var name: String = "CJL"
}
//1-设置监听
func  setupKVO(){
    self.person.addObserver(self, forKeyPath: "name", options: .new, context: nil)
}
//2-回调
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    print("响应")
    print(change as Any)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    print("来了")
    person.name = "\(person.name) 6"
    print(person.name)
}
//3-移除监听
deinit{
    self.removeObserver(self, forKeyPath: "name", context: nil)
}
  • Rx实现
func  setupKVO(){
    self.person.rx.observeWeakly(String.self, "name")
        .subscribe { value in
            print(value as Any)
        }
        .disposed(by: disposeBag)
    
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    print("来了")
    person.name = "\(person.name) 6"
}

Target-Action

这里以 UIButton 点击事件为例

  • 传统实现
var button: UIButton = {
    let btn = UIButton(type: .custom)
    btn.frame = CGRect(x: 100, y: 100, width: 200, height: 100)
    btn.setTitle("按钮点击", for: .normal)
    btn.backgroundColor = UIColor.lightGray
    return btn
}()

func setupButton(){
    button.addTarget(self, action: #selector(didClickButton), for: .touchUpInside)
}
@objc func didClickButton(){
    print("点击按钮")
}
  • Rx实现
func setupButton(){
    button.rx.controlEvent(.touchUpInside)
        .subscribe { _ in
            print("点击事件")
        }
        .disposed(by: disposeBag)
}

代理

这里以 UITextFiledDelegate 代理方法为例

  • 传统实现
func textFieldDidChangeSelection(_ textField: UITextField) {
    print(textField.text)
}
  • Rx实现
func setupTextField(){
    textFiled.rx.text.orEmpty.changed
        .subscribe { text in
            print(text)
        }
        .disposed(by: disposeBag)
}

此时还可以将 TextField 跟 button进行绑定,将textfiled输入的文本作为 button的标题

func setupTextField(){
    //textFiled跟button绑定,输入的内容作为button的title
    textFiled.rx.text
        .bind(to: button.rx.title())
        .disposed(by: disposeBag)
}

手势

这里以 UITapGestureRecognizer 点击手势为例

  • 传统实现
func setupGestureRecongizer(){
    let tap = UITapGestureRecognizer()
    self.view.addGestureRecognizer(tap)
    self.view.isUserInteractionEnabled = true
    tap.addTarget(self, action: #selector(tapAction(_:)))
}
@objc func tapAction(_ tap: UITapGestureRecognizer){
    print(tap.view)
}
  • Rx实现
func setupGestureRecongizer(){
    let tap = UITapGestureRecognizer()
    self.view.addGestureRecognizer(tap)
    self.view.isUserInteractionEnabled = true

    tap.rx.event
        .subscribe { tap in
            print(tap.view)
        }
        .disposed(by: disposeBag)
    
}

通知

这里以 keyboardWillShowNotification(键盘弹起通知)为例

  • 传统实现
func setupNotification(){
    // 监听键盘弹出通知
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name:UIResponder.keyboardWillShowNotification,object: nil)
}
@objc func keyboardWillShow(_ noti: Notification){
    print("键盘弹起")
}
  • Rx实现
func setupNotification(){
    // 监听键盘弹出通知
    NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
            .subscribe { noti in
                print("键盘弹起")
            }
            .disposed(by: disposeBag)
}

timer

实现计时器功能

  • 传统实现

5s倒计时

func setupTimer(){
    var countDownNum = 5
    let countdownTimer = Timer(timeInterval: 1.0, repeats: true) { timer in
        if countDownNum == 0 {
              // 销毁计时器
            timer.invalidate()
            // countDownNum = 5
            print(">>> Timer has Stopped!")
        } else {
            print(">>> Countdown Number: \(countDownNum)")
            countDownNum -= 1
        }
    }
    // 设置宽容度
    countdownTimer.tolerance = 0.2
    // 添加到当前 RunLoop,mode为默认。
    RunLoop.current.add(countdownTimer, forMode: .default)
    // 开始计时
    countdownTimer.fire()
}
  • Rx实现

正向计时

注:RxSwift 中的 timer并不是我们常见的几种timer定义的,而是自定义的,即不断创建序列,发送信号来实现的定时器效果,这个后面篇章会详细讲解

func setupTimer(){
    var timer: Observable<Int> = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
        timer.subscribe { num in
            print(num)
        }
        .disposed(by: disposeBag)
}

网络请求

  • 传统实现
func setupNetwork() {
    let url = URL(string: "https://www.baidu.com")
    URLSession.shared.dataTask(with: url!) { data, response, error in
        print(String.init(data: data!, encoding: .utf8)!)
    }.resume()
}
  • Rx实现
func setupNetwork() {
    let url = URL(string: "https://www.baidu.com")
    URLSession.shared.rx.response(request: URLRequest(url: url))
        .subscribe { response, data in
            print(response)
        }
        .disposed(by: disposeBag)
}

多个任务间有依赖关系

通过用户名密码取得 Token 然后通过 Token 取得用户信息,

  • 传统实现
// 用回调的方式封装接口
enum API {
    /// 通过用户名密码取得一个 token
    static func token(username: String, password: String,
        success: (String) -> Void,
        failure: (Error) -> Void) {  }

    /// 通过 token 取得用户信息
    static func userinfo(token: String,
        success: (String) -> Void,
        failure: (Error) -> Void) { }
}
func setupTaskDependency(){
    /// 通过用户名和密码获取用户信息
    API.token(username: "111", password: "222") { token in
        API.userinfo(token: token) { userinfo in
            print("获取用户信息成功: \(userinfo)")
        } failure: { error in
            print("获取用户信息失败: \(error)")
        }

    } failure: { error in
        print("获取用户信息失败: \(error)")
    }
}
  • Rx实现

避免了回调地狱,使得代码易读、已维护

/// 用 Rx 封装接口
enum API {
    /// 通过用户名密码取得一个 token
    static func token(username: String, password: String) -> Observable<String> {
        return Observable<String>.create{ observer in
            observer.onNext("1")
            return Disposables.create()
        }
    }

    /// 通过 token 取得用户信息
    static func userInfo(token: String) -> Observable<String> {
        return Observable<String>.create{ observer in
            observer.onNext("2")
            return Disposables.create()
        }
    }
}
func setupTaskDependency(){
    API.token(username: "111", password: "222")
        .flatMapLatest(API.userInfo)
        .subscribe(onNext: { userInfo in
            print("获取用户信息成功: \(userInfo)")
        }, onError: { error in
            print("获取用户信息失败: \(error)")
        })
        .disposed(by: disposeBag)
}

等待多个并发任务完成后处理结果

需要将两个网络请求合并成一个

  • 传统实现
let queue1:DispatchQueue = DispatchQueue.init(label: "textQueue1")
let queue2:DispatchQueue = DispatchQueue.init(label: "textQueue2")
// 队列queue加入队列组
queue1.async (group: self.gropQueue){
    /*任务1*/
    print("获取老师信息接口")

}
queue2.async (group: self.gropQueue){
    /*任务2*/
    print("获取老师评论接口")
}
/*2个异步任务完成后执行界面处理操作*/
self.gropQueue.notify(queue: DispatchQueue.main) {
    /*界面处理*/
    print("统一处理")
}
  • Rx实现

用几行代码实现复杂的异步操作

/// 用 Rx 封装接口
enum API2 {
    /// 取得老师的详细信息
    static func teacher(teacherId: Int) -> Observable<String> {
        return Observable<String>.create{ observer in
            observer.onNext("取得老师的详细信息")
            return Disposables.create()
        }
    }

    /// 取得老师的评论
    static func teacherComments(teacherId: Int) -> Observable<[String]> {
        return Observable<[String]>.create{ observer in
            observer.onNext(["取得老师的评论"])
            return Disposables.create()
        }
    }
}
func setupConcurrentTasks(){
    /// 同时取得老师信息和老师评论
    Observable.zip(
          API2.teacher(teacherId: 110),
          API2.teacherComments(teacherId: 130)
        ).subscribe(onNext: { (teacher, comments) in
            print("获取老师信息成功: \(teacher)")
            print("获取老师评论成功: \(comments.count) 条")
        }, onError: { error in
            print("获取老师信息或评论失败: \(error)")
        })
        .disposed(by: disposeBag)
}

疑问点

从上面的实现看,主要关注以下方面:

  • 其他对象调用的 rx 到底是什么?为什么都可以调用 rx?
  • timer 的创建为什么跟其他对象有所不同?
  • Observable、Observer是什么?

其他对象调用的 rx 到底是什么?为什么都可以调用 rx?

  • 点击 rx 进入其源码实现
/// A type that has reactive extensions.
public protocol ReactiveCompatible {
    /// Extended type  关联属性
    associatedtype ReactiveBase

    /// Reactive extensions.
    static var rx: Reactive<ReactiveBase>.Type { get set }

    /// Reactive extensions.
    var rx: Reactive<ReactiveBase> { get set }
}

//协议的扩展
extension ReactiveCompatible {
    /// Reactive extensions.
    public static var rx: Reactive<Self>.Type {
        get { Reactive<Self>.self }
        // this enables using Reactive to "mutate" base type
        // swiftlint:disable:next unused_setter_value
        set { }
    }

    /// Reactive extensions.
    public var rx: Reactive<Self> {
        get { Reactive(self) }
        // this enables using Reactive to "mutate" base object
        // swiftlint:disable:next unused_setter_value
        set { }
    }
}

import Foundation

// NSObject 遵循了 rx 所在的协议
/// Extend NSObject with `rx` proxy.
extension NSObject: ReactiveCompatible { }

  • 从源码可以看出 rx 是 ReactiveCompatible 协议的属性,而NSObject 遵循了 ReactiveCompatible,而我们常说 万物皆对象,对象溯源到底最终都指向 NSObject(即 rx 是 NSObject的扩展),所以这里得出一个结论:万物即可 rx

其余问题我们将在下一节来进行一一探索

参考文档

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

推荐阅读更多精彩内容