前言介绍
RX是一个帮助我们简化异步编程的框架。它拓展了观察者模式,使我们可以自由组合多个异步事件,而不需要去关心线程,同步,线程安全,并发数据以及I/O阻塞。RXSwift是RX的Swift版本。
为什么要使用RxSwift
- 复合
- 复用
- 清晰
- 易用 - 因为它抽象了异步编程,使我们统一了代码风格
- 稳定 - 因为Rx使完全通过单元测试的
Target Action
button.rx.tap.subscribe(onNext: {
print("button Tapped")
})
.disposed(by: disposeBag)
代理
scrollView.rx.contentOffset
.subscribe(onNext: { contentOffset in
print("contentOffset: \(contentOffset)")
})
.disposed(by: disposeBag)
闭包回调
URLSession.shared.rx.data(request: URLRequest(url: url))
.subscribe(onNext: { data in
print("Data Task Success with count: \(data.count)")
}, onError: { error in
print("Data Task Error: \(error)")
})
.disposed(by: disposeBag)
通知
NotificationCenter.default.rx
.notification(.UIApplicationWillEnterForeground)
.subscribe(onNext: { (notification) in
print("Application Will Enter Foreground")
})
.disposed(by: disposeBag)
不需要去管理观察者的生命周期,这样你就有更多精力去关注业务逻辑。
KVO
user.rx.observe(String.self, #keyPath(User.name))
.subscribe(onNext: { newValue in
print("do something with newValue")
})
.disposed(by: disposeBag)
多个任务之间有依赖关系
/// 用 Rx 封装接口
enum Api {
/// 通过用户名密码取得一个 token
static func token(username: String, password: String) -> Observable { ... }
/// 通过 token 取得用户信息
static func userInfo(token: String) -> Observable { ... }
}
/// 通过用户名和密码获取用户信息
Api.token(username: "beeth0ven", password: "987654321")
.flatMapLatest(Api.userInfo)
.subscribe(onNext: { userInfo in
print("获取用户信息成功: \(userInfo)")
}, onError: { error in
print("获取用户信息失败: \(error)")
})
.disposed(by: disposeBag)
等待多个并发任务完成后处理结果
/// 用 Rx 封装接口
enum Api {
/// 取得老师的详细信息
static func teacher(teacherId: Int) -> Observable { ... }
/// 取得老师的评论
static func teacherComments(teacherId: Int) -> Observable<[Comment]> { ... }
}
/// 同时取得老师信息和老师评论
Observable.zip(
Api.teacher(teacherId: teacherId),
Api.teacherComments(teacherId: teacherId)
).subscribe(onNext: { (teacher, comments) in
print("获取老师信息成功: \(teacher)")
print("获取老师评论成功: \(comments.count) 条")
}, onError: { error in
print("获取老师信息或评论失败: \(error)")
})
.disposed(by: disposeBag)
函数响应式编程
[图片上传失败...(image-6266d-1598506756093)]
Observable 理解成Signal,订阅Signal去完成一些处理操作。比如绑定数据,最后销毁这个Signal。
函数式编程是种编程范式,它需要我们将函数作为参数传递,或者作为返回值返还。我们可以通过组合不同的函数来得到想要的结果。
对每个事件都做出一些响应,所以这种编程叫做函数响应式编程。我们通过不同的构建函数,来创建所需要的数据序列。最后通过适当的方式来响应这个序列。这就是函数响应式编程。
数据绑定
数据绑定(订阅):就是指将可被监听的序列绑定到观察者上。
比如:
let image: Observable = ...
image.bind(to: imageView.rx.image)
将一个图片序列 “同步” 到imageView上。这个序列里面的图片可以是异步产生的。这里定义的 image 就是上图中蓝色部分(可被监听的序列)。这种“同步机制”就是数据绑定(订阅)。
RXSwift核心
[图片上传失败...(image-9ce6ac-1598506756094)]
- Observable - 产生事件(即信号)
- Observer - 响应事件(即绑定信号后的处理)
- Operator - 创建变化组合事件(组合多个信号,并行或者串行)
- Disposable - 管理绑定(订阅)的生命周期(一般管理信号销毁居多)
- Schedules - 线程队列调配
// Observable
let text = usernameOutlet.rx.text.orEmpty.asObservable()
// Observable
let passwordValid = text
// Operator
.map { $0.characters.count >= minimalUsernameLength }
// Observer
let observer = passwordValidOutlet.rx.isHidden
// Disposable
let disposable = passwordValid
// Scheduler 用于控制任务在那个线程队列运行
.subscribeOn(MainScheduler.instance)
.observeOn(MainScheduler.instance)
.bind(to: observer)
...
// 取消绑定,你可以在退出页面时取消绑定
disposable.dispose()
Observable - 可被监听的序列
所有的事物都是序列,是信号。它能异步的触发一系列事件并携带不可更改的状态变量。简单来说,它能让某个类的实例在一段事件内实现对另一个实例对象值的观察。例如:观察者可以捕获对所有可观察对象触发的事件,从而实现UI的实时更新或者是数据的实时处理。
subscribe后面的onNext,onError, onCompleted 分别响应。这些为事件。completed事件和error事件都会终止可观察对象的生命周期。不会继续触发新的事件。
public enum Event {
case next(Element)
case error(Swift.Error)
case completed
}
- next - 序列产生了一个新的元素
- error - 创建序列时产生了一个错误,导致序列终止
- completed - 序列的所有元素都已经成功产生,整个序列已经完成
Single
- 要么产生一个元素,要么产生一个error事件
- 不会共享状态变化
Completable
- 发出零个元素
- 发出一个
completed
事件或者一个error
事件 - 不会共享状态变化
Maybe
- 发出一个元素或者一个
completed
事件或者一个error
事件 - 不会共享状态变化
Driver
- 不会产生
error
事件 - 一定在
MainScheduler
监听(主线程监听) - 共享状态变化
ControlEvent
专门用于描述UI控件所产生的事件。
- 不会产生
error
事件 - 一定在
MainScheduler
订阅(主线程订阅) - 一定在
MainScheduler
监听(主线程监听) - 共享状态变化
Sequences - 序列
有限观察序列 (Finite observable sequences)
该序列是指那些最后会以completed或者error事件终止生命周期的可观察对象。
比如网络任务:
API.download(file: "http://www...")
.subscribe( onNext: { data in
append data to temporary file },
onError: { error in
display error to user },
onCompleted: {
use downloaded file })
无限观察序列 (Infinite observable sequences)
与网络任务不同的是,UI以及交互事件都是无限观察序列。它们并不存在一个明确的生命周期终止。
比如:
UIDevice.rx.orientation.subscribe(onNext: { current in
switch current {
case .landscape:
re-arrange UI for landscape
case .portrait:
re-arrange UI for portrait
}
})
Observer - 观察者
观察者是用来监听事件,然后它需要这个事件做出响应。
AnyObserver
可以用来描述任意一种观察者
Binder
- 不会处理错误事件
- 确保绑定都是在给定
Scheduler
上执行(默认MainScheduler)
一旦产出错误事件,在调试环境下将执行fatalError
,在发布环境下将打印错误信息。
Operator - 操作符
操作符可以帮助我们创建新的序列,或者变化组合原有的序列,从而生成一个新的序列。
filter - 过滤
map - 转换
zip - 配对
还有很多,这里暂时不展开太多。
Disposable - 可被清除的资源
通常来说,一个序列如果发出了error
或者completed
事件,那么所有内部资源都会被释放。如果需要提前释放或取消订阅,那么可以使用dispose
方法。
一般我们推荐使用清除包(DisposeBag)或者takeUntil操作符来管理订阅的生命周期。
DisposeBag - 清除包
var disposeBag = DisposeBag()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
textField.rx.text.orEmpty
.subscribe(onNext: { text in print(text) })
.disposed(by: self.disposeBag)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.disposeBag = DisposeBag()
}
当清除包被释放的时候,清除包内部所有可被清除的资源(Disposable)都将被清除。
takeUntil
override func viewDidLoad() {
super.viewDidLoad()
...
_ = usernameValid
.takeUntil(self.rx.deallocated)
.bind(to: passwordOutlet.rx.isEnabled)
_ = usernameValid
.takeUntil(self.rx.deallocated)
.bind(to: usernameValidOutlet.rx.isHidden)
_ = passwordValid
.takeUntil(self.rx.deallocated)
.bind(to: passwordValidOutlet.rx.isHidden)
_ = everythingValid
.takeUntil(self.rx.deallocated)
.bind(to: doSomethingOutlet.rx.isEnabled)
_ = doSomethingOutlet.rx.tap
.takeUntil(self.rx.deallocated)
.subscribe(onNext: { [weak self] in self?.showAlert() })
}
这将使得订阅一直持续到控制器的dealloc事件产生为止。
Schedulers - 调度器
Schedules是RX实现多线程的核心模块,主要用于控制任务在哪个线程或队列运行。
在线程这部分主要有两个操作符:observeOn 和 subscribeOn ,常用的还是 observeOn 。
调用 observeOn 指定接下来的操作在哪个线程。
调用 subscribeOn 决定订阅者的操作执行在哪个线程。
let rxData: Observable = ...
rxData
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] data in
self?.data = data
})
.disposed(by: disposeBag)
使用subscribeOn
用subscribeOn
来决定数据序列的构建函数在哪个Scheduler
使用observeOn
用observeOn
来决定在哪个Scheduler
监听这个数据序列.
一个比较典型的例子就是,在后台发起网络请求,然后解析数据,最后在主线程刷新页面。你就可以先用 subscribeOn 切到后台去发送请求并解析数据,最后用 observeOn 切换到主线程更新页面。
MainScheduler
主线程
SerialDispatchQueueScheduler
串行队列
ConcurrentDispatchQueueScheduler
并行队列
OperationQueueScheduler
可以理解成NSOperationQueue
.
Error Handling - 错误处理
一旦序列里面发生了一个error
事件,整个序列将被终止。
- retry - 重试
- catch - 恢复
retry
retryWhen
延时后重试
catchError
catchError
可以在错误产生时,用一个备用元素或者一组备用元素将错误替换掉。
result
可以将错误转换成result,即使发生了错误,序列也不会终止。
Subject
Subject是一个桥梁,既是 Observable 也是 Observer
- 作为一个 Observer,它可以订阅序列
- 同时作为一个Obserable,它可以转发或者发射数据。
Subject有几种:
- PublishSubject
- ReplaySubject
- BehaviorSubject
- Variable
PublishSubject
当有观察者订阅PublishSubject时,PublishSubject会发射订阅之后的数据给这个观察者。(存在数据丢失的问题)
ReplaySubject
和PublishSubject不同,不论观察者什么时候订阅,ReplaySubject都会发射完整的数据给观察者。
BehaviorSubject
当一个观察者订阅一个BehaviorSubject,它会发送原序列最近的那个值(如果还没有值会有默认的),之后继续发射原序列的值。
Variable
Variable是BehaviorSubject的一个封装,但是不会因为错误终止也不会正常终止,是一个无限序列。
注意的问题
cell中使用button重复订阅的问题
解决办法:为Cell添加一个DisposeBag,在prepareForReuse
时,更换新的DisposeBag
var disposeBag = DisposeBag()
override func prepareForReuse() {
super.prepareForReuse()
disposeBag = DisposeBag()
}
冷、热信号的问题
热信号,一般是我们接触到的最新的东西。
冷信号,一般是依赖其他的信号订阅后的行为,比如一些异步操作。
耦合性问题
RXSwift滥用会导致项目耦合性变差,可读性和可维护性都会变得很差,并且有一定的学习成本,会给项目本身带来一定的风险。
循环引用问题
RXSwift使用过程中会大量使用block,使用不当容易导致循环引用。