Swift学习20:RxSwift使用

张建 lol

前言

  • OC 响应式编程一般使用 ReactiveCocoa

  • Swift 响应式编程一般使用 RxSwiftRxSwift 目的是让 数据/事件流和异步任务 能够更方便的 序列化处理

RxSwift 做了什么?

  • RxSwift 把我们程序中每一个 操作 都看成一个 事件

  • 比如:一个 TextField 文本的改变,一个 Button 按钮被点击,一个 网络请求结束 等,每一个 事件源 都可以看成一个 管道,也就是 sequence

  • 比如:TextField,当我们改变文本内容时,这个 TextField 就会不断的发出事件,从这个 sequence 中不断流出,我们只需要监听这个 sequence,每流出一个 事件 就做相应的处理。

  • 同理,Button 也是一个 sequence,每点击一次就流出一个事件

RxSwift 的核心思想是 Observable

  • Observable sequence:表示 可监听或可观察,也就是说 RxSwift 的核心思想是 可监听的序列

  • 并且 Observable sequence 可以接受 异步信号,也就是说,信号可以异步给 监听者

RxSwift Observable 有三种信号

  • 新的信号到来:Next
  • 信号发生错误,序列不会再产生信号:Error
  • 序列发送信号完成,不会再产生新的信号:Completed

RxSwift Observable 取消监听

  • 在有限的时间自动结束(Completed/Error),比如:一个网络请求作为一个序列,当网络请求完成时,Observable 自动结束,资源被释放

  • 信号不会自己结束,比如:Timer,每隔一段时间发送一个新的信号,这时需要手动取消监听,来释放相应的资源

  • 在比如:一个 label.rx.text 是一个 Observable,通常需要调用 addDisposableTo(disposeBag) 来让其 deinit,也就是所有者要释放的时候,自动取消监听

当然,除了手动释放,RxSwift 也提供了一些操作符,比如:takeUntil 来根据条件取消

RxSwift 引入

Podfile文件中添加如下,并 pod update

1
2
3
# 响应式编程库
pod 'RxSwift'
pod 'RxCocoa'

RxSwift 简单使用

  • 首先,导入头文件
1
2
import RxSwift
import RxCocoa
  • 其次,创建 deinit 属性,也就是 所有者要释放的时候,自动取消监听

注:
fileprivate 关键字:只能在当前文件下使用
private 关键字:只能在当前类中使用,类扩展中也可以使用
lazy 延迟存储:在第一次使用的时候才会初始化

1
2
// 创建deinit属性,在观察者被释放的时候,自动取消监听
fileprivate lazy var bag = DisposeBag()
  1. RxSwift 监听按钮的点击
  • 创建一个按钮
1
2
3
4
5
// 传统方式
let btn = UIButton(type: UIButton.ButtonType.custom)
btn.backgroundColor = UIColor.blue
btn.frame = CGRect(x: 100, y: 100, width: 100, height: 50)
self.view.addSubview(btn)
  • 传统的按钮点击方式
1
2
3
4
5
6
7
8
btn.addTarget(self, action: #selector(btnClick(btn:)), for: UIControl.Event.touchUpInside)

🔽

@objc func btnClick(btn: UIButton) {
print("clickBtn")
// 打印结果:clickBtn
}
  • RxSwift的按钮点击方式
1
2
3
4
5
// RxSwift
btn.rx.tap.subscribe { event in
print("RxSwift btnClick")
// 打印结果:RxSwift btnClick
}.disposed(by: bag)
  1. RxSwift 监听 UITextField 输入框文字变化
  • 创建 UITextField 文本框
1
2
3
4
5
let tf = UITextField(frame: CGRect(x: 100, y: 200, width: 100, height: 50))
// 设置代理
tf.delegate = self
tf.backgroundColor = UIColor.blue
self.view.addSubview(tf)
  • 传统方式:设置代理 tf.delegate = self ,遵循协议 UITextFieldDelegate
1
2
3
4
5
6
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// 打印文字变化结果
print(textField.text as Any)
// 结果:Optional(Optional("好👌?"))
return true
}
  • RxSwift的方式
