美文网首页iOS开发框架使用与解析
RxSwift - 使用与源码分析

RxSwift - 使用与源码分析

作者: Lcr111 | 来源:发表于2023-03-05 11:54 被阅读0次

前言

经过对RxSwift的工作原理简单分析后iOS框架 - RXSwift原理初探,我们就来看看如何使用及分析下对应的工作流程。
RxSwift版本-6.5.0

1、准备工作

新建Swift项目,利用Cocoapods集成RxSwift和RxCocoa两个三方库:

platform :ios, '10.0'
target 'Rx-Test1' do

use_frameworks!

pod 'RxSwift'
pod 'RxCocoa'

end
2、KVO

新建Person类,添加属性name,点击页面改变name以观察name属性的变动:

class Person: NSObject {
    var name: String = "Lcr"
}

在ViewController添加如下代码:

var person: Person = Person()
let disposeBag = DisposeBag()

func setupKVO() {
        self.person.rx.observe(String.self, "name")
            .subscribe { value in
                print(value as Any)
            }
            .disposed(by: disposeBag)
}
    
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.person.name = "xuxixixixiixi"
}

调用setupKVO(),当我们点击屏幕时,改变person.name,发现报如下错误:

Thread 1: "[<Rx_Test1.Person 0x60000374ef80> addObserver:<_TtC7RxCocoaP33_F7515DBB13B60709A3CB25DD19EDD11D11KVOObserver 0x600003907270> forKeyPath:@"name" options:5 context:0x0] was sent to an object that is not KVC-compliant for the "name" property."

也就是说不能通过KVC方式去获取到name属性,也就是获取不到name属性,所以需要给name属性添加上@objc修饰符:

@objc var name: String = "Lcr"

重新运行立即打印结果:next(Optional("Lcr")),点击屏幕打印next(Optional("xuxixixixiixi")),说明当name值改变的时候就会触发一次.next方法。
查看.observe方法定义:

.observe

返回KVOObservable类型的序列,即.observe生成一个序列,初始化只是简单保存相应的参数:


KVOObservable初始化

接着.subscribe方法的调用(KVOObservable内部的.subscribe),

KVO-.subscribe

发现里面生成一个KVOObserver类型的observer观察者,进入初始化方法,然后继续super.init方法,就会发现_RXKVOObserver类内的代码就很熟悉了:


KVO-_RXKVOObserver

实现了addObserver、observeValueForKeyPath以及removeObserver三部曲,self.target为person对象,self.keyPath为"name",self.callback为下图中的闭包即KVOObserver初始化带进来的:

KVO-callback闭包
所以当name属性值改变时,self.callback(change[NSKeyValueChangeNewKey])就会触发一次闭包运行,接着observer.on(.next)),最后就会运行到最外层订阅的闭包,并将新值作为参数带进去:
.subscribe { value in
         print(value as Any)
}

可见RxSwift内部实现中间类为我们做了我们想做的事,代码量少,以及代码都在一块,方便管理。

3、UIButton点击
func setButtonTap() {
        //self.button.rx.tap  也可直接这么用因为 tap = controlEvent(.touchUpInside)
        self.button.rx.controlEvent(.touchUpInside)
            .subscribe { _ in
                print("点击按钮")
            }
            .disposed(by: disposeBag)
    }

也可直接.tap方式添加按钮的点击事件,查看.tap即为controlEvent(.touchUpInside)
查看.controlEvent方法实现:

UIButton-.controlEvent

同样的也是返回一个ControlEvent类型的序列,内部也初始化了一个观察者source,并以这个source为参数进行初始化:

ControlEvent初始化
发现self.events为外界的source序列通过.subscribe函数返回的SubscribeOn类型的序列:
.subscribe(on)
此处的source还是为外界带进来的source序列,scheduler为调度者:

查看SubscribeOn类包括初始化方法及run方法,看到这里是不是感觉这个类的结构如此熟悉?
没错!可以直接来看sink.run()方法:
sink.run
此处的source即为最外层.controlEvent方法中创建的source序列,触发.subscribe后,.controlEvent中的闭包就会运行,于是
UIButton-.controlEvent
就来到ControlTarget初始化中:

