这是一个基本的 Rx 执行流程。
RxSwift的核心内容:
在RxSwift里,以时间为索引的常量队列,这种概念叫做Observable。
其中,最上面的一排,就是一个Observable
。从左到右,表示时间由远及近的流动过程。上面的每一个形状,就表示在“某个时间点发生的事件”,而最右边的竖线则表示事件成功结束。
在实际应用中,我们通常使用Observable
序列作为入口,把外部事件连接到响应式框架里面,比如我们通过Observable
把网络请求的结果连接进入MVVM框架中。
Observable
Observable
这个类是RxSwift框架的基础,我们可以称它为可观察序列。它的作用就是可以异步
的产生一系列的Event(事件),即一个Observable
对象会随着时间推移不定期的发出event(Element:T)
事件。Observable中的每一个元素,都可以理解为一个异步发生的事件。
有了可观察序列,还需要一个Observer
(订阅者)来订阅它,这样这个订阅者才能收到Observable
不时发出的Event
。
RxSwift使用Event枚举表示事件,定义如下:
public enum Event<Element> {
//Next element is produced
case next(Element)
//Sequenece terminated with an error
case error(Swift.Error)
///Sequence completed successfully
case completed
}
.next(value:T)
:用于装载数据的事件。当Observable序列发送数据时,订阅者会收到next事件,我们可以从该事件中取出实际的数据。.error(error:Error)
:用于装载错误事件。当发生错误的时候,Observable序列会发出error事件并且关闭该序列,订阅者一旦收到error事件后就无法接收其他事件了。.completed:
用于正常关闭序列的事件。当Observable序列发出completed事件时就会关闭自己,订阅者在收到completed事件以后就吴福安收到任何其他事件了。用于生成只有一个事件的Observable序列。
let observable = Observable<Int>.just(5)
用固定数量的元素生成一个Observable序列。
Observable.of("1", "2", "3", "4", "5", "6", "7", "8", "9")
用一个Sequence
类型的对象创建一个Observable
序列;
Observable.from(["1", "2", "3", "4", "5", "6", "7", "8", "9"])
自定义事件序列,详细内容看下面。
该方法创建一个不做任何操作,而是直接发送一个错误的 Observable
序列。
enum MyError: Error {
case A
case B
}
let observable = Observable<Int>.error(MyError.A)
该方法创建一个空内容的 Observable 序列。
let observable = Observable<Int>.empty()
该方法创建一个永远不会发出 Event(也不会终止)的 Observable 序列。
let observable = Observable<Int>.never()
(1)该方法通过指定起始和结束数值,创建一个以这个范围内所有值作为初始值的 Observable 序列。
(2)下面样例中,两种方法创建的 Observable 序列都是一样的。
//使用range()
let observable = Observable.range(start: 1, count: 5)
//使用of()
let observable = Observable.of(1, 2, 3 ,4 ,5)
该方法创建一个可以无限发出给定元素的 Event 的 Observable 序列(永不终止)。
let observable = Observable.repeatElement(1)
(1)该方法创建一个只有当提供的所有的判断条件都为 true 的时候,才会给出动作的 Observable 序列。
(2)下面样例中,两种方法创建的 Observable 序列都是一样的。
//使用generate()方法
let observable = Observable.generate(
initialState: 0,
condition: { $0 <= 10 },
iterate: { $0 + 2 }
)
//使用of()方法
let observable = Observable.of(0 , 2 ,4 ,6 ,8 ,10)
(1)该个方法相当于是创建一个 Observable 工厂,通过传入一个 block 来执行延迟 Observable 序列创建的行为,而这个 block 里就是真正的实例化序列对象的地方。
(2)下面是一个简单的演示样例:
//用于标记是奇数、还是偶数
var isOdd = true
//使用deferred()方法延迟Observable序列的初始化,通过传入的block来实现Observable序列的初始化并且返回。
let factory : Observable<Int> = Observable.deferred {
//让每次执行这个block时候都会让奇、偶数进行交替
isOdd = !isOdd
//根据isOdd参数,决定创建并返回的是奇数Observable、还是偶数Observable
if isOdd {
return Observable.of(1, 3, 5 ,7)
}else {
return Observable.of(2, 4, 6, 8)
}
}
//第1次订阅测试
factory.subscribe { event in
print("\(isOdd)", event)
}
//第2次订阅测试
factory.subscribe { event in
print("\(isOdd)", event)
}
运行结果如下,可以看到我们两次订阅的得到的 Observable 是不一样的:
(1)这个方法创建的 Observable 序列每隔一段设定的时间,会发出一个索引数的元素。而且它会一直发送下去。
(2)下面方法让其每 1 秒发送一次,并且是在主线程(MainScheduler)发送。
let observable = Observable<Int>.interval(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)
observable.subscribe { event in
print(event)
}
这个方法有两种用法,一种是创建的 Observable 序列在经过设定的一段时间后,产生唯一的一个元素。
//5秒种后发出唯一的一个元素0
let observable = Observable<Int>.timer(RxTimeInterval.seconds(5), scheduler: MainScheduler.instance)
observable.subscribe { event in
print(event)
}
另一种是创建的 Observable 序列在经过设定的一段时间后,每隔一段时间产生一个元素。
//延时5秒种后,每隔1秒钟发出一个元素
let observable = Observable<Int>.timer(RxTimeInterval.seconds(5), period: 1, scheduler: MainScheduler.instance)
observable.subscribe { event in
print(event)
}
创建一个包含字符1-9的序列
import RxSwift
Observable.of("1", "2", "3", "4", "5", "6", "7", "8", "9")
// Or
Observable.from(["1", "2", "3", "4", "5", "6", "7", "8", "9"])
定义好事件序列之后,我们就可以处理过滤偶数的需求了。
_ = Observable.of("1", "2", "3", "4", "5", "6", "7", "8", "9")
.map { Int($0) }
.filter { $0 != nil && $0! % 2 == 0 }
我们创建的Observable,表达的是异步操作。Observable中的每一个元素,都可以理解为一个异步发生的事件。
因此,当我们对Observable调用map和filter方法时,只表示我们要对事件序列中的元素进行处理的逻辑,而并不会立即对Observable中的元素进行处理。
在响应式编程里面,订阅者是一个重要的角色。上层模块都担任订阅者角色,主要订阅下层模块的Observable
序列。
在RxSwift中,订阅者可以调用Observable
对象的subscribe
方法来订阅。
有人“订阅”这个Observable
中的事件的时候,上面的筛选操作才会发生,像这样:
var evenNumberObservable =
Observable.of("1", "2", "3", "4", "5", "6", "7", "8", "9")
.map { Int($0) }
.filter {
if let item = $0, item % 2 == 0 {
print("Even: \(item)")
return true
}
return false
}
evenNumberObservable.subscribe { event in
print("Event: \(event)")
这表示的,就是我们“从头至尾”关注了evenNumberObservable这个序列中的所有事件。
重新编译执行一下,就可以看到筛选的过程和结果了,我们关注到了这个筛选事件中的所有偶数:
如果想要获取事件里面的数据,可以通过:
print(event.element)
那么,除了“全程关注”的人之外,还有一类是“半路来的人”,对于这些人,就只能看到他们关注到evenNumberObservable之后,发生的事件了,我们可以用下面的代码,来理解这个场景:
evenNumberObservable.skip(2).subscribe { event in
print("Event: \(event)")
}
这里,我们用了另外一个operator skip来模拟“半路关注”的情况。skip(2)可以让订阅者“错过”前2次发生的事件。此时,对这个订阅者而言,他就完全不知道之前还过滤出了2个偶数,他看到的结果就是这样的:
把这两次订阅放在一个图里,就是这样:
通过这两个例子,我们要表达的最重要的一个思想,就是Observable中的每一个元素都表示一个“异步发生的事件”这样的概念,operator对Observable的加工是在订阅的时候发生的。
一个 Observable
序列被创建出来后它不会马上就开始被激活从而发出 Event
,而是要等到它被某个人订阅了才会激活它。
而 Observable
序列激活之后要一直等到它发出了 .error
或者 .completed
的 event 后,它才被终结。
使用该方法我们可以手动取消一个订阅行为。
如果我们觉得这个订阅结束了不再需要了,就可以调用 dispose()
方法把这个订阅给销毁掉,防止内存泄漏。
当一个订阅行为被 dispose 了,那么之后 observable
如果再发出 event,这个已经 dispose 的订阅就收不到消息了。下面是一个简单的使用样例。
let observable = Observable.of("A", "B", "C")
//使用subscription常量存储这个订阅方法
let subscription = observable.subscribe { event in
print(event)
}
//调用这个订阅的dispose()方法
subscription.dispose()
除了 dispose() 方法之外,我们更经常用到的是一个叫 DisposeBag 的对象来管理多个订阅行为的销毁:
我们可以把一个 DisposeBag 对象看成一个垃圾袋,把用过的订阅行为都放进去。
而这个 DisposeBag 就会在自己快要 dealloc 的时候,对它里面的所有订阅行为都调用 dispose() 方法。
let disposeBag = DisposeBag()
//第1个Observable,及其订阅
let observable1 = Observable.of("A", "B", "C")
observable1.subscribe { event in
print(event)
}.disposed(by: disposeBag)
//第2个Observable,及其订阅
let observable2 = Observable.of(1, 2, 3)
observable2.subscribe { event in
print(event)
}.disposed(by: disposeBag)
通常单独对subscribe的返回值调用dispose()方法并不是一个好的编码习惯。RxSwift提供了一个类似RAII的机制,叫做DisposeBag,我们可以把所有的订阅对象放在一个DisposeBag里,当DisposeBag对象被销毁的时候,它里面“装”的所有订阅对象就会自动取消订阅,对应的事件序列的资源也就被自动回收了。
在实际使用当中,只建议为一个订阅者定义一个disposeBag即可。
有时候,Observable中的事件值并不像整数或字符串这么简单,当我们需要精确控制发送给订阅者的成功、错误和结束事件时,就可以使用RxSwift提供的create operator。
在Observable+Creation.swift里,可以看到create的签名是这样的:
public static func create(
_ subscribe: @escaping (AnyObserver<E>) -> Disposable
) -> Observable<E>
subscribe并不是指事件真正的订阅者,而是用来定义当有人订阅Observable中的事件时,应该如何向订阅者发送不同情况的事件,理解这个问题,是使用create的关键。
于是,create调用大体的结构,就是这样的:
let customOb = Observable<Int>.create {
// Hanle next, error, completed event here
}
然后,再来看subscribe
自身,当然,它是一个closure,此时,AnyObserver
在这个closure里,表示任意一个订阅的“替身”,我们要用这个“替身”来表达向订阅者发送各种事件的行为。理解了这个概念,我们的create调用就可以进一步细化成这样:
let customOb = Observable<Int>.create { observer in
// next event
observer.onNext(10)
observer.onNext(11)
// complete event
observer.onCompleted()
}
表示,只要有人订阅了costomOb中的事件,我们就先向订阅者发送两次.next
事件,值分别是10和11,然后,发送.completed
表示Observable结束。
最后,subscribe还要返回一个Disposable对象,我们可以用这个对象取消订阅进而回收customOb占用的资源:
let customOb = Observable<Int>.create { observer in
// The same as above...
return Disposables.create()
}
这样,我们就自定义了一个Observable,它会向每个订阅者发送10和11,然后结束。我们可以用下面的代码来试一下:
let disposeBag = DisposeBag()
customOb.subscribe(
onNext: { print($0) },
onCompleted: { print("Completed") },
onDisposed: { print("Game over") }
).addDisposableTo(disposeBag)
// 10
// 11
// Completed
// Game over
至此,我们就完成90%的工作了,但你可能还记得,我们还没自定义发生错误时,向订阅者发送的内容。其实很简单,先定义一个表示具体错误的类型:
enum CustomError: Error {
case somethingWrong
}
然后,在create的定义里,要发生错误的地方,直接通知订阅者就好了:
let customOb = Observable<Int>.create { observer in
// next event
observer.onNext(10)
observer.onError(MyError.somethingWrong)
observer.onNext(11)
// complete event
observer.onCompleted()
return Disposables.create()
}
最后,在订阅的时候,我们可以直接通过onError来得到错误通知:
customOb.subscribe(
onNext: { print($0) },
onError: { print($0) },
onCompleted: { print("Completed") },
onDisposed: { print("Game over") }
).addDisposableTo(disposeBag)
// 10
// somethingWrong
// Game over
重新执行之前的订阅,我们就只能在结果中看到上面注释中的结果了。
在实际的编程中,有时我们会串联多个operator对事件序列进行处理,虽然这样写起来很方便,但发生问题调试时就很麻烦了,因为紧密串联在一起的代码让我们很难方便的洞察每一个环节的状态。为此,RxSwift提供了一个类似“旁路”功能的operator:do。它的用法和subscribe类似,只不过事件会“穿过”do,继续发给后续的环节。这样,如果我们怀疑某个串联的环节发生了问题,就可以插入一个do operator进行观察:
customOb.do(
onNext: { print("Intercepted: \($0)") },
onError: { print("Intercepted: \($0)") },
onCompleted: { print("Intercepted: Completed") },
onDispose: { print("Intercepted: Game over") }
)
.subscribe(
onNext: { print($0) },
onError: { print($0) },
onCompleted: { print("Completed") },
onDisposed: { print("Game over") }
).addDisposableTo(disposeBag)
这样,customOb中的事件就会“流经”do之后,继续发送给subscribe,方便我们观察订阅到的每一个内容。重新执行一下,就能看到下面这样的结果:
// Intercepted: 10
// 10
// Intercepted: somethingWrong
// somethingWrong
// Game over
// Intercepted: Game over
在do里,用于处理取消订阅事件的参数是onDispose,但subscribe中对应的则是onDisposed,甚至,这个微小的差异还影响了最终打印的结果。
我们可以将 debug 调试操作符添加到一个链式步骤当中,这样系统就能将所有的订阅者、事件、和处理等详细信息打印出来,方便我们开发调试。
customOb.debug()
.subscribe(
onNext: { print($0) },
onError: { print($0) },
onCompleted: { print("Completed") },
onDisposed: { print("Game over") }
).addDisposableTo(disposeBag)
在上面的例子里,我们把debug安插在了订阅前取代了do operator。这样,debug就会在不影响subscribe的同时,自动打印customOb发出的所有事件。执行一下,就可以得到类似下面这样的结果:
2017-04-06 18:56:25.348: main.swift:23 (RxSwiftInSPM) -> subscribed
2017-04-06 18:56:25.355: main.swift:23 (RxSwiftInSPM) -> Event next(10)
10
2017-04-06 18:56:25.356: main.swift:23 (RxSwiftInSPM) -> Event error(somethingWrong)
somethingWrong
Game over
2017-04-06 18:56:25.356: main.swift:23 (RxSwiftInSPM) -> isDisposed
可以看到,带有时间和源代码信息的行,就是debug operator提供的详细信息,包括了从订阅开始,收到.next,收到.error一直到最后.disposed的全过程,这样,调试起来,就方便多了。
debug() 方法还可以传入标记参数,这样当项目中存在多个 debug 时可以很方便地区分出来。
let disposeBag = DisposeBag()
Observable.of("2", "3")
.startWith("1")
.debug("调试1")
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
通过将 RxSwift.Resources.total 打印出来,我们可以查看当前 RxSwift 申请的所有资源数量。这个在检查内存泄露的时候非常有用。
print(RxSwift.Resources.total)
let disposeBag = DisposeBag()
print(RxSwift.Resources.total)
Observable.of("BBB", "CCC")
.startWith("AAA")
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
print(RxSwift.Resources.total)
观察者(Observer)的作用就是监听事件,然后对这个事件做出响应。
Observer
。点击按钮时一个事件。Observer
。Observable
的 subscribe
方法后面描述当事件发生时,需要如何做出响应。import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
private let disposeBag = DisposeBag()
private var contentLabel:UILabel!
override func viewDidLoad() {
super.viewDidLoad()
contentLabel = UILabel(frame: CGRect(x: 100, y: 100, width: 200, height: 50))
contentLabel.textColor = .black
contentLabel.text = "开始"
view.addSubview(contentLabel)
//1、创建Int类型的事件序列
let observable1 = Observable<Int>.interval(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)
.map{"序号:\($0)"}//把Int类型的事件序列转换成String类型的
.subscribe { element in //创建观察者
self.contentLabel.text = element
} onError: { erorr in
print("onError:\(erorr)")
} onCompleted: {
print("onCompleted")
} onDisposed: {
print("onDisposed")
}.disposed(by: disposeBag)
}
}
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
private let disposeBag = DisposeBag()
private var contentLabel:UILabel!
override func viewDidLoad() {
super.viewDidLoad()
contentLabel = UILabel(frame: CGRect(x: 100, y: 100, width: 200, height: 50))
contentLabel.textColor = .black
contentLabel.text = "开始"
view.addSubview(contentLabel)
//创建Int类型的事件序列
let observable2 = Observable<Int>.interval(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)
.map{"序号:\($0)"} //转换成String类型的事件序列
.bind { element in //创建观察者
self.contentLabel.text = element
}.disposed(by: disposeBag)
}
}
AnyObserver
可以用来描叙任意一种观察者。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
private let disposeBag = DisposeBag()
private var contentLabel:UILabel!
override func viewDidLoad() {
super.viewDidLoad()
contentLabel = UILabel(frame: CGRect(x: 100, y: 100, width: 200, height: 50))
contentLabel.textColor = .black
contentLabel.text = "开始"
view.addSubview(contentLabel)
//创建观察者
let observer = AnyObserver<String> { event in
switch event {
case .next(let element):
self.contentLabel.text = element
case .completed:
print("compleled")
case .error(let error):
print(error)
}
}
//创建事件序列
let observable3 = Observable<Int>.interval(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)
.map{"序号:\($0)"} //转换成string类型
.subscribe(observer) //订阅观察者
.disposed(by: disposeBag)
}
}
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
private let disposeBag = DisposeBag()
private var contentLabel:UILabel!
override func viewDidLoad() {
super.viewDidLoad()
contentLabel = UILabel(frame: CGRect(x: 100, y: 100, width: 200, height: 50))
contentLabel.textColor = .black
contentLabel.text = "开始"
view.addSubview(contentLabel)
//创建观察者
let observer = AnyObserver<String> { event in
switch event {
case .next(let element):
self.contentLabel.text = element
case .completed:
print("compleled")
case .error(let error):
print(error)
}
}
//创建事件序列
let observable4 = Observable<Int>.interval(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)
.map{"序号:\($0)"} //转换成string类型
.bind(to: observer)//订阅观察者
.disposed(by: disposeBag)
}
}
Binder
主要有以下两个特征:
在label 标签的文字显示就是一个典型的 UI 观察者
。它在响应事件时,只会处理 next 事件,而且更新 UI 的操作需要在主线程上执行。那么这种情况下更好的方案就是使用 Binder。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
private let disposeBag = DisposeBag()
private var contentLabel:UILabel!
override func viewDidLoad() {
super.viewDidLoad()
contentLabel = UILabel(frame: CGRect(x: 100, y: 100, width: 200, height: 50))
contentLabel.textColor = .black
contentLabel.text = "开始"
view.addSubview(contentLabel)
//创建观察者
let binder = Binder<String>(contentLabel) { label, str in
label.text = str
}
let observable5 = Observable<Int>.interval(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)
.map{"序号:\($0)"}
.bind(to: binder).disposed(by: disposeBag)
}
}
其实 RxCocoa 在对许多 UI 控件进行扩展时,就利用 Binder 将控件属性变成UI观察者,这样空间属性就可以处理相关的事件序列。比如 UIControl+Rx.swift 中的 isSelected 属性便是一个 observer :
import RxSwift
import UIKit
extension Reactive where Base: UIControl {
/// Bindable sink for `enabled` property.
public var isSelected: Binder<Bool> {
return Binder(self.base) { control, value in
control.isSelected = value
}
}
}
因此我们可以将序列直接绑定到它上面。比如下面样例,button 会在可用、不可用这两种状态间交替变换(每隔一秒)。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
private let disposeBag = DisposeBag()
private var contentButton:UIButton!
override func viewDidLoad() {
super.viewDidLoad()
contentButton = UIButton(frame: CGRect(x: 100, y: 100, width: 300, height: 45))
contentButton.setTitleColor(UIColor.red, for: .normal)
contentButton.setTitleColor(UIColor.orange, for: .selected)
contentButton.setTitle("改变颜色", for: .normal)
view.addSubview(contentButton)
let observable = Observable<Int>.interval(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)
.map{$0 % 2 == 0} //将事件序列转换为bool类型
.bind(to: contentButton.rx.isSelected).disposed(by: disposeBag)
}
有时我们想让 UI 控件创建出来后默认就有一些观察者,而不必每次都为它们单独去创建观察者。比如我们想要让所有的 UIlabel 都有个 fontSize 可绑定属性,它会根据事件值自动改变标签的字体大小。
这里我们通过对 UILabel 进行扩展,增加了一个 fontSize 可绑定属性。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
private let disposeBag = DisposeBag()
private var contentLabel:UILabel!
override func viewDidLoad() {
super.viewDidLoad()
contentLabel = UILabel(frame: CGRect(x: 150, y: 100, width: 200, height: 100))
contentLabel.text = "HelloWorld!"
contentLabel.textColor = .black
view.addSubview(contentLabel)
let observable = Observable<Int>.interval(RxTimeInterval.milliseconds(500), scheduler: MainScheduler.instance)
.map{CGFloat($0)} //转换成double类型的事件序列
.filter{$0>14} //大于14时才订阅观察者
.bind(to: contentLabel.fontSize).disposed(by: disposeBag)
}
}
extension UILabel {
var fontSize:Binder<CGFloat> {
return Binder(self) { target, size in
target.font = UIFont.systemFont(ofSize: size)
}
}
}
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
let disposeBag = DisposeBag()
override func viewDidLoad() {
//Observable序列(每隔0.5秒钟发出一个索引数)
let observable = Observable<Int>.interval(0.5, scheduler: MainScheduler.instance)
observable
.map { CGFloat($0) }
.bind(to: label.rx.fontSize) //根据索引数不断变放大字体
.disposed(by: disposeBag)
}
}
extension Reactive where Base: UILabel {
public var fontSize: Binder<CGFloat> {
return Binder(self.base) { label, fontSize in
label.font = UIFont.systemFont(ofSize: fontSize)
}
}
}
其实 RxSwift 已经为我们提供许多常用的可绑定属性。比如 UILabel 就有 text 和 attributedText 这两个可绑定属性。
import RxSwift
import UIKit
extension Reactive where Base: UILabel {
/// Bindable sink for `text` property.
public var text: Binder<String?> {
return Binder(self.base) { label, text in
label.text = text
}
}
/// Bindable sink for `attributedText` property.
public var attributedText: Binder<NSAttributedString?> {
return Binder(self.base) { label, text in
label.attributedText = text
}
}
}
那么上文那个定时显示索引数的样例,我们其实不需要自定义 UI 观察者,直接使用 RxSwift 提供的绑定属性即可。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
private let disposeBag = DisposeBag()
private var contentLabel:UILabel!
override func viewDidLoad() {
super.viewDidLoad()
contentLabel = UILabel(frame: CGRect(x: 150, y: 100, width: 200, height: 100))
contentLabel.text = "HelloWorld!"
contentLabel.textColor = .black
view.addSubview(contentLabel)
let observable = Observable<Int>.interval(RxTimeInterval.milliseconds(500), scheduler: MainScheduler.instance)
.map{CGFloat($0)} //转换成double类型的事件序列
.filter{$0>14} //大于14时才订阅观察者
.bind(to: contentLabel.rx.fontSize).disposed(by: disposeBag)
}
}
extension Reactive where Base : UILabel {
var fontSize:Binder<CGFloat> {
return Binder(self.base) { target, size in
target.font = UIFont.systemFont(ofSize: size)
}
}
}
使用Observable
的工厂方法生成的对象都是只读,一旦生成,就无法添加新的事件。但是很多时候,我们需要往Observable序列增加事件,比如要把用户点击UI的事件添加到Observable序列中,或者把底层模块的事件添加到上层模块的序列中。比如订阅一个输入框的输入内容,当用户每输入一个字后,这个输入框关联的 Observable 就会发出一个带有输入内容的 Event,通知给所有订阅者。
RxSwift为我们提供了Subject
及其onNext
方法可以完成这项操作。
Subject即是观察者,也是Observable:说它是观察者是因为它能够动态地接收新的值。说它是一个Observable是因为当有了新的值之后,就会通过Event将新值发出给其他的所有的观察者。
在MVVM架构里面,就会大量使用Subject,当Resposity模块从Networking模块中接收事件,会把事件转送到自身的Subject来通知ViewModel,从而保证ViewModel的状态同步。因为Subject既可以接收也可以发送。
常用的Subject有PublishSubject、BehaviorSubject和RepaySubject。他们的区别在于订阅者能否收到订阅前的事件。
在订阅之后,它们的行为都是一致的,当Subject发出error或者completed事件以后,订阅者将无法接收到新的事件。
RxSwift 中文文档 中提到AsyncSubject
将在Observable
产生完成事件后,发出最后一个元素(仅仅只有最后一个元素),如果源Observable
没有发出任何元素,只有一个完成事件。那AsyncSubject
也只有一个完成事件。
它会对随后的观察者发出最终元素。如果Observabl
因为产生了一个 error 事件而中止, AsyncSubject
就不会发出任何元素,而是将这个 error 事件发送出来。”
enum TestError: Error {
case errorA
case errorB
}
let asyncSubject = AsyncSubject<String>()
asyncSubject.subscribe{print($0)}.disposed(by: disposeBag)
asyncSubject.onNext("这是第一条消息,不会打印出来")
asyncSubject.onNext("这是第二条消息,不会打印出来")
//asyncSubject.onError(TestError.errorA)
asyncSubject.onNext("这是第三条消息")
//asyncSubject.onCompleted()
PublishSuject 用于发布(Publish)事件,它的特点是订阅者只能接收订阅后的事件。
let publishSubject = PublishSubject<Int>()
publishSubject.onNext(1) //充当Observable事件序列发出
//创建观察者,无法收到订阅之前的事件
let observer1 = publishSubject.subscribe { event in
print("observer1: \(event)")
}
observer1.disposed(by: disposeBag)
publishSubject.onNext(2)
let observer2 = publishSubject.subscribe { event in
print("observer2: \(event)")
}
observer2.disposed(by: disposeBag)
publishSubject.onNext(3)
publishSubject.onCompleted()
publishSubject.onNext(4)
首先,我们生成一个名叫publishSubject
的对象,并发出onNext
(1)事件,接着通过subscribe
方法来生成一个名叫observer1的观察者。
由于publishSubject
的观察者只能收到订阅以后的事件,因此observer1
无法收到之前的onNext
(1)的事件。
当publishSubject
发出onNext(2)
事件时,observer1
就会收到该事件。在此之后,我们又生成了第二个观察者observer2
,该观察者也没法接收到以前的事件。当publishSubject
发出onNext(3)
和completed
事件的时候,两个观察者都能接收到。因为completed
事件把该 Subject 关闭了,之后所有观察者都不能接收到onNext(4)
事件。
observer1: next(2)
observer1: next(3)
observer2: next(3)
observer1: completed
observer2: completed
PublishSubject 很适合发送新的事件,但有时候,消息发送者需要比观察者先进行初始化,此时观察者就无法接收到原有事件。
BehaviorSubject 用于缓存一个事件,当观察者订阅 BehaviorSubject 时,会马上收到该 Subject 里面最后一个事件。
let behaviorSubject = BehaviorSubject<Int>(value: 1)
let observer1 = behaviorSubject.subscribe { event in
print("observer1: \(event)")
}
observer1.disposed(by: disposeBag)
behaviorSubject.onNext(2)
let observer2 = behaviorSubject.subscribe { event in
print("observer2: \(event)")
}
observer2.disposed(by: disposeBag)
behaviorSubject.onNext(3)
behaviorSubject.onCompleted()
behaviorSubject.onNext(4)
因为 BehaviorSubject
要给观察者提供订阅前的最后一条事件,我们需要传递初始值来生成BehaviorSubject
。在上面的代码中可以看到,我们传递了1来新建behaviorSubject
对象,当observer1
订阅时马上就能接收到next(1)
事件。而observer2
订阅的时候只能接收到前一个next(2)
事件。接着,它们都能收到next(3)
事件。当收到completed
事件后,observer1
和observer2
都停止接收其他事件了。
observer1: next(1)
observer1: next(2)
observer2: next(2)
observer1: next(3)
observer2: next(3)
observer1: completed
observer2: completed
BehaviorSubject 只能缓存一个事件,当我们需要缓存 N 个事件时,就可以使用 ReplaySubject。
let replaySubject = ReplaySubject<Int>.create(bufferSize: 2)
replaySubject.onNext(1)
replaySubject.onNext(2)
let observer1 = replaySubject.subscribe { event in
print("observer1: \(event)")
}
observer1.disposed(by: disposeBag)
replaySubject.onNext(3)
let observer2 = replaySubject.subscribe { event in
print("observer2: \(event)")
}
observer2.disposed(by: disposeBag)
replaySubject.onNext(4)
replaySubject.onCompleted()
replaySubject.onNext(5)
为了看出与 BehaviorSubject 的不同之处,在这里我把 N 设置为 “2”。首先我们把 2 传入bufferSize来创建一个replaySubject对象,然后发出两个next事件,当observer1订阅时会马上得到1和2两个值。
接着replaySubject再发出一个next(3)事件。当observer2订阅的时候会接收到最近的两个值2和3。在此以后observer1和observer2会不断接收replaySubject的事件,直到收到completed事件后停止。其运行效果如下:
observer1: next(1)
observer1: next(2)
observer1: next(3)
observer2: next(2)
observer2: next(3)
observer1: next(4)
observer2: next(4)
observer1: completed
observer2: completed
除了能缓存更多的数据以外,还有一情况我们会选择使用 ReplaySubject 而不是BehaviorSubject。在初始化 BehaviorSubject 的时候,我们必须提供一个初始值。如果我没办法提供,只能把存放的类型定义为 Optional (可空)类型。但是我们可以使用 ReplaySubject 来避免这种情况。
ControlProperty 专门用于描述 UI 控件属性的,它具有以下特征:
Variable 是早期添加到 RxSwift 的概念,通过 “setting” 和 “getting”, 他可以帮助我们从原先命令式的思维方式,过渡到响应式的思维方式。
但这只是我们一厢情愿的想法。许多开发者滥用 Variable,来构建 重度命令式 系统,而不是 Rx 的 声明式 系统。这对于新手很常见,并且他们无法意识到,这是代码的坏味道。所以在 RxSwift 4.x 中 Variable 被轻度弃用,仅仅给出一个运行时警告。
在 RxSwift 5.x 中,他被官方的正式的弃用了,并且在需要时,推荐使用 BehaviorRelay
或者 BehaviorSubject
。
除了事件序列之外,在平时的编程中我们还经常需遇到一类场景,就是需要某个值是有“响应式”特性的,例如可以通过设置这个值来动态控制按钮是否禁用,是否显示某些内容等。为了方便这个操作,RxSwift还提供了一个特殊的subject,叫做Variable。
我们可以像定义一个普通变量一样定义一个Variable:
let stringVariable = Variable("Episode1")
当我们要订阅一个Variable对象的时候,要先明确使用asObservable()方法。而不像其他subject一样直接订阅:
let stringVariable = Variable("Episode1")
let sub1 = stringVariable
.asObservable()
.subscribe {
print("sub1: \($0)")
}
// sub1: next(Episode1)
而当我们要给一个Variable设置新值的时候,要明确访问它的value属性,而不是使用onNext方法:
stringVariable.value = "Episode2"
// sub1: next(Episode2)
最后要说明的一点是,Variable只用来表达一个“响应式”值的语义,因此,它有以下两点性质:
因此,下面的代码都会导致编译错误:
// !!! The following code CANNOT compile !!!
stringVariable.asObservable().onError(MyError.myError)
stringVariable.asObservable().onCompleted()
操作符能够帮助观察者在接收事件之前把Observable序列中的事件进行过滤、转换或者合并。比如我们使用map操作符把Model数据转换成ViewModel类型类更新UI。这样的map操作符就属于转换操作符,能帮助我们把一种数据类型转换成为另外一种数据类型。
此外还有filter和distinctUnitChanged等过滤操作符,我们可以使用过滤操作符把订阅者不关心的事件给过滤掉,还有合并操作符startWith、concat、merge、combineLatest和zip,用于组装与合并多个Observable序列。
过滤操作符用于过滤事件,我们可以使用过滤操作符把订阅者不关心的事件给过滤掉。常用的过滤操作符有 filter 和 distinctUntilChanged。
filter操作符常用于通过规则过滤不需要的事件,例如在朋友圈功能里面,可以把发布时间早于一天前的信息过滤掉不显示。为了方便理解,我就以几个数字来解释下。如下所示,有 2、23、5、60、1、31,我想把小于 10 的数过滤掉,就可以通过 filter 设置过滤规则,然后打印出来的数字就是 23、 60、31。代码示例如下。
Observable.of(2, 23, 5, 60, 1, 31)
.filter { $0 > 10 }
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
distinctUntilChanged用于把相同的事件过滤掉。如下面例子中的第二个 1 和第四个 2,使用distinctUntilChanged 就可以把它们给过滤掉,然后打印出 1、 2、 1。代码和图例如下所示。
Observable.of(1, 1, 2, 2, 1)
.distinctUntilChanged()
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
除了相同的事件,我们还可以使用操作符distinctUntilChanged过滤掉相同的状态,从而避免频繁更新 UI。例如,我们先使用本地缓存数据呈现 UI,然后发起网络请求。当请求成功以后可以把结果数据与缓存进行对比,如果数据一致就没必要再次更新 UI。
转换操作符非常实用,能帮助我们从一种数据类型转变成另外一种类型,例如我们可以把用于数据传输和存储的 Model 类型转换成用于 UI 呈现的 ViewModel 类型。
该操作符通过传入一个函数闭包把原来的 Observable 序列转变为一个新的 Observable 序列。
map是一个十分常用的操作符,可用于从一种类型转换成另外一种类型,例如下面的例子,我把数值类型转换成字符串。程序执行的时候会打印 “String: 1” 和 “String: 2”。代码和图例如下所示。
Observable.of(1, 2)
.map { "String: " + String($0) }
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
compactMap常用于过滤掉值为nil的操作符,你可以把 compactMap 理解为同时使用 filter 和 map 的两个操作符。filter 把nil的值过滤掉,而 map 把非空的值进行转换。
例如下面的例子中,我把字符串的值转换为数值类型,并把转换不成功的值过滤掉。由于 “not-a-number” 不能转换成数值类型,因此被过滤掉了,执行的时候会打印 1 和 2。代码示例如下所示:
Observable.of("1", "not-a-number", "2")
.compactMap { Int($0) }
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
map 在做转换的时候容易出现“升维”的情况。即转变之后,从一个序列变成了一个序列的序列。我们可以这样理解:把事件序列理解成为一个一维数组,map转换的时候容易变成二维数组,比如[[1,2],[3,4]]
,而flatMap就是要把二维数组合并成为一个一维数组,如:[1,2,3,4]
let subject1 = BehaviorSubject(value: "A0")
let subject2 = BehaviorSubject(value: "B0")
let observable = Observable.of(subject1,subject2)
subject1.onNext("A1")
subject2.onNext("B1")
subject1.onNext("A2")
subject2.onNext("B2")
observable.flatMap{$0}.subscribe { sub in
print(sub.element)
}.disposed(by: disposeBag)
subject1.onNext("A3")
subject2.onNext("B3")
subject1.onNext("A4")
subject2.onNext("B4")
打印结果:
Optional("A2")
Optional("B2")
Optional("A3")
Optional("B3")
Optional("A4")
Optional("B4")
之所以A0、A1、B0、B1
没有显示,主要是因为BehaviorSubject只能缓存一个元素。
let subject1 = ReplaySubject<String>.create(bufferSize: 5)
let subject2 = ReplaySubject<String>.create(bufferSize: 5)
let observable = Observable.of(subject1,subject2)
subject1.onNext("A1")
subject2.onNext("B1")
subject1.onNext("A2")
subject2.onNext("B2")
observable.flatMap{$0}.subscribe { sub in
print(sub.element)
}.disposed(by: disposeBag)
subject1.onNext("A3")
subject2.onNext("B3")
subject1.onNext("A4")
subject2.onNext("B4")
打印结果:
Optional("A1")
Optional("A2")
Optional("B1")
Optional("B2")
Optional("A3")
Optional("B3")
Optional("A4")
Optional("B4")
flatMap用于把两层的 Observable 序列合并到一层。
struct TemperatureSensor {
let temperature: Observable<Int>
}
let sensor1 = TemperatureSensor(temperature: Observable.of(21, 23))
let sensor2 = TemperatureSensor(temperature: Observable.of(22, 25))
Observable.of(sensor1, sensor2)
.flatMap { $0.temperature }
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
在这个例子中,我定义一个叫作TemperatureSensor的结构体,用来表示收集温度的传感器,该结构体包含了一个类型为Observable的temperature的属性。
假如天气站有多个这样的传感器,我们要把它们的温度信息合并到一个单独的 Observable 序列中方便统计,此时就可以使用 flatMap 来完成这项任务。
具体来说,我们在flatMap方法的闭包里面返回temperature属性,由于该属性是一个Observable对象,因此flatMap方法会把这些序列统一合并到一个单独的 Observable 序列里面,并打印出 21、23、22、25。
let subject1 = BehaviorSubject(value: "A0")
let subject2 = BehaviorSubject(value: "B0")
let observable = Observable.of(subject1,subject2)
subject1.onNext("A1")
subject2.onNext("B1")
subject1.onNext("A2")
subject2.onNext("B2")
observable.flatMapLatest{$0}.subscribe { sub in
print(sub.element)
}.disposed(by: disposeBag)
subject1.onNext("A3")
subject2.onNext("B3")
subject1.onNext("A4")
subject2.onNext("B4")
打印结果:
Optional("A2")
Optional("B2")
Optional("B3")
Optional("B4")
BehaviorSubject只会缓存一个事件,所以只打印了A2、B2
,flatMapLatest的特点从所有的事件序列中发出旧的事件,但是只会从最后一个事件序列中发出新的事件。所以这里只有subject2发出新事件。
let subject1 = ReplaySubject<String>.create(bufferSize: 5)
let subject2 = ReplaySubject<String>.create(bufferSize: 5)
let observable = Observable.of(subject1,subject2)
subject1.onNext("A1")
subject2.onNext("B1")
subject1.onNext("A2")
subject2.onNext("B2")
observable.flatMapLatest{$0}.subscribe { sub in
print(sub.element)
}.disposed(by: disposeBag)
subject1.onNext("A3")
subject2.onNext("B3")
subject1.onNext("A4")
subject2.onNext("B4")
打印结果:
Optional("A1")
Optional("A2")
Optional("B1")
Optional("B2")
Optional("B3")
Optional("B4")
let subject1 = BehaviorSubject(value: "A0")
let subject2 = BehaviorSubject(value: "B0")
let observable = Observable.of(subject1,subject2)
subject1.onNext("A1")
subject2.onNext("B1")
subject1.onNext("A2")
subject2.onNext("B2")
observable.flatMapFirst{$0}.subscribe { sub in
print(sub.element)
}.disposed(by: disposeBag)
subject1.onNext("A3")
subject2.onNext("B3")
subject1.onNext("A4")
subject2.onNext("B4")
打印结果:
Optional("A2")
Optional("A3")
Optional("A4")
BehaviorSubject只会缓存一个事件,所以只打印了A2、A3、A4
,flatMapFirst的特点就是只会从第一个事件序列中发出事件
let subject1 = ReplaySubject<String>.create(bufferSize: 5)
let subject2 = ReplaySubject<String>.create(bufferSize: 5)
let observable = Observable.of(subject1,subject2)
subject1.onNext("A1")
subject2.onNext("B1")
subject1.onNext("A2")
subject2.onNext("B2")
observable.flatMapFirst{$0}.subscribe { sub in
print(sub.element)
}.disposed(by: disposeBag)
subject1.onNext("A3")
subject2.onNext("B3")
subject1.onNext("A4")
subject2.onNext("B4")
打印结果:
Optional("A1")
Optional("A2")
Optional("A3")
Optional("A4")
合并操作符用于组装与合并多个 Observable 序列。
startWith可以使订阅者在接收到 Observable 序列的事件前,先收到传给 startWith 方法的事件。它的使用非常简单,例如在下面的例子中,我们把 3 和 4 传递给startWith。那么在执行过程中,会先把 3 和 4 事件发送给订阅者,其运行效果为 3、4、1、2。代码示例如下:
Observable.of(1, 2)
.startWith(3, 4)
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
打印结果: 3、4、1、2
日常中我们可以通过startWith方法,把加载事件插入网络数据事件之前,以此来保持 UI 状态的自动更新。
concat能把多个 Observable 序列按顺序合并在一起。例如,在下面的例子中我们合并了两个 Observable 序列,第一个包含 1 和 2,第二个包含 3 和 4,那么执行的时候会打印 1、2、3、4。代码示例如下。
Observable.of(1, 2)
.concat(Observable.of(3, 4))
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
merge,常用于合并多个 Observable 序列的操作符,和 concat 不一样的地方是它能保持原来事件的顺序。我们可以通过一个例子来看看,它是怎样合并 Observable 序列的。代码示例如下:
let first = PublishSubject<Int>()
let second = PublishSubject<Int>()
Observable.of(first, second)
.merge()
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
first.onNext(1)
first.onNext(2)
second.onNext(11)
first.onNext(3)
second.onNext(12)
second.onNext(13)
first.onNext(4)
我们调用merge方法把两个 PublishSubject 合并在一起,然后不同的 PublishSubject 会分别发出不同的next事件,订阅者根据事件发生的顺序来接收到相关事件。如下图所示,程序执行时会打印 1、2、11、3、12、13、4。
combineLatest 操作符将多个 Observables 中最新的元素通过一个函数组合起来,然后将这个组合的结果发出来。这些源 Observables 中任何一个发出一个元素,他都会发出一个元素(前提是,这些 Observables 曾经发出过元素)。
演示1:
let disposeBag = DisposeBag()
let first = PublishSubject<String>()
let second = PublishSubject<String>()
Observable.combineLatest(first, second) { $0 + $1 }
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
first.onNext("1")
second.onNext("A")
first.onNext("2")
second.onNext("B")
second.onNext("C")
second.onNext("D")
first.onNext("3")
first.onNext("4")
输出结果:
1A
2A
2B
2C
2D
3D
4D
演示2
combineLatest会把两个 Observable 序列里最后的事件合并起来,代码示例如下。
let first = PublishSubject<String>()
let second = PublishSubject<String>()
Observable.combineLatest(first, second) { $0 + $1 }
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
first.onNext("1")
second.onNext("a")
first.onNext("2")
second.onNext("b")
second.onNext("c")
first.onNext("3")
first.onNext("4")
在程序执行过程中,当其中一个 PublishSubject 发出next事件时,就会从另外一个 PublishSubject 取出其最后一个事件,然后调用combineLatest方法的闭包,把这两个事件合并起来并通知订阅者。上述的例子在执行时会打印 1a、2a、2b、2c、3c、4c。
在实际开发中,combineLatest方法非常实用。我们可以用它来监听多个 Observable 序列,然后组合起来统一更新状态。例如在一个登录页面里面,我们可以同时监听用户名和密码两个输入框,当它们同时有值的时候才激活登录按钮。
将两个 Observables 最新的元素通过一个函数组合起来,当第一个 Observable 发出一个元素,就将组合后的元素发送出来
演示1
当第一个 Observable 发出一个元素时,就立即取出第二个 Observable 中最新的元素,然后把第二个 Observable 中最新的元素发送出去。
let disposeBag = DisposeBag()
let firstSubject = PublishSubject<String>()
let secondSubject = PublishSubject<String>()
firstSubject
.withLatestFrom(secondSubject)
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
firstSubject.onNext("️")
firstSubject.onNext("️")
secondSubject.onNext("1")
secondSubject.onNext("2")
firstSubject.onNext("")
打印结果: 2
演示2
当第一个 Observable 发出一个元素时,就立即取出第二个 Observable 中最新的元素,将第一个 Observable 中最新的元素 first 和第二个 Observable 中最新的元素second组合,然后把组合结果 first+second发送出去。
let disposeBag = DisposeBag()
let firstSubject = PublishSubject<String>()
let secondSubject = PublishSubject<String>()
firstSubject
.withLatestFrom(secondSubject) {
(first, second) in
return first + second
}
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
firstSubject.onNext("️")
firstSubject.onNext("️")
secondSubject.onNext("1")
secondSubject.onNext("2")
firstSubject.onNext("")
2
zip也能用于合并两个 Observable 序列,和 combineLatest 不一样的地方是, zip 只会把两个 Observable 序列的事件配对合并。就像两队小朋友,排在前头的手牵手来到一个新队列。一旦出来就不再留在原有队列了。
为了方便理解 zip 与 combineLatest 的区别,我在下面例子中也使用了一样的数据并保持事件发送的顺序。
let first = PublishSubject<String>()
let second = PublishSubject<String>()
Observable.zip(first, second) { $0 + $1 }
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
first.onNext("1")
second.onNext("a")
first.onNext("2")
second.onNext("b")
second.onNext("c")
first.onNext("3")
first.onNext("4")
在上述的例子中,有两个 PublishSubject,其中first发出 1、2、3、4,而second发出 a、b、c。zip方法会返回它们的合并事件 1a、2b、3c。由于first所发出next(“4”)事件没有在second里面找到对应的事件,所以合并后的 Observable 序列只有三个事件。
使用catchAndReturn
当遇到 error 事件的时候,就返回指定的值,然后结束。
let disposeBag = DisposeBag()
let sequenceThatFails = PublishSubject<String>()
sequenceThatFails
.catchAndReturn("错误")
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
sequenceThatFails.onNext("a")
sequenceThatFails.onNext("b")
sequenceThatFails.onNext("c")
sequenceThatFails.onError(MyError.A)
sequenceThatFails.onNext("d")
使用catch方法
该方法可以捕获 error,并对其进行处理。
同时还能返回另一个 Observable 序列进行订阅(切换到新的序列)。
let disposeBag = DisposeBag()
let sequenceThatFails = PublishSubject<String>()
let recoverySequence = Observable.of("1", "2", "3")
sequenceThatFails
.catch {
print("Error:", $0)
return recoverySequence
}
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
sequenceThatFails.onNext("a")
sequenceThatFails.onNext("b")
sequenceThatFails.onNext("c")
sequenceThatFails.onError(MyError.A)
sequenceThatFails.onNext("d")
使用该方法当遇到错误的时候,会重新订阅该序列。比如遇到网络请求失败时,可以进行重新连接。
retry() 方法可以传入数字表示重试次数。不传的话只会重试一次。
let disposeBag = DisposeBag()
var count = 1
let sequenceThatErrors = Observable<String>.create { observer in
observer.onNext("a")
observer.onNext("b")
//让第一个订阅时发生错误
if count == 1 {
observer.onError(MyError.A)
print("Error encountered")
count += 1
}
observer.onNext("c")
observer.onNext("d")
observer.onCompleted()
return Disposables.create()
}
sequenceThatErrors
.retry(2) //重试2次(参数为空则只重试一次)
.subscribe(onNext: { print($0) })
Single 是 Observable 的另外一个版本。它要么发出一个元素,要么产生一个 error 事件。
Single 比较常见的例子就是执行 HTTP 请求,然后返回一个应答或错误。不过我们也可以用 Single 来描述任何只有一个元素的序列。
为方便使用,RxSwift 还为 Single 订阅提供了一个枚举(SingleEvent):
.success:
里面包含该 Single 的一个元素值.error:
用于包含错误public enum SingleEvent<Element> {
case success(Element)
case error(Swift.Error)
}
//获取豆瓣某频道下的歌曲信息
func getPlaylist(_ channel: String) -> Single<[String: Any]> {
return Single<[String: Any]>.create { single in
let url = "https://douban.fm/j/mine/playlist?"
+ "type=n&channel=\(channel)&from=mainsite"
let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _, error in
if let error = error {
single(.error(error))
return
}
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data,
options: .mutableLeaves),
let result = json as? [String: Any] else {
single(.error(DataError.cantParseJSON))
return
}
single(.success(result))
}
task.resume()
return Disposables.create { task.cancel() }
}
}
//与数据相关的错误类型
enum DataError: Error {
case cantParseJSON
}
接着我们可以使用如下方式使用这个 Single:
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
//获取第0个频道的歌曲信息
getPlaylist("0")
.subscribe { event in
switch event {
case .success(let json):
print("JSON结果: ", json)
case .error(let error):
print("发生错误: ", error)
}
}
.disposed(by: disposeBag)
}
}
也可以使用 subscribe(onSuccess:onError:) 这种方式:
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
//获取第0个频道的歌曲信息
getPlaylist("0")
.subscribe(onSuccess: { json in
print("JSON结果: ", json)
}, onError: { error in
print("发生错误: ", error)
})
.disposed(by: disposeBag)
}
}
我们可以通过调用 Observable 序列的 .asSingle() 方法,将它转换为 Single。
let disposeBag = DisposeBag()
Observable.of("1")
.asSingle()
.subscribe({ print($0) })
.disposed(by: disposeBag)
Completable 是 Observable 的另外一个版本。它要么只产生一个 completed 事件,要么产生一个 error 事件。
Completable 和 Observable
有点类似。适用于那些只关心任务是否完成,而不需要在意任务返回值的情况。比如:在程序退出时将一些数据缓存到本地文件,供下次启动时加载。像这种情况我们只关心缓存是否成功。
为方便使用,RxSwift 为 Completable 订阅提供了一个枚举(CompletableEvent):
public enum CompletableEvent {
case error(Swift.Error)
case completed
}
//将数据缓存到本地
func cacheLocally() -> Completable {
return Completable.create { completable in
//将数据缓存到本地(这里掠过具体的业务代码,随机成功或失败)
let success = (arc4random() % 2 == 0)
guard success else {
completable(.error(CacheError.failedCaching))
return Disposables.create {}
}
completable(.completed)
return Disposables.create {}
}
}
//与缓存相关的错误类型
enum CacheError: Error {
case failedCaching
}
接着我们可以使用如下方式使用这个 Completable:
cacheLocally()
.subscribe { completable in
switch completable {
case .completed:
print("保存成功!")
case .error(let error):
print("保存失败: \(error.localizedDescription)")
}
}
.disposed(by: disposeBag)
也可以使用 subscribe(onCompleted:onError:) 这种方式:
cacheLocally()
.subscribe(onCompleted: {
print("保存成功!")
}, onError: { error in
print("保存失败: \(error.localizedDescription)")
})
.disposed(by: disposeBag)
Maybe 同样是 Observable 的另外一个版本。它介于 Single 和 Completable 之间,它要么只能发出一个元素,要么产生一个 completed 事件,要么产生一个 error 事件。
Maybe 适合那种可能需要发出一个元素,又可能不需要发出的情况。
为方便使用,RxSwift 为 Maybe 订阅提供了一个枚举(MaybeEvent):
public enum MaybeEvent<Element> {
case success(Element)
case error(Swift.Error)
case completed
}
func generateString() -> Maybe<String> {
return Maybe<String>.create { maybe in
//成功并发出一个元素
maybe(.success("hangge.com"))
//成功但不发出任何元素
maybe(.completed)
//失败
//maybe(.error(StringError.failedGenerate))
return Disposables.create {}
}
}
//与缓存相关的错误类型
enum StringError: Error {
case failedGenerate
}
接着我们可以使用如下方式使用这个 Maybe:
generateString()
.subscribe { maybe in
switch maybe {
case .success(let element):
print("执行完毕,并获得元素:\(element)")
case .completed:
print("执行完毕,且没有任何元素。")
case .error(let error):
print("执行失败: \(error.localizedDescription)")
}
}
.disposed(by: disposeBag)
也可以使用 subscribe(onSuccess:onCompleted:onError:) 这种方式:
generateString()
.subscribe(onSuccess: { element in
print("执行完毕,并获得元素:\(element)")
},
onError: { error in
print("执行失败: \(error.localizedDescription)")
},
onCompleted: {
print("执行完毕,且没有任何元素。")
})
.disposed(by: disposeBag)
我们可以通过调用 Observable 序列的 .asMaybe() 方法,将它转换为 Maybe。
let disposeBag = DisposeBag()
Observable.of("1")
.asMaybe()
.subscribe({ print($0) })
.disposed(by: disposeBag)
Driver 的目标是提供一种简便的方式在 UI 层编写响应式代码。
Driver是为了简化UI层编写响应式代码所提供的序列。如果你遇到的序列满足以下特征,你就可以使用它
error
事件MainScheduler
)shareReplayLatestWhileConnected
)(1)Driver 最常使用的场景应该就是需要用序列来驱动应用程序的情况了,比如:
(2)与普通的操作系统驱动程序一样,如果出现序列错误,应用程序将停止响应用户输入。
(3)在主线程上观察到这些元素也是极其重要的,因为 UI 元素和应用程序逻辑通常不是线程安全的。
(4)此外,使用构建 Driver 的可观察的序列,它是共享状态变化。
这个是官方提供的样例,大致的意思是根据一个输入框的关键字,来请求数据,然后将获取到的结果绑定到另一个 Label 和 TableView 中。
(1)初学者使用 Observable 序列加 bindTo 绑定来实现这个功能的话可能会这么写:
let results = query.rx.text
.throttle(0.3, scheduler: MainScheduler.instance) //在主线程中操作,0.3秒内值若多次改变,取最后一次
.flatMapLatest { query in //筛选出空值, 拍平序列
fetchAutoCompleteItems(query) //向服务器请求一组结果
}
//将返回的结果绑定到用于显示结果数量的label上
results
.map { "\($0.count)" }
.bind(to: resultCount.rx.text)
.disposed(by: disposeBag)
//将返回的结果绑定到tableView上
results
.bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.disposed(by: disposeBag)
但这个代码存在如下 3 个问题:
(2)把上面几个问题修改后的代码是这样的:
let results = query.rx.text
.throttle(0.3, scheduler: MainScheduler.instance)//在主线程中操作,0.3秒内值若多次改变,取最后一次
.flatMapLatest { query in //筛选出空值, 拍平序列
fetchAutoCompleteItems(query) //向服务器请求一组结果
.observeOn(MainScheduler.instance) //将返回结果切换到到主线程上
.catchErrorJustReturn([]) //错误被处理了,这样至少不会终止整个序列
}
.shareReplay(1) //HTTP 请求是被共享的
//将返回的结果绑定到显示结果数量的label上
results
.map { "\($0.count)" }
.bind(to: resultCount.rx.text)
.disposed(by: disposeBag)
//将返回的结果绑定到tableView上
results
.bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.disposed(by: disposeBag)
虽然我们通过增加一些额外的处理,让程序可以正确运行。到对于一个大型的项目来说,如果都这么干也太麻烦了,而且容易遗漏出错。
(3)而如果我们使用 Driver 来实现的话就简单了,代码如下:
代码讲解:
(1)首先我们使用 asDriver
方法将 ControlProperty
转换为 Driver。
(2)接着我们可以用 .asDriver(onErrorJustReturn: [])
方法将任何 Observable 序列都转成 Driver,因为我们知道序列转换为 Driver 要他满足 3 个条件:
asDriver(onErrorJustReturn: [])
相当于以下代码: let safeSequence = xs
.observeOn(MainScheduler.instance) // 主线程监听
.catchErrorJustReturn(onErrorJustReturn) // 无法产生错误
.share(replay: 1, scope: .whileConnected)// 共享状态变化
return Driver(raw: safeSequence) // 封装
(3)同时在 Driver 中,框架已经默认帮我们加上了 shareReplayLatestWhileConnected
,所以我们也没必要再加上"replay"相关的语句了。
(4)最后记得使用 drive 而不是 bindTo
let results = query.rx.text.asDriver() // 将普通序列转换为 Driver
.throttle(0.3, scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
.asDriver(onErrorJustReturn: []) // 仅仅提供发生错误时的备选返回值
}
//将返回的结果绑定到显示结果数量的label上
results
.map { "\($0.count)" }
.drive(resultCount.rx.text) // 这里使用 drive 而不是 bindTo
.disposed(by: disposeBag)
//将返回的结果绑定到tableView上
results
.drive(resultsTableView.rx.items(cellIdentifier: "Cell")) { // 同样使用 drive 而不是 bindTo
(_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.disposed(by: disposeBag)
由于 drive 方法只能被 Driver 调用。这意味着,如果代码存在 drive,那么这个序列不会产生错误事件并且一定在主线程监听。这样我们就可以安全的绑定 UI 元素。
ControlProperty 是专门用来描述 UI 控件属性,拥有该类型的属性都是被观察者(Observable)。
ControlProperty 具有以下特征:
其实在 RxCocoa 下许多 UI 控件属性都是被观察者(可观察序列)。比如我们查看源码(UITextField+Rx.swift),可以发现 UITextField 的 rx.text 属性类型便是 ControlProperty
import RxSwift
import UIKit
extension Reactive where Base: UITextField {
public var text: ControlProperty<String?> {
return value
}
public var value: ControlProperty<String?> {
return base.rx.controlPropertyWithDefaultEvents(
getter: { textField in
textField.text
},
setter: { textField, value in
if textField.text != value {
textField.text = value
}
}
)
}
//......
}
那么我们如果想让一个 textField 里输入内容实时地显示在另一个 label 上,即前者作为被观察者,后者作为观察者。可以这么写:
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var label: UILabel!
let disposeBag = DisposeBag()
override func viewDidLoad() {
//将textField输入的文字绑定到label上
textField.rx.text
.bind(to: label.rx.text)
.disposed(by: disposeBag)
}
}
extension UILabel {
public var fontSize: Binder<CGFloat> {
return Binder(self) { label, fontSize in
label.font = UIFont.systemFont(ofSize: fontSize)
}
}
}
ControlEvent 是专门用于描述 UI 所产生的事件,拥有该类型的属性都是被观察者(Observable)。
ControlEvent 和 ControlProperty 一样,都具有以下特征:
同样地,在 RxCocoa 下许多 UI 控件的事件方法都是被观察者(可观察序列)。比如我们查看源码(UIButton+Rx.swift),可以发现 UIButton 的 rx.tap 方法类型便是 ControlEvent
:
import RxSwift
import UIKit
extension Reactive where Base: UIButton {
public var tap: ControlEvent<Void> {
return controlEvent(.touchUpInside)
}
}
那么我们如果想实现当一个 button 被点击时,在控制台输出一段文字。即前者作为被观察者,后者作为观察者。可以这么写:
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
@IBOutlet weak var button: UIButton!
override func viewDidLoad() {
//订阅按钮点击事件
button.rx.tap
.subscribe(onNext: {
print("欢迎访问hangge.com")
}).disposed(by: disposeBag)
}
}
保持程序状态自动更新之所以困难,很大的原因在于处理并发的异步事件是一件很繁琐的事情。为了方便处理来自不同线程的并发异步事件,RxSwift为哦们提供了排程器。它可以帮助我们把繁重的任务调度到后台排程器完成,并能指定其运行方式(如是串行还是并发),也能保证UI的任务都在主线程上执行。
比如一般Networking和DataStore模块都在后台排程器上执行,而View模块都在主排程器上执行。
根据串行或者并发来归类,我们可以把排程器分为两大类串行的排程器和并发的排程器。
串行的排程器包括CurrentThreadScheduler、MainScheduler、SerialDispatchQueueScheduler。
其中,CurrentThreadScheduler可以把任务安排在当前的线程上执行,这是默认的排程器。当我们不指定排程器的时候,RxSwift都会使用CurrentThreadScheduler把任务当在当前线程里面串行执行;MainScheduler是把任务调度到主线程MainThread里面并且马上执行,它主要用于执行UI相关的任务。而SerialDispatchQueueScheduler则会把任务放在dispatch_queue_t里面并且串行执行。
并发的排程器包括ConcurrentDispatchQueueScheduler和OperationQueueScheduler。
其中,ConcurrentDispatchQueueScheduler把任务安排到dispatch_queue_t里面,且以为并发的方式执行。该排程器一般用于执行后台任务,例如网络访问和数据缓存等等。我么可以指定DipsatchQueue的类型,例如使用ConcurrentDispatchQueueScheduler(qos:.background)来指定使用后台线程执行任务。
OperationQueueScheduler是把任务放在NSOperationQueue里面,以并发的方式执行。这个排程器一般用于执行繁重的后台任务,并通过设置maxConcurrentOperaationCount来控制所执行的并发任务的最大数量。它可以用于下载大文件。
Observable.of(1,2,3,4).subscribeOn(ConcurrentDispatchQueueScheduler(qos:.background)).dumpObservable().map{"\(getThreadName()):\($0)"}.observeOn(MainScheduler.instance).dumpObserver().disposed(by:disposeBag)
首先我们传入ConcurrentDispatchQueueScheduler(qos:.background)来调用subscribeOn方法,把Observable序列发出事件的执行代码都调度到后台排程器去执行,然后通过传入MainSchduluer…instance来调用observeOn,把订阅者的逻辑都调度到主排程器去执行。
这是一种常用的模式,我们通常使用后台排程器来进行网络访问并处理返回数据,然后通过主排程器把数据呈现到UI中去。
RxSwift中文文档
RxSwift学习笔记