1
2
3
4
5
6
7
8
9
// RxSwift
tf.rx.text.subscribe { (event: Event<String?>) in
// 将UITextField文字改变的内容打印
print(event.element as Any)
// 打印结果:Optional(Optional("好👌?"))
}.disposed(by: bag)

// 可以将当前文本绑定到label上
// tf.rx.text.bind(to: lb.rx.text).disposed(by: bag)
  1. RxSwift 监听 UILabel 文字/frame等属性改变
  • 创建 UILabel
1
2
3
4
5
let label = UILabel(frame: CGRect(x: 100, y: 300, width: 100, height: 50))
label.text = "my name is zj"
label.textColor = UIColor.red
label.backgroundColor = UIColor.blue
self.view.addSubview(label)
  • RxSwift 监听属性改变
1
2
3
4
5
6
7
8
9
10
11
// RxSwift
// 监听text
label.rx.observe(String.self, "text").subscribe { (str: String?) in
print(str as Any)
// 打印结果:Optional("my name is zj")
}.disposed(by: bag)
// 监听frame
label.rx.observe(CGRect.self, "frame").subscribe { (rect: CGRect?) in
print(rect!)
// 打印结果:(100.0, 300.0, 100.0, 50.0)
}.disposed(by: bag)
  1. 通知
  • 发送通知
1
2
3
// 发送额外的数据
let info = ["name":"zj","age":32] as [String:Any]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ComplexNotification"), object: nil, userInfo: info)
  • 传统方式 接收通知
1
2
3
4
5
6
7
8
// 接收通知
NotificationCenter.default.addObserver(self, selector: #selector(handleNotification(noti:)), name: Notification.Name(rawValue: "ComplexNotification"), object: nil)

// 接收到通知后的回调
@objc func handleNotification(noti: Notification) {
print(noti.userInfo as Any)
// 打印结果:Optional([AnyHashable("age"): 32, AnyHashable("name"): "zj"])
}
  • RxSwift方式 接收通知
1
2
3
4
// RxSwift    NotificationCenter.default.rx.notification(Notification.Name(rawValue: "ComplexNotification")).subscribe { (noti: Notification) in
print(noti.userInfo as Any)
// 打印结果:Optional([AnyHashable("age"): 32, AnyHashable("name"): "zj"])
}.disposed(by: bag)
  1. 监听滚动偏移
1
2
3
scrollView.rx.contentOffset.subscribe { point in
print("滚动偏移:\(point.element!)")
}.disposed(by: bag)
  1. 闭包回调
  • 创建一个网络请求
1
2
let url = URL(string: "https://www.qctt.cn/home")
let urlRequest = URLRequest.init(url: url!)
  • 传统方式实现
1
2
3
4
5
6
7
8
9
10
11
12
// 传统写法
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
guard error != nil else {
print("Data Task Error: \(String(describing: error))")
return
}
guard let data = data else {
print("Data Task Error: unkonwn")
return
}
print("Data Task Success with count: \(data.count)")
}.resume()
  • RxSwift方式实现
1
2
3
4
5
6
7
// RxSwift
URLSession.shared.rx.response(request: urlRequest)
.subscribe { response,data in
print("Data Task Success with count: \(data) \n \(response)")
} onError: { error in
print("Data Task Error: \(String(describing: error))")
}.disposed(by: bag)
  • 打印结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