从中可以看出,这是给Button添加点击事件,control.addTarget(self, action: selector, for: controlEvents)

4、UITextField

输入框主要就是监听输入事件以及输入后的响应:

func setupTextfield() {
        self.textfield.rx.text.orEmpty.changed
            .subscribe { text in
                print(text)
            }.disposed(by: disposeBag)
    }

查看.text里到底做了什么:

UITextField.text
继续查看base.rx.controlPropertyWithDefaultEvents
controlProperty
go on,相关重要操作就在.controlProperty函数里面:
UITextField.controlProperty
进入ControlProperty初始化函数,发现似曾相识的values.subscribe(on:
ControlProperty初始化
SubscribeOn
类似于按钮的点击,接下里的步骤都是一样的,最终都会回到上方.text函数中的闭包调用,即给输入框添加事件响应。
control.addTarget(self, action: selector, for: controlEvents)
5、手势
func setupGestureRecognizer() {
        let tap = UITapGestureRecognizer()
        self.textLcbel.addGestureRecognizer(tap)
        self.textLcbel.isUserInteractionEnabled = true
        tap.rx.event.subscribe { tap in
            print(tap.view!)
        }.disposed(by: disposeBag)
    }

查看.event方法:


手势.event
6、通知
func setupNotification() {
        NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
            .subscribe { noti in
                print(noti)
            }.disposed(by: disposeBag)
    }

查看.notification函数:


.notification

当.subscribe订阅之后,必定会来到上图中的闭包,闭包运行,实际就是添加通知以及响应闭包。当收到通知时,再运行保存的闭包:

{ notification in
        observer.on(.next(notification))
}

就能触发.subscribe后的闭包运行,处理相关事件,达到接收通知的目的。

7、定时器

拓展:Swift三种定时器对比:

var timer = Timer()
var gcdTimer: DispatchSourceTimer?
var cadTimer: CADisplayLink?

//方式一 NSTimer
timer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerFire), userInfo: nil, repeats: true)
timer.fire()
RunLoop.current.add(timer, forMode: .common)

//方式二 GCD
gcdTimer = DispatchSource.makeTimerSource()
gcdTimer?.schedule(deadline: .now(), repeating: .seconds(1))
gcdTimer?.setEventHandler(handler: {
     print("hello GCD")
})
gcdTimer?.resume()
//        gcdTimer?.suspend()
//        gcdTimer?.cancel()
//        gcdTimer = nil

