RxSwift 使用
原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、函数响应式编程
- 1、函数式
- 2、响应式
- 3、RxSwift
- 二、RxSwift的UI用法
- 1、button点击事件
- 2、textfiled文本响应
- 3、使用RxSwift来控制scrollView滚动效果
- 4、使用RxSwift来控制手势响应
- 5、使用RxSwift来控制通知事件
- 6、使用RxSwift来控制timer定时器
- 7、使用RxSwift来控制网络请求
- 8、使用RxSwift来控制按钮是否可以点击
- 9、使用RxSwift来控制Table视图
- 三、observable可观察序列的创建
- 1、emty方法
- 2、just方法
- 3、of方法
- 4、from方法
- 5、deferred方法
- 6、rang方法
- 7、generate方法
- 8、repeatElement方法
- 9、error方法
- 10、never方法
- 11、create方法
- 四、高阶函数
- 1、使用Driver序列来请求网络
- 2、组合操作符
- 3、映射操作符
- 4、过滤条件操作符
- 5、集合控制操作符
- 6、从可观察对象的错误通知中恢复的操作符
- 7、链接操作符
- 五、既是序列也是观察者的Subject
- 1、PublishSubject
- 2、BehaviorSubject
- 3、ReplaySubject
- 4、AsyncSubject
- 5、BehaviorRelay
- 六、场景序列
- 1、controlEvent
- 2、binder
- 七、综合案例
- 1、用户偏好设置
- 2、Subject实战
- Demo
- 参考文献
一、函数响应式编程
1、函数式
a、定义
函数式编程简称FP
(Functional Programming
),函数式编程就是一种抽象程度很高的编程范式,它将计算机运算看做是数学中函数的计算,而纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
b、特点
允许把函数本身作为参数传入另一个函数,同时还允许返回一个函数。
// f是函数 y是返回值 x是参数
// x参数自身又可以作为其他函数的y值,造成嵌套
// 假如x参数为2,2 = 1+1 = 0+2 可以存在多种变式
函数表达式: y = f(x) ---> x = f(x) ---> y = f(f(x))
c、范例
需求
对于数组 [1,2,3,4,5,6,7,8,9,10]
,首先获取 > 3
的数字,获取到数字之后 + 1
,再输出所有数字中的偶数。
普通写法
代码嵌套层次非常深,可读性和复用性也是非常差的,维护以及代码构建成本太高,这样就成了函数式出现的必要性。
let array = [1,2,3,4,5,6,7]
for num in array
{
if num > 3
{
let number = num + 1
if (number % 2 == 0)
{
print(number)// 输出结果为6和8,是5和7增加1后的数字
}
}
}
函数式写法
array.filter{ $0 > 3 }
.filter{ ($0 + 1) % 2 == 0 }
.forEach{ print($0) }// 输出结果为5和7,即原来的数组中的数字本身
2、响应式
a、定义
对象对某一数据流变化做出响应的这种编码方式称为响应式。例如我们在爱奇艺平台观看视频,我们只需要在某一个时刻订阅了这个视频,后面平台自媒体运营者不断更新视频,我们随着时间也能自定接受推送,这就是响应。再比如小明今年18岁,小凡15岁,小明比小凡大3岁,10年后小明28岁,小凡的年龄也要随着小明的年龄变化而变为25岁,而不是仍然停留在15岁。
在iOS开发中我们经常会响应一些事件button
、tap
、textField
、textView
、notifaction
、KVO
、NSTimer
等等这些,都需要做响应监听,响应后都需要在对应的响应事件中去做处理。在原生开发中,触发对象与响应方法是分离的,如button
的初始化和点击响应方法是分离的,这样导致功能代码与逻辑代码分析需要分开进行容易出错。
b、使用KVO实现响应式范例
输出结果
敌军发送了一条至关重要的情报
报告长官,我部已成功截获敌军信息
Optional([__C.NSKeyValueChangeKey(_rawValue: new): XieJiapei 平民,这个人真实身份是卧底,速速接应, __C.NSKeyValueChangeKey(_rawValue: kind): 1])
❶ 人物身份
-
@objc
告诉编译器使用OC来执行这段代码 -
dynamic
表示使用运行时,因为KVO
在运行时里才能使用
class Person: NSObject
{
@objc dynamic var identity:String = "XieJiapei 平民"
}
❷ 手指点击屏幕更改了人物身份
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
print("敌军发送了一条至关重要的情报")
person.identity = "\(person.identity),这个人真实身份是卧底,速速接应"
}
❸ 使用KVO实现响应式,为人物身份添加观察者观察该值变化
func useKVO()
{
self.person.addObserver(self, forKeyPath: "identity", options: .new, context: nil)
}
❹ KVO中观察到值发生变化时调用的函数
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
print("报告长官,我部已成功截获敌军信息")
print(change as Any)
}
❺ 销毁KVO中的观察者,不再进行观察该值变化
deinit
{
self.removeObserver(self.person, forKeyPath: "identity")
}
3、RxSwift
a、定义
RxSwift
是 ReactiveX
家族的重要一员,ReactiveX
是 Reactive Extensions
的缩写,一般简写为 Rx
。ReactiveX
官方给Rx
的定义是:Rx
是一个使用可观察数据流进行异步编程的编程接口。ReactiveX
不仅仅是一个编程接口,它是一种编程思想的突破,它影响了许多其它的程序库和框架以及编程语言。它拓展了观察者模式,使你能够自由组合多个异步事件,而不需要去关心线程,同步,线程安全,并发数据以及I/O
阻塞。
RxSwift
是 Rx
为 Swift 语言开发的一门函数响应式编程语言, 它可以代替iOS系统的 Target Action
/ 代理 / 闭包 / 通知 / KVO
,同时还提供网络、数据绑定、UI事件处理、UI的展示和更新、多线程等。
RxSwift
最典型的特色就是解决Swift
这门静态语言的响应能力,利用随时间维度序列变化为轴线,用户订阅关心能随轴线一直保活,达到订阅一次,响应一直持续。
RxSwift
需要导入两个框架。RxSwift
框架提供了RX
的核心逻辑,是函数响应式编程的思想所在。RxCocoa
框架使UIKit
框架下的各种控件具有RX
提供的各种功能。
import UIKit
import RxCocoa
import RxSwift
b、使用RxSwift实现响应式范例
- observeWeakly:为该值添加观察者
- subscribe:观察到值发生变化时调用输出最新值
- disposed:将它放到垃圾袋中等下次出门了一起扔掉
self.person.rx.observeWeakly(String.self, "identity")
.subscribe(onNext: { (value) in print(value as Any)})
.disposed(by: disposeBag)
输出结果为
Optional("XieJiapei 平民")
敌军发送了一条至关重要的情报
Optional("XieJiapei 平民,这个人真实身份是卧底,速速接应")
二、RxSwift的UI用法
1、button点击事件
a、传统的添加UI事件的方式
缺点是UI控件的初始化与触发的事件不在同一个位置。
self.button.addTarget(self, action: #selector(didClickButton), for: .touchUpInside)
b、使用RxSwift来控制button响应
![](https://img.haomeiwen.com/i9570900/51ceae82e97e5aab.png)
button.backgroundColor = .black
button.setTitle("Button", for: .normal)
self.button.rx.tap
.subscribe(onNext: { () in print("点击了按钮")})
.disposed(by: disposeBag)
self.view.addSubview(button)
倘若要控制响应事件的触发方式,可以使用以下方式
self.button.rx.controlEvent(.touchUpOutside)
2、textfiled文本响应
a、传统的textfiled文本响应的方式
![](https://img.haomeiwen.com/i9570900/aec793e9e24ece24.png)
self.textFiled.delegate = self
extension ReplacesUIElement: UITextFieldDelegate
{
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
if let num = Int(string)
{
if num % 2 == 1
{
print("打印输入的奇数:\(num)")// 输出1、3、5
}
}
return true
}
}
b、使用RxSwift来控制textfiled文本响应
self.textFiled.rx.text.orEmpty.changed
.subscribe(onNext: { (text) in print("监听到用户输入的文本为:\(text)") })
.disposed(by: disposeBag)
输出的结果为
监听到用户输入的文本为:
监听到用户输入的文本为:1
监听到用户输入的文本为:12
监听到用户输入的文本为:123
监听到用户输入的文本为:1234
监听到用户输入的文本为:12345
监听到用户输入的文本为:123456
将textfiled文本值传递到button作为title
![](https://img.haomeiwen.com/i9570900/a7da624909d767bd.png)
self.textFiled.rx.text
.bind(to: self.button.rx.title())
.disposed(by: disposeBag)
3、使用RxSwift来控制scrollView滚动效果
![](https://img.haomeiwen.com/i9570900/7ca801290941d7b2.gif)
scrollView.backgroundColor = .yellow
scrollView.contentSize = CGSize(width: 200, height: 300 * 4)
self.view.addSubview(scrollView)
scrollView.rx.contentOffset
.subscribe(onNext: { [weak self] (content) in
self?.view.backgroundColor = UIColor.init(red: content.y/255.0*0.8, green: content.y/255.0*0.3, blue: content.y/255.0*0.6, alpha: 1.0)
print(content.y)
})
.disposed(by: disposeBag)
输出结果为:
643.3333333333334
644.6666666666666
648.6666666666666
690.0
......
4、使用RxSwift来控制手势响应
![](https://img.haomeiwen.com/i9570900/a733eaf9bdf1858b.png)
let tap = UITapGestureRecognizer()
self.label.addGestureRecognizer(tap)
self.label.isUserInteractionEnabled = true
self.label.text = "手势"
self.label.backgroundColor = .brown
view.addSubview(self.label)
tap.rx.event
.subscribe(onNext: { tap in print(tap.view!)})
.disposed(by: disposeBag)
输出结果为:
<UILabel: 0x7fa54190e8d0; frame = (130 620; 200 50); text = '手势'; gestureRecognizers = <NSArray: 0x600003779f50>; layer = <_UILabelLayer: 0x600001a3c780>>
5、使用RxSwift来控制通知事件
![](https://img.haomeiwen.com/i9570900/e632b1bbd16c5392.png)
NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
.subscribe(onNext: { notification in print(notification)})
.disposed(by: disposeBag)
输出结果为:
name = UIKeyboardWillShowNotification, object = nil, userInfo = ......
6、使用RxSwift来控制timer定时器
a、传统控制定时器的方式
![](https://img.haomeiwen.com/i9570900/de236538a9863a25.gif)
方案一
NSTimer
是会受runloop
影响的,它的准确性依赖于runloop
的状态。
self.traditionTimer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerFire), userInfo: nil, repeats: true)
// 只会打印一次
self.traditionTimer.fire()
// 会打印多次,但是和滚动视图冲突,在滚动时计时器停止
RunLoop.current.add(self.traditionTimer, forMode: .default)
// 会打印多次,在滚动时计时器继续运行
RunLoop.current.add(self.traditionTimer, forMode: .common)
方案二
// 会打印多次,但是和滚动视图冲突,在滚动时计时器停止
self.traditionTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { (timer) in
print("喝点小酒")
})
方案三
GCD
封装的timer
的准确性是比较高的,经常是追求性能的小伙伴们封装工具类时的一个不错选择。
// 会打印多次,在滚动时计时器继续运行
gcdTimer = DispatchSource.makeTimerSource()
gcdTimer.schedule(deadline: DispatchTime.now(), repeating: DispatchTimeInterval.seconds(1))
gcdTimer.setEventHandler {
print("脑袋晕晕的")
}
gcdTimer.resume()// 开始
gcdTimer.suspend()// 暂停
gcdTimer.cancel()// 取消
gcdTimer = nil// 销毁
方案四
- 修改帧率:如果在特定帧率内无法提供对象的操作,可以通过降低帧率解决.一个拥有持续稳定但是较慢帧率的应用要比跳帧的应用顺滑的多
- preferredFramesPerSecond:用来设置每秒刷新次数,默认值为屏幕最大帧率60
// 会打印多次,但是和滚动视图冲突,在滚动时计时器停止
displayLink = CADisplayLink(target: self, selector: #selector(timerFire))
displayLink.preferredFramesPerSecond = 1
displayLink.add(to: .current, forMode: .default)
displayLink.isPaused = true // 暂停
b、使用RxSwift来控制timer定时器
![](https://img.haomeiwen.com/i9570900/48e26d5ea91aa721.gif)
timer = Observable<Int>.interval(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)// 主线程
timer.subscribe(onNext: { num in print("下雪了❄️")})
.disposed(by: disposeBag)
可以看到RXSwift
的计时器使用的不是NSTimer
,因为当我们拖动滚动视图的时候,计时器并没有停止工作。RxSwift
实现的timer
免去了我们计时器的一些不必要的麻烦,包括runloop
影响、销毁问题、线程问题。
给垃圾袋重新赋值,那么旧的垃圾袋就会被销毁掉,而旧的垃圾袋里面含有计时器,所以计时器也同时被销毁掉了,不再运行。其他停止计时器的方式还包括完成、错误。
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
self.disposeBag = DisposeBag()
}
7、使用RxSwift来控制网络请求
传统的网络请求方式
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()
输出结果为:
href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>©2017 Baidu <a href=http://www.baidu.com/duty/>使用百度前必读</a> <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a> 京ICP证030173号 <img
使用RxSwift来控制网络请求
let url = URL(string: "https://www.baidu.com")
URLSession.shared.rx.response(request: URLRequest(url: url!))
.subscribe(onNext: { (response, data) in print(data)})
.disposed(by: disposeBag)
输出结果为:
curl -X GET
"https://www.baidu.com" -I -v
Success (205ms): Status 200
2443 bytes
8、使用RxSwift来控制按钮是否可以点击
![](https://img.haomeiwen.com/i9570900/89c97d6ccd70f407.png)
a、需求
当用户输入用户名时,如果用户名不足 5个字就给出红色提示语,并且无法输入密码,当用户名符合要求时才可以输入密码。同样当用户输入的密码不到 5 个字时也给出红色提示语。
当用户名和密码有一个不符合要求时底部的绿色按钮不可点击,只有当用户名和密码同时有效时按钮才可点击。当点击绿色按钮后弹出一个提示框。
b、使用RxSwift进行实现
用户名是否有效
let usernameVaild = usernameTextFiled.rx.text.orEmpty
.map { (text) -> Bool in return text.count >= minUsernameLength }
根据用户名是否有效来判断用户名有效提示框是否需要隐藏,密码输入框是否允许输入
usernameVaild.bind(to: usernameValidLabel.rx.isHidden)
.disposed(by: disposeBag)
usernameVaild.bind(to: passwordTextFiled.rx.isEnabled)
.disposed(by: disposeBag)
密码是否有效
let passwordVaild = passwordTextFiled.rx.text.orEmpty
.map { (text) -> Bool in return text.count >= minPasswordLength }
根据密码是否有效来判断密码有效提示框是否需要隐藏
passwordVaild.bind(to: passwordValidLabel.rx.isHidden)
.disposed(by: disposeBag)
密码和用户名同时有效才允许按钮点击
Observable.combineLatest(usernameVaild, passwordVaild){ $0 && $1}
.bind(to: loginBtn.rx.isEnabled)
.disposed(by: disposeBag)
点击按钮打印文本
loginBtn.rx.tap.subscribe(onNext: { () in
print("源代码分析好恶心,头都绕晕了")
}).disposed(by: disposeBag)
9、使用RxSwift来控制Table视图
![](https://img.haomeiwen.com/i9570900/6451427fbe6b2fa6.png)
这是一个过分使用了RxSwift
来完成功能的典型例子,本来一定程度上可以简便我们工作量的RxSwift
,一旦过度使用,代码逻辑就会变得混乱不堪,不懂这个框架的高级内容的人根本无法看懂你在表达什么,只会觉得一头雾水,想揍扁你的冲动都有了,反正我是这样觉得的。这东西好处就是简便,坏处就是非常绕,无论是使用方式还是源码分析。
a、正在运行
let isRunning = Observable
.merge([playButton.rx.tap.map({ return true }), stopButton.rx.tap.map({ return false })])
.startWith(false)
.share(replay: 1, scope: .whileConnected)
打印是否正在运行
isRunning
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
正在运行序列和停止按钮是否能够点击进行绑定
isRunning
.bind(to: stopButton.rx.isEnabled)
.disposed(by: disposeBag)
b、没有运行
let isNotRunning = isRunning
.map({ running -> Bool in
print(running)
return !running })
.share(replay: 1, scope: .whileConnected)
将没有运行和运行按钮能够点击事件绑定,和割裂按钮隐藏事件绑定
isNotRunning
.bind(to: splitUpButton.rx.isHidden)
.disposed(by: disposeBag)
isNotRunning
.bind(to: playButton.rx.isEnabled)
.disposed(by: disposeBag)
c、计时器
创建计时器
timer = Observable<Int>
.interval(DispatchTimeInterval.milliseconds(100), scheduler: MainScheduler.instance)
.withLatestFrom(isRunning, resultSelector: {_, running in running})
.filter({running in running})
.scan(0, accumulator: {(acc, _) in
return acc+1
})
.startWith(0)
.share(replay: 1, scope: .whileConnected)
打印计时器中的时间
timer
.subscribe { (milliseconds) in print("\(milliseconds)00ms") }
.disposed(by: disposeBag)
将格式化后的日期展示到Label
上
timer.map(stringFromTimeInterval)
.bind(to: timeLabel.rx.text)
.disposed(by: disposeBag)
d、割开序列
以计时器的当前时间为准割开作为片段
let lapsSequence = timer
.sample(splitUpButton.rx.tap)
.map(stringFromTimeInterval)
.scan([String](), accumulator: { lapTimes, newTime in
return lapTimes + [newTime]
})
.share(replay: 1, scope: .whileConnected)
将割开的片段展示到Table
中
lapsSequence
.bind(to: tableView.rx.items (cellIdentifier: "Cell", cellType: UITableViewCell.self))
{ (row, element, cell) in
cell.textLabel?.text = "\(row+1)) \(element)"
}
.disposed(by: disposeBag)
设置tableview
的delegate
tableView
.rx.setDelegate(self)
.disposed(by: disposeBag)
更新Tableview
的headview
里面的计数
lapsSequence.map({ laps -> String in
return "\t\(laps.count) laps"
})
.startWith("\tno laps")
.bind(to: countLable.rx.text)
.disposed(by: disposeBag)
三、observable可观察序列的创建
1、emty方法
let emptyObservable = Observable<Int>.empty()
_ = emptyObservable.subscribe(onNext: { (number) in
print("订阅:",number)
}, onError: { (error) in
print("错误:",error)
}, onCompleted: {
print("完成")
}) {
print("释放回调")
}
输出结果为:
完成
释放回调
本来序列事件是Int
类型的,但是这里调用emty
函数返回空序列,所以直接进入complete
。
2、just方法
我们显示地标注出了Observable
的类型为Observable<[String]>
,即指定了这个 Observable
所发出的事件携带的数据类型必须是String
类型的。该方法通过传入一个默认值来初始化单个信号序列,构建一个只有一个元素的Observable
队列,订阅完信息自动complete
。
方式一
let array = ["天地玄黄","宇宙洪荒"]
Observable<[String]>.just(array)
.subscribe { (number) in print(number)}
.disposed(by: disposeBag)
输出结果为:
next(["天地玄黄", "宇宙洪荒"])
completed
方式二
_ = Observable<[String]>.just(array).subscribe(onNext: { (number) in
print("订阅:",number)
}, onError: { (error) in
print("错误:",error)
}, onCompleted: {
print("完成回调")
}) {
print("释放回调")
}
输出结果为:
订阅: ["天地玄黄", "宇宙洪荒"]
完成回调
释放回调
3、of方法
可以接受同类型的数量可变的参数,无论字典、数组还是多个元素都可以正常使用。
多个元素
Observable<String>.of("百尺竿头","更进一步")
.subscribe { (event) in print(event) }
.disposed(by: disposeBag)
输出结果为:
next(百尺竿头)
next(更进一步)
completed
字典
Observable<[String: Any]>.of(["name":"谢佳培","age":23])
.subscribe { (event) in
print(event)
}.disposed(by: disposeBag)
输出结果为:
next(["age": 23, "name": "谢佳培"])
completed
数组
Observable<[String]>.of(["瑞幸咖啡","神州优车集团"])
.subscribe { (event) in print(event)}
.disposed(by: disposeBag)
输出结果为:
next(["瑞幸咖啡", "神州优车集团"])
completed
4、from方法
当我们从数组、集合中获取序列的时候需要对可选项进行处理,该序列会将可选序列转换为可观察序列,更加安全。
func from()
{
Observable<[String]>.from(optional: ["华东师范大学","厦门大学","华侨大学"])
.subscribe { (event) in print(event) }
.disposed(by: disposeBag)
}
输出结果为:
next(["华东师范大学", "厦门大学", "华侨大学"])
completed
5、deferred方法
使用deferred()
方法延迟Observable
序列的初始化。通过传入的block
来实现根据外界的条件返回不同的Observable
序列的初始化。
let isOdd = true// 是否是奇数
_ = Observable<Int>.deferred
{ () -> Observable<Int> in
if isOdd
{
return Observable.of(1,3,5,7,9)
}
else
{
return Observable.of(0,2,4,6,8)
}
}
.subscribe { (event) in print(event) }
输出结果为:
next(1)
next(3)
next(5)
next(7)
next(9)
completed
6、rang方法
生成指定范围的可观察序列。
Observable.range(start: 2, count: 5)
.subscribe { (event) in print(event) }
.disposed(by: disposeBag)
输出结果为:
next(2)
next(3)
next(4)
next(5)
next(6)
completed
7、generate方法
整数累加
Observable.generate(initialState: 0,// 初始值
condition: { $0 < 10}, // 条件 直到小于10
iterate: { $0 + 2 }) // 迭代 每次+2
.subscribe { (event) in print(event) }
.disposed(by: disposeBag)
输出结果为:
next(0)
next(2)
next(4)
next(6)
next(8)
completed
数组遍历
let array = ["女孩1","女孩2","女孩3","女孩4","女孩5"]
Observable.generate(initialState: 0,
condition: { $0 < array.count},
iterate: { $0 + 1 })
.subscribe(onNext: { print("遍历数组结果:",array[$0])})
.disposed(by: disposeBag)
输出结果为:
遍历数组结果: 女孩1
遍历数组结果: 女孩2
遍历数组结果: 女孩3
遍历数组结果: 女孩4
遍历数组结果: 女孩5
8、repeatElement方法
生成无限次重复给定元素的可观察序列。
Observable<Int>.repeatElement(5)
.subscribe { (event) in print("酒量:",event)}
.disposed(by: disposeBag)
输出结果为:
酒量: next(5)
酒量: next(5)
酒量: next(5)
....无限次
9、error方法
返回一个以错误信息结束的可观察序列,比如请求网络失败时候发送的失败信号。
Observable<String>.error(NSError.init(domain: "error", code: 1997, userInfo: ["reason":"薪资太低"]))
.subscribe { (event) in print("订阅:",event) }
.disposed(by: disposeBag)
输出结果为:
订阅: error(Error Domain=error Code=1997 "(null)" UserInfo={reason=薪资太低})
10、never方法
永远不会发出事件也不会终止的可观察序列。
Observable<String>.never()
.subscribe { (event) in print("兔子🐰,我保证永远都不变心,可以把屠龙宝刀放下了吗?",event) }
.disposed(by: disposeBag)
无输出结果。
11、create方法
该方法接受一个闭包形式的参数用来对每一个订阅者发送消息,就好比用户订阅了微信公众号成了你的粉丝,你就要对他/她负责,每天发送优质内容给他/她阅读。
// 1.创建序列
let observable = Observable<String>.create
{ observer in
// 3.发送消息
// 对订阅者发出.next事件,且携带了一个数据"这是一篇富有..."
observer.onNext("这是一篇富有启发性的有趣文章,希望你们喜欢")
// 对订阅者发出.completed事件
observer.onCompleted()
// 因为一个订阅行为会有一个Disposable类型的返回值,所以在结尾一定要返回一个Disposable
return Disposables.create()
}
// 2.订阅消息
_ = observable.subscribe{ print("粉丝阅读了公众号(漫游在云海的鲸鱼)发来的消息:\($0)") }
输出结果为:
粉丝阅读了公众号(漫游在云海的鲸鱼)发来的消息:next(这是一篇富有启发性的有趣文章,希望你们喜欢)
粉丝阅读了公众号(漫游在云海的鲸鱼)发来的消息:completed
四、高阶函数
1、使用Driver序列来请求网络
![](https://img.haomeiwen.com/i9570900/578958ba6e5517e7.png)
a、直接使用Observable序列进行网络请求
❶ 生成序列中的序列
将textFiled
序列中的text
通过flatMap
映射,得到调用dealwithData:
方法返回的text
序列,最终得到的result
指的是text
序列,而不是textFiled
序列。
let result = textFiled.rx.text.skip(1)
.flatMap
{ [weak self](input) -> Observable<Any> in
return (self?.dealwithData(inputText:input ?? ""))!
}
❷ 将text转化为序列的函数
在这个函数中异步进行了网络请求,如果请求成功则将在textFiled
中输入的text
转变为"已经输入:\(inputText)"
,请求失败则返回错误信息,两种情况下都是作为创建的新序列返回。
func dealwithData(inputText:String)-> Observable<Any>
{
print("开始请求网络了")
// 创建了新序列
return Observable<Any>.create (
{ (observable) -> Disposable in
// 输入12345则请求网络失败
if inputText == "12345"
{
observable.onError(NSError.init(domain: "com.xiejiapei.cn", code: 1997, userInfo: nil))
}
// 异步请求网络
DispatchQueue.global().async
{
print("请求网络时候所处的线程为: \(Thread.current)")
observable.onNext("已经输入\(inputText)")
observable.onCompleted()
}
return Disposables.create()
})
}
❸ 订阅了两次同样的信号
result
.subscribe(onNext: { (element) in print("订阅到的内容为:\(element)")})
.disposed(by: disposeBag)
result
.subscribe(onNext: { (element) in print("订阅到的内容为:\(element),获得订阅内容时所在的线程为:\(Thread.current)")})
.disposed(by: disposeBag)
❹ 输出正常的结果
可以看到由于我们对于同一信号订阅了两次,因此请求了两次网络,获得的却是同样的信息,这样就浪费了流量来进行不必要的重复请求。还有一个问题就是我们获取到订阅内容时候所处的线程为子线程,而不是能够更新UI的主线程,假如我们此时更新了UI就会出错。
开始请求网络了
开始请求网络了
请求网络时候所处的线程为: <NSThread: 0x600000202a80>{number = 6, name = (null)}
订阅到的内容为:已经输入1234
请求网络时候所处的线程为: <NSThread: 0x600000202a80>{number = 6, name = (null)}
订阅到的内容为:已经输入1234,获得订阅内容时所在的线程为:<NSThread: 0x600000202a80>{number = 6, name = (null)}
❺ 输出错误信息
按照常理来说,假如网络请求失败了,我们不应该将错误信息发送到订阅内容里,因为订阅内容是用于UI显示的,是直接拿到内容展示给用户观看的,而将error
里面的错误信息直接展示给用户的话会让他暴跳如雷的,揍你的冲动都有了,所以我们需要针对错误信息进行处理,展示给用户他能够理解的提示框,比如网络出错了,请稍后重试等。
开始请求网络了
Unhandled error happened: Error Domain=com.xiejiapei.cn Code=1997 "(null)"
开始请求网络了
Unhandled error happened: Error Domain=com.xiejiapei.cn Code=1997 "(null)"
请求网络时候所处的线程为: <NSThread: 0x60000026dc40>{number = 9, name = (null)}
请求网络时候所处的线程为: <NSThread: 0x60000026dc40>{number = 9, name = (null)}
b、解决使用Observable序列进行网络请求时遇到的问题
❶ 添加share解决重复请求问题
let result = textFiled.rx.text.skip(1)
.flatMap
{ [weak self](input) -> Observable<Any> in
return (self?.dealwithData(inputText:input ?? ""))!
}
.share(replay: 1, scope: .whileConnected)
输出结果为:
开始请求网络了
请求网络时候所处的线程为: <NSThread: 0x60000324fbc0>{number = 3, name = (null)}
订阅到的内容为:已经输入1234
订阅到的内容为:已经输入1234,获得订阅内容时所在的线程为:<NSThread: 0x60000324fbc0>{number = 3, name = (null)}
这样对于同样一个信号就只请求一次了。
❷ 添加MainScheduler解决子线程更新UI问题
let result = textFiled.rx.text.skip(1)
.flatMap
{ [weak self](input) -> Observable<Any> in
return (self?.dealwithData(inputText:input ?? ""))!
.observe(on: MainScheduler())
}
.share(replay: 1, scope: .whileConnected)
输出结果为:
开始请求网络了
请求网络时候所处的线程为: <NSThread: 0x6000002df080>{number = 6, name = (null)}
订阅到的内容为:已经输入1234
订阅到的内容为:已经输入1234,获得订阅内容时所在的线程为:<NSThread: 0x6000002e0280>{number = 1, name = main}
可以看到获得订阅内容时所在的线程已经是主线程了,这样就可以放心更新UI内容了。
❸ 添加catchAndReturn解决错误事件
let result = textFiled.rx.text.skip(1)
.flatMap
{ [weak self](input) -> Observable<Any> in
return (self?.dealwithData(inputText:input ?? ""))!
.observe(on: MainScheduler())
.catchAndReturn("检测到了错误事件")
}
.share(replay: 1, scope: .whileConnected)
输出结果为:
开始请求网络了
订阅到的内容为:检测到了错误事件
订阅到的内容为:检测到了错误事件,获得订阅内容时所在的线程为:<NSThread: 0x600001cdcb00>{number = 1, name = main}
请求网络时候所处的线程为: <NSThread: 0x600001c9a040>{number = 5, name = (null)}
这样当网络请求失败的时候,订阅到的内容就不再是给编程人员使用的error
信息,而是可以直接展示给用户,让用户能够理解的提示信息了。
c、使用Driver序列来请求网络
使用Observable
序列进行网络请求显得较为繁琐,多了编程人员不关心的线程切换、错误提示的处理、重复网络请求等问题,所以可以使用更为简便的Driver
序列来请求网络。
❶ 创建序列
let result = textFiled.rx.text.orEmpty
.asDriver()
.flatMap
{
return self
.dealwithData(inputText: $0)
.asDriver(onErrorJustReturn: "检测到了错误事件")
}
❷ 订阅信号
由于drive
不支持Any
类型,所以使用map
进行了类型转化。
result
.map { "订阅到的内容的文本长度为: \(($0 as! String).count)"}
.drive(self.label.rx.text)
result
.map { "订阅到的内容为:\($0 as! String)"}
.drive(self.button.rx.title())
❸ 输出结果为
Driver
序列自动帮我们解决了线程切换、错误提示的处理、重复网络请求等问题。
开始请求网络了
请求网络时候所处的线程为: <NSThread: 0x60000259b4c0>{number = 9, name = (null)}
![](https://img.haomeiwen.com/i9570900/d460ca0f135b9abd.png)
2、组合操作符
a、startWith
在开始从可观察源发出元素之前,发出指定的元素序列。
Observable.of("1", "2", "3", "4")
.startWith("A")
.startWith("B")
.startWith("C", "a", "b")
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
输出结果为:
CabBA1234
b、merge
将源可观察序列中的元素组合成一个新的可观察序列。
let subject1 = PublishSubject<String>()
let subject2 = PublishSubject<String>()
// 合并subject1和subject2
Observable.of(subject1, subject2)
.merge()
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
subject1.onNext("好")
subject1.onNext("想")
subject2.onNext("睡")
subject2.onNext("觉")
subject1.onNext("呀")
subject2.onNext("!")
输出结果结果为:
好
想
睡
觉
呀
!
c、zip
将多个源可观测序列组合成一个新的可观测序列,只有当多个序列同时有值的时候才会响应,否则只是存值。
let stringSubject = PublishSubject<String>()
let intSubject = PublishSubject<Int>()
Observable
.zip(stringSubject, intSubject)
{ stringElement, intElement in
"\(stringElement) \(intElement)"
}
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
// 在这里存储了内容,但是并不会响应,除非另一个也响应了
stringSubject.onNext("天王盖地虎")
stringSubject.onNext("宝塔镇河妖")
intSubject.onNext(1)
intSubject.onNext(2)
stringSubject.onNext("我爱米老鼠") // 再存一个内容
intSubject.onNext(3)
输出结果为:
天王盖地虎 1
宝塔镇河妖 2
我爱米老鼠 3
d、combineLatest
这个函数应用非常频繁,比如需要账户和密码同时满足才能进行登陆。
let stringSubject = PublishSubject<String>()
let intSubject = PublishSubject<Int>()
Observable.combineLatest(stringSubject, intSubject)
{ stringElement, intElement in
"\(stringElement) \(intElement)"
}
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
// 存一个内容
stringSubject.onNext("我好笨啊,脑袋总是昏昏沉沉的")
// 再存一个内容的话会直接覆盖掉上一个内容,这点和zip不一样
stringSubject.onNext("羡慕别人思维逻辑那么清晰")
// 第一次响应
intSubject.onNext(1)
// 用2覆盖前面int的值1,再进行第二次响应
intSubject.onNext(2)
// 覆盖前面的内容再次响应
stringSubject.onNext("我要是也能聪明一点就好了")
输出结果为:
羡慕别人思维逻辑那么清晰 1
羡慕别人思维逻辑那么清晰 2
我要是也能聪明一点就好了 2
e、switchLatest
就像Github
上面的branch
分支一样,切换到哪个分支,就只对当前分支起作用。
let switchLatestSubject1 = BehaviorSubject(value: "普天之下")
let switchLatestSubject2 = BehaviorSubject(value: "1")
// 选择了 switchLatestSubject1 就不会监听 switchLatestSubject2
let switchLatestSubject = BehaviorSubject(value: switchLatestSubject1)
switchLatestSubject.asObservable()
.switchLatest()
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
switchLatestSubject1.onNext("莫非王土")
switchLatestSubject1.onNext("率土之滨")
// 2-3都会不会监听,但是会保存,由2覆盖1,再由3覆盖2
switchLatestSubject2.onNext("2")
switchLatestSubject2.onNext("3")
// 切换到 switchLatestSubject2
switchLatestSubject.onNext(switchLatestSubject2)
// 不会再输出莫非王臣
switchLatestSubject1.onNext("莫非王臣")
switchLatestSubject2.onNext("4")
输出结果为:
普天之下
莫非王土
率土之滨
3
4
3、映射操作符
a、map
返回转换后的元素组成的新的可观察序列。
let observable = Observable.of(1,2,3,4)
observable.map
{ (number) -> Int in
return number+2
}
.subscribe
{
print("\($0)")
}
.disposed(by: disposeBag)
输出结果为:
next(3)
next(4)
next(5)
next(6)
completed
b、flatMap
针对序列中序列:比如网络序列中还有模型序列。
let boy = Player(score: 90)
let girl = Player(score: 100)
let player = BehaviorSubject(value: boy)
// player本身就是序列,这里再将player的score属性也转化为了序列
player.asObservable()
.flatMap { $0.score.asObservable() }
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
boy.score.onNext(60)
girl.score.onNext(80)
// 切换到女孩
player.onNext(girl)
boy.score.onNext(50)
girl.score.onNext(70)
输出结果为:
90
60
80
50
70
c、scan
从初始就带有一个默认值开始,然后对可观察序列发出的每个元素应用累加器闭包,并以单个元素可观察序列的形式返回每个中间结果。
Observable.of(10, 100, 1000)
.scan(2)
{ aggregateValue, newValue in
aggregateValue + newValue // 10 + 2 , 100 + 10 + 2 , 1000 + 100 + 2
}
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
输出结果为:
Observable.of(10, 100, 1000)
.scan(2)
{ aggregateValue, newValue in
aggregateValue + newValue // 10 + 2 , 100 + 10 + 2 , 1000 + 100 + 2
}
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
4、过滤条件操作符
a、filter
仅从满足指定条件的可观察序列中发出那些元素。
Observable.of(1,2,3,4,5,6,7,8,9,0)
.filter { $0 % 2 == 0 }
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
输出结果为:
2
4
6
8
0
b、distinctUntilChanged
剔除重复元素。
Observable.of("1", "2", "2", "2", "3", "3", "4")
.distinctUntilChanged()
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
输出结果为:
1
2
3
4
c、elementAt
只发出指定位置的元素的序列。
Observable.of("我", "好", "困", "惑", "呀")
.element(at: 3)
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
输出结果为:
惑
d、single
只发出可观察序列中的第一个元素(或满足条件的第一个元素)。
Observable.of("棕色的长毛衣", "蓝色的牛仔裤", "黑色的皮靴")
.single()
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
Observable.of("棕色的长毛衣", "女朋友")
.single { $0 == "女朋友" }
.subscribe { print($0) }
.disposed(by: disposeBag)
输出结果为:
棕色的长毛衣
next(女朋友)
completed
e、take
上面signal
只有一个序列,在实际开发会受到局限,这里引出的take
达到想几个就几个的效果。
Observable.of("好多呀", "学不完","想去玩", "天都黑了")
.take(2)
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
输出结果为:
好多呀
学不完
f、takeLast
仅从可观察序列的末尾发出指定数量的元素。
Observable.of("做框架的大哥", "你不累吗","我学得都疲惫了", "你创造的时候不嫌麻烦吗")
.takeLast(2)
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
输出结果为:
我学得都疲惫了
你创造的时候不嫌麻烦吗
g、takeWhile
当指定条件为true
的时候才发出元素。
Observable.of(1, 2, 3, 4, 5, 6)
.take(while: { (number) -> Bool in
return number < 3
})
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
输出结果为:
1
2
h、takeUntil
应用非常频繁,比如页面销毁了就不能获取值了。
let sourceSequence = PublishSubject<String>()
let referenceSequence = PublishSubject<String>()
sourceSequence
.take(until: referenceSequence)// 停止条件
.subscribe { print($0) }
.disposed(by: disposeBag)
sourceSequence.onNext("可乐")
sourceSequence.onNext("果粒橙")
sourceSequence.onNext("雪碧")
// 条件一出来,下面就不执行了
referenceSequence.onNext("芬达")
sourceSequence.onNext("咖啡")
sourceSequence.onNext("奶茶")
sourceSequence.onNext("果冻")
输出结果为:
next(可乐)
next(果粒橙)
next(雪碧)
completed
I、skip
每次看教材习题答案的时候最害怕看到的两个字:略过。
Observable.of(1, 2, 3, 4, 5, 6)
.skip(2)
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
输出结果为:
3
4
5
6
J、skipUntil
let sourceSeq = PublishSubject<String>()
let referenceSeq = PublishSubject<String>()
sourceSeq
.skip(until: referenceSeq)
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
// 没有满足条件,下面语句不会执行
sourceSeq.onNext("此刻你最想说什么")
sourceSeq.onNext("我有一句妈卖批")
sourceSeq.onNext("不知当讲不当讲")
// 条件一出来,就可以正常执行了
referenceSeq.onNext("条件")
sourceSeq.onNext("来个人")
sourceSeq.onNext("把作者给我拖出去")
sourceSeq.onNext("五花大绑 五马分尸 五雷轰顶")
输出结果为:
来个人
把作者给我拖出去
五花大绑 五马分尸 五雷轰顶
5、集合控制操作符
a、toArray
将一个可观察序列转换为一个数组。
Observable.range(start: 1, count: 10)
.toArray()
.subscribe { print($0) }
.disposed(by: disposeBag)
输出结果为:
success([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
b、reduce
从一个设置的初始化值开始,然后对一个可观察序列发出的所有元素应用累加器闭包。
Observable.of(10, 100, 1000)
.reduce(1, accumulator: +) // 1 + 10 + 100 + 1000 = 1111
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
输出结果为:
1111
c、concat
以顺序方式连接元素。
let subject1 = BehaviorSubject(value: "瑞幸咖啡/神州优车集团")
let subject2 = BehaviorSubject(value: "挺感谢它的")
let subjectsSubject = BehaviorSubject(value: subject1)
subjectsSubject.asObservable()
.concat()
.subscribe { print($0) }
.disposed(by: disposeBag)
subject1.onNext("留不住我")
subject1.onNext("不过还是")
subjectsSubject.onNext(subject2)
// 必须要等subject1完成了才能订阅到subject2,常用来控制执行顺序
subject1.onCompleted()
subject2.onNext("在我毕业的时候收留了即将流落街头的我")
subject2.onNext("不仅给我了一口饱饭还提供了一个温暖的私人房间")
subject2.onNext("工作的环境也挺好的")
输出结果为:
next(瑞幸咖啡/神州优车集团)
next(留不住我)
next(不过还是)
next(挺感谢它的)
next(在我毕业的时候收留了即将流落街头的我)
next(不仅给我了一口饱饭还提供了一个温暖的私人房间)
next(工作的环境也挺好的)
6、从可观察对象的错误通知中恢复的操作符
a、catchAndReturn
发送错误序列会返回预设的错误提示。
let sequenceThatFails = PublishSubject<String>()
sequenceThatFails
.catchAndReturn("对不起,大脑死机了")
.subscribe { print($0) }
.disposed(by: disposeBag)
// 正常序列可以发送成功
sequenceThatFails.onNext("心情低落")
sequenceThatFails.onNext("没人关心")
// 发送错误序列会返回预设的错误提示
sequenceThatFails.onError(self.error)
输出结果为:
next(心情低落)
next(没人关心)
next(对不起,大脑死机了)
completed
b、catchError
通过切换到提供的恢复可观察序列,从错误事件中恢复。
let recoverySequence = PublishSubject<String>()
let sequenceThatFails = PublishSubject<String>()
sequenceThatFails
.catch
{
// 获取到了错误序列后,在这里的闭包中处理完毕
print("错误信息:", $0)
// 返回给用户需要的序列(比如showAlert)
return recoverySequence
}
.subscribe { print($0) }
.disposed(by: disposeBag)
// 发送正常序列
sequenceThatFails.onNext("第二季一拳超人")
sequenceThatFails.onNext("一拳消灭了蜈蚣长老")
// 发送错误序列
sequenceThatFails.onError(error)
recoverySequence.onNext("太厉害,又太扯淡了")
输出结果为:
next(第二季一拳超人)
next(一拳消灭了蜈蚣长老)
错误信息: Error Domain=com.xiejiapei.cn Code=1997 "(null)"
next(太厉害,又太扯淡了)
c、retry
通过无限地重新订阅可观察序列来恢复重复的错误事件。
var count = 1// 重复条件
let sequenceRetryErrors = Observable<String>.create
{ observer in
observer.onNext("蝴蝶效应")
observer.onNext("恐怖游轮")
observer.onNext("土拨鼠之日")
if count < 3 // 结束条件
{
print("错误序列来了")
// 接收到了错误序列,触发重试序列
observer.onError(self.error)
count += 1
}
observer.onNext("明日边缘")
observer.onNext("时空旅行者的妻子")
observer.onNext("永无止境")
observer.onCompleted()
return Disposables.create()
}
sequenceRetryErrors
.retry()
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
输出结果为:
蝴蝶效应
恐怖游轮
土拨鼠之日
错误序列来了
蝴蝶效应
恐怖游轮
土拨鼠之日
错误序列来了
蝴蝶效应
恐怖游轮
土拨鼠之日
明日边缘
时空旅行者的妻子
永无止境
上面是通过自己手动设置了结束条件count < 3
来控制重复次数,也可以在.retry(3)
里直接设置重试次数。
d、debug
打印所有订阅、事件和处理。
sequenceRetryErrors
.retry()
.debug()
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
输出结果为:
恐怖游轮
2021-01-18 11:12:04.027: HigherOrderFunction.swift:558 (retry()) -> Event next(土拨鼠之日)
土拨鼠之日
错误序列来了
2021-01-18 11:12:04.027: HigherOrderFunction.swift:558 (retry()) -> Event next(蝴蝶效应)
......
7、链接操作符
connect
会同时向多个订阅发送同一消息。
let subject = PublishSubject<Any>()
subject.subscribe { print("00:\($0)") }
.disposed(by: disposeBag)
let networkObservable = Observable<Any>.create
{ (observer) -> Disposable in
sleep(2)// 模拟网络延迟
print("我开始请求网络了")
observer.onNext("请求到的网络数据")
observer.onNext("请求到的本地")
observer.onCompleted()
return Disposables.create
{
print("销毁回调了")
}
}.publish()
networkObservable.subscribe(onNext:
{ (anything) in
print("订阅1:",anything)
})
.disposed(by: disposeBag)
networkObservable.subscribe(onNext:
{ (anything) in
print("订阅2:",anything)
})
.disposed(by: disposeBag)
_ = networkObservable.connect()
输出结果为:
我开始请求网络了
订阅1: 请求到的网络数据
订阅2: 请求到的网络数据
订阅1: 请求到的本地
订阅2: 请求到的本地
销毁回调了
五、既是序列也是观察者的Subject
1、PublishSubject
订阅之前的消息都会被忽略掉,比如我们订阅了微信公众号后,就只会接收到它之后发送的日更消息。
// 1:初始化序列
let publishSubject = PublishSubject<Int>()
// 2:发送信号(这里信号就是响应序列)
// 订阅之前的消息都会被忽略掉,所以不会输出发送的1
publishSubject.onNext(1)
// 3:订阅信号
publishSubject.subscribe { print("订阅到了:",$0)}
.disposed(by: disposeBag)
// 4:再次发送信号(响应序列)
publishSubject.onNext(2)
publishSubject.onNext(3)
输出结果为:
订阅到了: next(2)
订阅到了: next(3)
- 很明显
publishSubject
能够订阅信号(这是序列最基本的能力) - 能够发送信号(响应序列)(这又是观察者的能力)
2、BehaviorSubject
通过一个默认初始值来创建,当订阅者订阅BehaviorSubject
时,会收到订阅后Subject
上一个发出的Event
,如果还没有收到任何数据,会发出一个默认值。之后就和PublishSubject
一样,正常接收新的事件。和PublishSubject
稍微不同就是BehaviorSubject
这个家伙有个存储功能:存储上一次的信号。
// 1:创建序列
// 当没有信号的时候,会默认发送信号:100
let behaviorSubject = BehaviorSubject.init(value: 100)
// 2:发送信号
// 还没有订阅信号,这里只是保存
// 保存的时候后面的信号会覆盖掉前面的,导致前面的信号不再发送
behaviorSubject.onNext(2)
behaviorSubject.onNext(3)
// 3:订阅信号
behaviorSubject.subscribe{ print("过年 订阅到了:",$0)}
.disposed(by: disposeBag)
// 再次发送
behaviorSubject.onNext(4)
behaviorSubject.onNext(5)
// 再次订阅,此时订阅到的信号是最后一次发出的信号
behaviorSubject.subscribe{ print("回家 订阅到了:",$0)}
.disposed(by: disposeBag)
输出结果为:
过年 订阅到了: next(3)
过年 订阅到了: next(4)
过年 订阅到了: next(5)
回家 订阅到了: next(5)
3、ReplaySubject
开辟了一个bufferSize
空间,可以指定保留信号的个数。
// 1:创建序列
// 2表示能够保存订阅序列之前的两个信号
let replaySubject = ReplaySubject<Int>.create(bufferSize: 2)
// 2:发送信号
replaySubject.onNext(1)
replaySubject.onNext(2)
replaySubject.onNext(3)
replaySubject.onNext(4)
// 3:订阅信号
replaySubject.subscribe{ print("订阅到了:",$0)}
.disposed(by: disposeBag)
// 再次发送
replaySubject.onNext(7)
replaySubject.onNext(8)
replaySubject.onNext(9)
输出结果为:
订阅到了: next(3)
订阅到了: next(4)
订阅到了: next(7)
订阅到了: next(8)
订阅到了: next(9)
4、AsyncSubject
只发送由源Observable
发送的最后一个事件。
// 1:创建序列
let asyncSubject = AsyncSubject<Int>.init()
// 2:发送信号
asyncSubject.onNext(1)
asyncSubject.onNext(2)
// 3:订阅信号
asyncSubject.subscribe{ print("订阅到了:",$0)}
.disposed(by: disposeBag)
// 再次发送
asyncSubject.onNext(3)
asyncSubject.onNext(4)
//asyncSubject.onError(NSError.init(domain: "大学压迫", code: 10086, userInfo: nil))
asyncSubject.onCompleted()
输出结果为:
订阅到了: next(4)
订阅到了: completed
5、BehaviorRelay
可以避免再写繁琐的闭包。
let behaviorRelay = BehaviorRelay(value: 100)
print("不写闭包了:\(behaviorRelay.value)")
behaviorRelay.accept(1000)
print("天妒英才:\(behaviorRelay.value)")
输出结果为:
不写闭包了:100
不写闭包了:1000
六、场景序列
这个小节介绍平时开发经常运用的序列,这些序列都是由普通序列封装而来,但是能够简易流程。
1、controlEvent
- 专门用于描述 UI 控件所产生的事件
- 不会产生
error
事件 - 一定在
MainScheduler
(主线程)订阅和监听
let controlEventObservable = self.button.rx.controlEvent(.touchUpInside)
controlEventObservable
.subscribe
{ (reslut) in
print("订阅:\(reslut) \n \(Thread.current)")
}
.disposed(by: disposeBag)
输出结果为:
订阅:next(())
<NSThread: 0x600003cdc9c0>{number = 1, name = main}
2、binder
![](https://img.haomeiwen.com/i9570900/91cb592ec3c44632.png)
- 不会处理错误事件,一旦产生错误事件,在调试环境下将执行
fatalError
,在发布环境下将打印错误信息 - 确保绑定都是在给定
Scheduler
上执行(默认MainScheduler
) - 针对UI控件封装的各种
binder
,使用起来非常便利
// 将文本框的输入文本和Lable显示的文本绑定
self.textFiled.rx.text
.bind(to: self.label.rx.text)
.disposed(by: disposeBag)
七、综合案例
1、用户偏好设置
a、必选项:选择生日
![](https://img.haomeiwen.com/i9570900/1c662130f1f2a22d.png)
必选项:出生日期不能早于今天否则边框变色。
let birthdayObservable = birthdayPicker.rx.date.map
{
DataPickerValidator.isValidDate(date: $0)
}
birthdayObservable.map { $0 ? UIColor.orange : UIColor.blue}
.subscribe(onNext: { color in self.birthdayPicker.layer.borderColor = color.cgColor} )
.disposed(by: disposeBag)
b、必选项:选择性别
![](https://img.haomeiwen.com/i9570900/1b0aea2e38b3246c.png)
❶ 创建性别选择序列
let genderSelectObservable = BehaviorSubject<Gender>(value: .notSelcted)
❷ 男生按钮和性别选择序列进行绑定
maleButton.rx.tap
.map { Gender.male }
.bind(to: genderSelectObservable)
.disposed(by: disposeBag)
❸ 女生按钮和性别选择序列进行绑定
femaleButton.rx.tap
.map { Gender.female }
.bind(to: genderSelectObservable)
.disposed(by: disposeBag)
❹ 根据选择的性别更改显示的图片
genderSelectObservable.subscribe(onNext: { (gender) in
switch gender
{
case .male:
self.maleButton.setImage(UIImage(named: "check"), for: .normal)
self.femaleButton.setImage(UIImage(named: "uncheck"), for: .normal)
case .female:
self.femaleButton.setImage(UIImage(named: "check"), for: .normal)
self.maleButton.setImage(UIImage(named: "uncheck"), for: .normal)
default:
break;
}
})
.disposed(by: disposeBag)
❺ 性别是否已经选择
let genderHasSelected = genderSelectObservable.map { $0 != .notSelcted ? true : false }
c、更新按钮是否可以点击
![](https://img.haomeiwen.com/i9570900/ff745f1ca568df52.png)
出生日期和性别都选择后更新按钮才可以点击。
Observable.combineLatest(birthdayObservable, genderHasSelected) { $0 && $1 }
.bind(to: updateButton.rx.isEnabled)
.disposed(by: disposeBag)
d、开关和滑条
![](https://img.haomeiwen.com/i9570900/ee60d9338d4f6f32.png)
开关
当UISwitch
为OFF
时,表示眼前的面试者不了解Swift
,因此,下面的UISlider
应该为0。当UISwitch
为ON
时,可以把UISlider
设置在1/4的位置作为默认值,表示面试者了解一点皮毛。
knowSwiftSwitch.rx.value
.map{ $0 ? 0.25 : 0}
.bind(to: swiftLevelSlider.rx.value)
.disposed(by: disposeBag)
滑条
当UISlider
不为0时,应该自动把UISwitch
设置为ON
,即面试者有了解过。当UISlider
为0时,应该自动把UISwitch
设置为OFF
,表示面试者一头雾水。
swiftLevelSlider.rx.value
.map { $0 != 0 ? true : false}
.bind(to: knowSwiftSwitch.rx.isOn)
.disposed(by: disposeBag)
e、计数器
![](https://img.haomeiwen.com/i9570900/55111583fb2a85e4.png)
通过计数器更改心跳的大小。
passionToLearnStepper.rx.value
.skip(1)
.subscribe(onNext: { (value) in
UserPreferences.heartHeight = UserPreferences.heartHeight + 10
self.heartImageView.frame = CGRect(x: 150, y: 750, width: UserPreferences.heartHeight, height: UserPreferences.heartHeight)
self.heartImageView.setNeedsLayout()
})
.disposed(by: disposeBag)
2、Subject实战
a、使用传统方式实现Table里的增删改查操作
![](https://img.haomeiwen.com/i9570900/4e96b37822cd8440.png)
❶ 添加新cell
@objc func didClickAddAction()
{
// 新行的index
let newRowIndex = modelItems.count
// 创建新行的数据
let model = SubjectModel(title:"多喝牛奶可以长高哦:\(Date().timeIntervalSince1970)", isFinished: false)
// 将新行的数据添加到数据列表中
// 必需先提供了数据才能添加cell
modelItems.append(model)
// 将index转化为IndexPath
let indexPath = IndexPath(row: newRowIndex, section: 0)
// 根据IndexPath在table中插入新行
tableView.insertRows(at: [indexPath], with: .automatic)
// 将新行的数据添加到数据库中
SubjectCacheManager.manager.insertModelToTable(models: [model])
}
❷ 删除cell
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath)
{
// 从数据库中删除数据
SubjectCacheManager.manager.deleteModelData(model: modelItems[indexPath.row])
// 从数据列中删除数据
modelItems.remove(at: indexPath.row)
// 从table中删除cell
let indexPaths = [indexPath]
tableView.deleteRows(at: indexPaths, with: .automatic)
tableView.deselectRow(at: indexPath, animated: true)
}
❸ 修改cell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let model = modelItems[indexPath.row]
// 将是否完成状态进行反转
model.toggleFinished()
// 将完成状态反转后到model更新到数据库中
SubjectCacheManager.manager.updataModelData(model: model)
// 刷新table中状态反转的cell
tableView.reloadRows(at: [indexPath], with: .fade)
// 获取数据库中的所有数据打印出来
let array = SubjectCacheManager.manager.fetachModelData()
for item in array
{
print("\(item.title) --- \(item.isFinished)")
}
}
b、使用Subject实现Table里的增删改查操作
![](https://img.haomeiwen.com/i9570900/09731e7158a745c6.png)
❶ 创建序列
// 将从数据库中获取到的所有数据作为modelsSubject的初始值
modelsSubject = BehaviorSubject(value: SubjectCacheManager.manager.fetachModelData())
modelsSubject?.subscribe(onNext:
{ [weak self](items) in
self?.modelItems = items// 将获取的所有数据赋值给列表
self?.tableView.reloadData()// 刷新表视图
self?.deleteButton.isEnabled = !items.isEmpty// items非空的话删除按钮便可点击
SubjectCacheManager.manager.updataAllData(models: items)// 根据items更新数据库
// 列表中没有完成的待办事项的数量
let num = items.filter(
{ (model) -> Bool in
return !model.isFinished
}).count
// 待办事项的数量小于6,导航栏的添加按钮便可点击
self?.navigationItem.rightBarButtonItem?.isEnabled = num <= 6
// 更新标题
self?.title = num == 0 ? "Subject实战" : "还有 \(num) 条待办事项"
}).disposed(by: disposeBag)
❷ 发送添加cell信号
@objc func didClickAddAction()
{
let newRowIndex = modelItems.count
let indexPath = IndexPath(row: newRowIndex, section: 0)
let model = SubjectModel(title:"多喝牛奶可以长高哦:\(Date().timeIntervalSince1970)", isFinished: false)
modelItems.append(model)
tableView.insertRows(at: [indexPath], with: .automatic)
// 将添加新行后的列表作为信号发送出去
modelsSubject?.onNext(modelItems)
}
❸ 发送删除cell信号
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath)
{
// 将删除cell后的列表作为信号发送出去
modelItems.remove(at: indexPath.row)
modelsSubject?.onNext(modelItems)
tableView.deselectRow(at: indexPath, animated: true)
}
❹ 发送修改cell信号
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let model = modelItems[indexPath.row]
// 将完成状态反转后的列表作为信号发送出去
model.toggleFinished()
modelsSubject?.onNext(modelItems)
}
c、使用Subject实现详情向列表界面传值
![](https://img.haomeiwen.com/i9570900/d0d6794b1652b13c.png)
❶ 创建序列。由列表界面订阅信号,在详情界面里发起响应
// todoSubject只在这个文件可用,因为只有在详情界面才更改待办事项的名字
fileprivate let todoSubject = PublishSubject<SubjectModel>()
// todoObservable提供给外界订阅详情中的待办事项获取其数据,但是不能对其进行更改
var todoObservable: Observable<SubjectModel>
{
return todoSubject.asObservable()
}
❷ 判断进入详情界面是要新增还是展示
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
todoNameTextField.becomeFirstResponder()
// 点击列表面里的cell进入详情界面需要显示数据
if let model = model
{
todoNameTextField.text = model.tittle
isFinishedSwitch.isOn = model.isFinished
}
// 点击新增cell进入详情界面无数据
else
{
model = SubjectModel(tittle: "", isFinished: false)
}
}
❸ 点击完成按钮后将model作为信号发送到上一个列表界面
@objc func didClickDoneAction()
{
// 点击完成按钮后将model作为信号发送到上一个列表界面
model.tittle = todoNameTextField.text ?? ""
model.isFinished = isFinishedSwitch.isOn
todoSubject.onNext(model)
self.navigationController?.popViewController(animated: true)
}
❹ 订阅信号后在回调闭包中更新UI
fileprivate func pushToDetailVC(model:SubjectModel?)
{
let detailVC = SubjectDetailViewController.init()
if let model = model
{
detailVC.model = model
}
// 订阅信号后在回调闭包中更新UI
detailVC.todoObservable.subscribe(onNext:
{ [weak self](model) in
self?.modelItems.append(model)
self?.modelsSubject?.onNext(self?.modelItems ?? [])
}).disposed(by: disposeBag)
self.navigationController?.pushViewController(detailVC, animated: true)
}
❺ 从列表界面进入详情界面
通过点击列表中的单元格进入详情界面。
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
// 进入详情界面修改cell
let model = modelItems[indexPath.row]
pushToDetailVC(model: model)
}
点击添加按钮进入详情界面。
@objc func didClickAddAction()
{
// 进入详情界面添加cell
pushToDetailVC(model: nil)
}
Demo
Demo在我的Github上,欢迎下载。
UseFrameworkDemo