curl -X GET 
"https://www.qctt.cn/home" -i -v
Success (1177ms): Status 200
Data Task Success with count: 115533 bytes
<NSHTTPURLResponse: 0x283380800> { URL: https://www.qctt.cn/home } { Status Code: 200, Headers {
"Access-Control-Allow-Origin" = (
"*"
);
"Cache-Control" = (
"no-store, no-cache, must-revalidate",
"no-cache"
);
"Content-Encoding" = (
gzip
);
"Content-Type" = (
"text/html; charset=UTF-8"
);
Date = (
"Mon, 27 Mar 2023 04:32:01 GMT"
);
Expires = (
"Thu, 19 Nov 1981 08:52:00 GMT"
);
Pragma = (
"no-cache"
);
Server = (
nginx
);
"Set-Cookie" = (
"PHPSESSID=533eb488d609128045ec4606bd6ed24e; path=/",
"XSRF-TOKEN=eyJpdiI6IjlFYmNraHFcL29lUk1aRTFMNVlsQkJBPT0iLCJ2YWx1ZSI6Ikh1TWlEalwvMHpTeU5tVWM5d0U1SHUyb1wvdExmNWFZdEJNbUVZT1NHUFhOYWdza2VFSUJreDlhTXdsTHM0anFpZGpteXlIMXVtZ1BCT3RBdktxaGE4QkE9PSIsIm1hYyI6ImZhYTQxODk3ZGZmZjBlYWE1MmJkYzUzZjc1NWM2ZWQ2N2IyZTUwODZiYmFmNzY5YjhkNzY5YWE3MmYwYjliNjkifQ%3D%3D; expires=Mon, 27-Mar-2023 06:32:01 GMT; Max-Age=7200; path=/",
"laravel_session=eyJpdiI6ImJnc3ZTNTNlM05RTVRRa0x5b1k0TUE9PSIsInZhbHVlIjoiQ2JOeHdlVDNSb3JvWnBcL0xNVG1PODFVNmgzQUNzUXg0KzhsdTB2bnpCOEpqb0hNVktnaTFyc1JhVHJNMFh1QzEyQmRVcXdSdG9mbnZrNHhyVEVcL3ZyUT09IiwibWFjIjoiNjM0MzMxMTk5NWI1N2U2YWJhMDJjNmY2MzMyNjRlMzc4YmUzODY1NzVhMTUzZWQzNDZlNDI2NWRhOTQ0YjkzMCJ9; expires=Mon, 27-Mar-2023 06:32:01 GMT; Max-Age=7200; path=/; HttpOnly"
);
Vary = (
"Accept-Encoding"
);
"access-control-allow-credentials" = (
true
);
"access-control-allow-headers" = (
"x-requested-with,Authorization"
);
"access-control-allow-methods" = (
"*"
);
} }
  1. 多个任务之间有依赖关系

例如:先通过用户名和密码获取 Token,然后通过 Token 获取用户信息

  • 传统的方式实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class UserInfo {
var name = ""
var age = 0
var sex = 0
}
enum API {
// 通过用户名密码获取一个接口
static func token(username: String, password: String, success: (String) -> Void, failure: (Error) -> Void) {
success("获取token成功")
}
// 通过 token 获取 用户信息
static func userInfo(token: String, success: (String) -> Void, failure: (Error) -> Void) {
success("获取userInfo成功")
}
}

🔽

API.token(username: "zj", password: "pass") { token in
print(token)
API.userInfo(token: token) { userInfo in
print(userInfo)
} failure: { error in
print("获取userInfo失败: \(error)")
}
} failure: { error in
print("获取token失败: \(error)")
}
  1. RxSwift 监听 系统方法
1
2
3
4
// 视图将要出现self.vc?.rx.sentMessage(#selector(HomeVC.viewWillAppear(_:))).subscribe(onNext: { value in
// 手动触发下拉刷新
self.tableV.startHeaderRefreshing(animated: true)
}).disposed(by: bag)
  • Post title:Swift学习20:RxSwift使用
  • Post author:张建
  • Create time:2023-02-07 11:02:45
  • Post link:https://redefine.ohevan.com/2023/02/07/Swift三方框架/Swift学习20:RxSwift使用/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.