//方式三 CADisplayLink
cadTimer = CADisplayLink(target: self, selector: #selector(timerFire))
cadTimer?.preferredFramesPerSecond = 1
cadTimer?.add(to: .current, forMode: .common)

@objc func timerFire() {
        print("Lcr111")
}

三种方式都可实现每秒打印的需求,但是NSTimer和CADisplayLink需要添加进RunLoop中,而且还得是 .common模式,不然会被UI事件影响正常的运行。
那RxSwift中使用的是哪一种方案呢?
在我们平时开发中,GCD用的比较多,使用方便,更精确,操作性也更强,且不用依赖RunLoop,不受UI事件影响,那RxSwift中是否也是用的GCD的方式呢?

Observable<Int>.timer(.seconds(1), period: .seconds(1), scheduler: MainScheduler.instance)
            .subscribe { result in
                print(result)
            }
            .disposed(by: disposeBag)

查看.timer里的实现:

timer-1
Timer-sink
timerSink-run
工作模式都是一样的,所以我们直接来到run方法中,继续跟进:
schedulePeriodic
查看并行队列调度者中的实现:


DispatchSource.makeTimerSource(queue: self.queue),可见RxSwift定时器就是封装的GCD
timer.setEventHandler(handler: {
        if cancelTimer.isDisposed {
                return
        }
       timerState = action(timerState)
})

任务闭包中action就是run()方法中带进来的闭包,

{ state in
        self.lock.performLocked {
        self.forwardOn(.next(state))
        return state &+ 1
}

这个闭包中的forwardOn最终执行的就是外界的onNext事件。state在这个闭包内每秒增加1,这样外面打印都是递增的数值。

注意:

  • 当第二个参数period没有没有给的时候,此时的定时器任务只执行一次
  • 也可使用interval方式创建定时器,而且可以不指定period参数也可无限制执行任务。
8、网络请求
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()
//方式二
        URLSession.shared.rx.response(request: URLRequest(url: url!))
            .subscribe { response, data in
                print(response)
            }
            .disposed(by: disposeBag)
    }

查看.response:

.response
方式二中RxSwift就是将.dataTask请求任务封装在内部,外部返回的数据中将各种情况分开处理,成功、失败和完成以及销毁都是分开处理的,不像方式一中处理返回结果时还得判断是否出错等等。
9、简单示例

下面例子就是日常开发中,登录界面很正常的功能,账号和密码长度的校验功能。
1.用户名长度不小于5,小于5时候密码不可输入
2.密码长度不小于5
3.登录按钮在满足上面两条件下才能点击

let nameValid = textfield.rx.text.orEmpty
            .map { text in
                return text.count >= 5
            }
        
        nameValid.bind(to: textLcbel.rx.isHidden)
            .disposed(by: disposeBag)
        nameValid.bind(to: passwordTf.rx.isEnabled)
            .disposed(by: disposeBag)
        
        // passwordTf 密码输入框
        // passwordLb 密码提示语
        let passwValid = passwordTf.rx.text.orEmpty
            .map { text in
                return text.count >= 5
            }
        passwValid.bind(to: passwordLb.rx.isHidden)
            .disposed(by: disposeBag)
        
        // button 登录按钮
        Observable.combineLatest(nameValid, passwValid){$0 && $1}
            .bind(to: button.rx.isEnabled)
            .disposed(by: disposeBag)
        
        button.rx.tap.subscribe { _ in
            print("登录")
        }.disposed(by: disposeBag)

.combineLatest我们下篇文章会详细介绍,此处就是达到同时订阅两个序列的目的,同时监听两个条件的满足与否。

本文只是对一些常用的UI控件对应RxSwift的简单使用以及原理探究,接下来会接触更多使用及原理探究。

相关文章

  • RxSwift源码分析(一)-核心逻辑解析

    前言:这几篇关于RxSwift源码分析的文章主要是对源码进行解析,不涉及到RxSwift的具体使用。具体使用可以查...

  • RxSwift-初探

    RxSwift-初探RxSwift核心逻辑-源码分析RxSwift-Observable序列的创建方式RxSwif...

  • RxSwift 核心探究

    前言 RxSwift 由于在日常工作中会经常使用,所以下面进行核心源码分析与探究,学习优秀开源框架之路,先进行序列...

  • RxSwift 粗略源码分析

    简单分析RxSwift源码,这段代码到底做了什么? RxSwift中有这些基础的东西:Observable、Obs...

  • RxSwift (三)Observable的创建,订阅,销毁

    @TOC 可观察的序列Observable 通过前面博客对Rxswift的源码分析,我们知道在Rxswift中一条...

  • 源码浅析 RxSwift 5.0 - Subscription

    源码浅析 RxSwift 5.0 - Subscription源码浅析 RxSwift 5.0 - Subscri...

  • RxSwift-初探

    就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!RxSwift-初探RxSwift核心逻辑-源码分析...

  • RxSwift-高阶函数(上)

    就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!RxSwift-初探RxSwift核心逻辑-源码分析...

  • RxSwift-高阶函数(下)

    就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!RxSwift-初探RxSwift核心逻辑-源码分析...

  • iOS 【Swift-循环】

    摘录:RxSwift源码分析(20)——内存管理[https://www.jianshu.com/p/0615f5...

网友评论

    本文标题:RxSwift - 使用与源码分析

    本文链接:https://www.haomeiwen.com/subject/gupjldtx.html