前言
RxSwift的目的是让数据/事件流和异步任务能够更方便的序列化处理,能够使用Swift进行响应式编程。
本文的目的:
- 介绍RxSwift的核心思想
- 讲解RxSwift的基础使用
- 介绍RxSwift的优点
RxSwif
t的核心是想是Observable<Element> sequence
,Observable
表示可监听或者可观察,也就是说RxSwift的
核心思想是可监听的序列。并且,Observable sequence
可以接受异步信号,也就是说,信号是可以异步给监听者的。
-
Observable(ObservableType)
和SequenceType
类似 -
ObservableType.subscribe
和SequenceType.generate
类似 - 由于
RxSwift
支持异步获得信号,所以用ObservableType.subscribe
,这和indexGenerator.next()
类似。
本文把RxSwift
中的序列的每一个Element
成为信号,因为异步的Element
是与时间相关的,称作信号更好理解一点。
RxSwift
中,ObservableType.subscribe
的回调(新的信号到来)一共有三种
enum Event<Element> {
case Next(Element) // 新的信号到来
case Error(ErrorType) // 信号发生错误,序列不会再产生信号
case Completed // 序列发送信号完成,不会再产生新的信号
}
protocol ObserverType {
func on(event: Event<Element>) //监听所有的信号
}
取消监听
Observable
分为两种
- 在有限的时间内会自动结束
(Completed/Error)
,比如一个网络请求当作一个序列,当网络请求完成的时候,Observable
自动结束,资源会被释放
2.信号不会自己结束,最简单的比如一个Timer
,每隔一段时间发送一个新的信号过来,这时候需要手动取消监听,来释放相应的资源,又比如一个label.rac_text
是一个Obserable
,通常需要这样调用addDisposableTo(disposeBag)
来让其在deinit
,也就是所有者要释放的时候,自动取消监听。
class Observable<Element> {
func subscribe(observer: Observer<Element>) -> Disposable //调用Disposable的方法来取消
}
当然,除了手动释放,RxSwift
提供了一些操作符,比如takeUntil
来根据条件取消
sequence
.takeUntil(self.rx_deallocated) //当对象要释放的时候,取消监听
.subscribe {
print($0)
}
信号处理的顺序
Observable
有个隐式的约定,那就是在一个信号处理完成之前,不会发送下一个信号,不管发送信号的线程是并发的or串行的。
例如
someObservable
.subscribe { (e: Event<Element>) in
print("Event processing started")
// processing
print("Event processing ended")
}
只会出现
[图片上传中...(正确结果.png-5f2e8d-1521776416167-0)]
不会出现的结果
第一个例子
我们监听textfield
的文字变化,然后,Log
出text
,当button
点击的时候,取消这次监听
class ObservableAndCancelController : UIViewController{
var subscription:Disposable?
@IBOutlet weak var textfield: UITextField!
@IBAction func cancelObserve(sender: AnyObject) {
subscription?.dispose()
}
override func viewDidLoad() {
super.viewDidLoad()
subscription = textfield.rx_text.subscribeNext { (text) in
print(text)
}
}
}
RxSwift
用extensiton
的方式,为UITextfield
,UIlabel
等控件添加了很多可监听的属性,这里的textfield.rx_text
就是一个
操作符(Operators)
在上文的第一个例子里面,你看到了监听信号,并且log
出值。事实上,这样直接处理信号的时候是很少的,很多时候,我们需要对信号进行映射,过滤,这时候我们就要用到操作符了。在这个文档里,你可以找到所有的操作符。
关于操作符效果,你可以参见http://rxmarbles.com/的可视化效果,这会给你一个更好的理解
例子二 map,filter,combineLatest
- map 对信号(Element)进行映射处理。比如输入是String,影射到Bool
- filter 对信号(Element)进行过滤处理。返回信号,和输入的信号是同一种类型
- combineLatest 对两种信号的值进行结合。可以返回不同种类的信号。
例如
let firstObserverable = firstTextfield.rx_text.map({"first" + $0})
let secondObserverable = secondTextfield.rx_text.filter({$0.characters.count > 3})
_ = Observable.combineLatest(firstObserverable, secondObserverable, resultSelector:{ ($0 + $1,$0.characters.count + $1.characters.count)}).subscribeNext { (element) in
print("combineLatest:\(element)")
}
对于,每一个fistTextfield
的信号,在字符串开始处增加”first”
;
对secondTextfield
的信号进行过滤,当长度大于3
的时候,才会继续传递;
对两个信号进行结合,取truple
类型,然后打印出来。
所以,当我在fistTextfield中,输入1234,然后secondTextfield中依次输入abcdefg的时候
例子三 创建一个Observable
Observerable
可以用来处理任务,并且异步返回Event
信号(Next,Error,Completion)
比如,这样一个方法
//Observable就是处理输入,并且把description发送出去
func createObserveable(object:AnyObject?)->Observable<String?>{
return Observable.create({ observer in
observer.onNext(object?.description)
observer.onCompleted()
return NopDisposable.instance
})
}
这样调用
_ = createObserveable(test).subscribe({ (event) in
switch event{
case .Next(let value):
print(value)
case .Completed:
print("Completed")
case .Error(let error):
print(error)
}
})
可以看到,创建一个Observable
相当容易,调用Observable.create
,在必要的时候发送onNext
,onError
,onCompleted
信号。然后返回一个Disposable
用来取消信号
throttle/retry/distinctUntilChanged/flatMapLatest
- throttle 忽略上一个信号的一段时间的变化,也就是说一段时间没有新的信号输入,才会向下发送
- distinctUntilChanged 直到信号改变了再发送
- retry 如果失败,重新尝试的次数
- flatMapLatest 仅仅执行最新的信号,当有新的信号来的时候,取消上一次未执行完的整个序列
最直接的例子就是搜索,通常我们想要
1.用户用一段时间没有输入的时候,在进进行网络请求,不然网络请求太频繁,对客户端和服务器都是负担
2.当新的请求来的时候,如果上一个未完成,则取消上一个
3.如果网络失败,能重新请求几次就更好了
这时候,用RxSwift你得代码会变的非常简单
let searchResults = searchBar.rx_text
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest { query -> Observable<[Repository]> in
if query.isEmpty {
return Observable.just([])
}
return doSearchAPI(query).retry(3)
.catchErrorJustReturn([])
}
.observeOn(MainScheduler.instance)
这里简单讲解下作用
-
throttle(0.3, scheduler: MainScheduler.instance)
保证用户没有输入0.3
秒后再进行下一步 -
distinctUntilChanged()
假如0.3
秒之前输入是ab
,0.3
秒后还是ab
,则不会进行下一步,只有改变了才会进行下一步 -
flatMapLatest
保证只搜索最新的,如果之前的没有完成,会被自动取消 -
doSearchAPI(query).retry(3)
保证,如果发生错误,自动重试3
次。
Schedulers
Schedulers 抽象化了线程,线程池,GCD中操作队列,Runloop等概念。可以理解为,Schedulers就是一个执行任务的线程。
有一点要注意:默认一个Observerable
在其创建的线程上执行
与Schedulers
相关的操作符有两个
-
observeOn(scheduler)
在一个scheduler
上执行任务,使用场景较多 -
subscribeOn(scheduler)
在一个scheduler
进行监听
比如:
sequence1
.observeOn(backgroundScheduler)
.map { n in
print("This is performed on the background scheduler")
}
.observeOn(MainScheduler.instance)
.map { n in
print("This is performed on the main scheduler")
}.subscribeOn(backgroundScheduler)
.subscribeNext{ n in
print("This is performed on the background scheduler")
}
__ 默认一个subscribeNext或者subscribe在其调用的线程上执行__
Serial/Concurrent Schedulers 串行或并行
和GCD
的队列很相似,并行Schedulers
的任务可以并发之行,串行Schedulers
只能依次执行。不过RxSwift有内部机制,保证上文提到的信号处理的顺序
RxSwift内置的Scheduler
通常,使用内置的Scheduler
足矣。
-
CurrentThreadScheduler
(串行) 当前线程Scheduler
,默认使用的 -
MainScheduler
(串行) 主线程 -
SerialDispatchQueueScheduler
封装了GCD
的串行队列 -
ConcurrentDispatchQueueScheduler
封装了GCD
的并行队列,这个在有任务要在后台执行的时候很有用 -
OperationQueueScheduler
封装了NSOperationQueue
例子四,在后台Scheduler之行任务,然后在主线程上更新UI
Variable
Variable表示一个可监听的数据结构。使用Variable,你可以监听数据的变化,也可以把其他值绑定到它身上。
当Variable被释放的时候,它会向监听者发送onCompleted
例子五,Variable
进行监听
class VariableController: UIViewController {
@IBOutlet weak var label: UILabel!
var timer:NSTimer?
var count = Variable(0)
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector:#selector(VariableController.updateValue) , userInfo: nil, repeats: true)
_ = count.asObservable().subscribeNext { (num) in
self.label?.text = "VariableValue:\(num)"
}
}
func updateValue(){
count.value = count.value + 1
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
timer?.invalidate()
}
}
数据绑定
数据绑定是开发的时候很常见的,比如根据文本的输入动态调整textfield
的背景色,动态调整按钮的enable
。亦或者根据textfield
的输入变化,动态的去反馈到model
层。如果你听过MVVM
,那你肯定知道,MVVM
的难点就是ViewModel
与View
的数据绑定问题。
不过,使用RxSwift
,数据绑定变的十分容易,你甚至可以把数据绑定到tableview
和collectionView
上去。
例子六,bindTo
很简单,随着Switch
的开关,view
进行显示/隐藏
只需要一行代码
_ = mySwitch.rx_value.bindTo(testView.rx_hidden)
例子七,根据输入,进行View
状态绑定
我们想要实现这样的状态
- 用户名至少6位,小于6位,则背景色是灰色,合法则透明
- 密码至少位8位,小于8位,则背景色是灰色,合法则透明
- 当用户名和密码都合法的时候,注册按钮enable,并且背景色变红
信号的处理方式如下:
let nameObserable = nameTextfield.rx_text.shareReplay(1).map({$0.characters.count >= 6})
let pwdObserable = passwordTextfield.rx_text.shareReplay(1).map({$0.characters.count >= 8})
_ = nameObserable.subscribeNext({ (valid) in
self.nameTextfield.backgroundColor = valid ? UIColor.clearColor():UIColor.lightGrayColor()
}).addDisposableTo(disposeBag)
_ = pwdObserable.subscribeNext({ (valid) in
self.passwordTextfield.backgroundColor = valid ? UIColor.clearColor(): UIColor.lightGrayColor()
}).addDisposableTo(disposeBag)//addDisposableTo(disposeBag)是为了自动释放
_ = Observable.combineLatest(nameObserable, pwdObserable) {$0 && $1}.subscribeNext({valid in
if valid{
self.registerButton.enabled = true
self.registerButton.backgroundColor = UIColor.redColor()
}else{
self.registerButton.enabled = false
self.registerButton.backgroundColor = UIColor.darkGrayColor()
}
}).addDisposableTo(disposeBag)
_ = registerButton.rx_tap.shareReplay(1).subscribeNext {
print("Button tapped")
}
共享监听Sharing subscription-shareReplay
这个是很常用的,比如一个Obserable
用做网络请求,通常,当你这样调用的时候,会创建两个序列,也就是会进行两次网络请求,这是不需要的
let network = networkWithText(text)
let subscription1 = network
.subscribeNext { n in
//创建第一个序列
}
let subscription2 = network
.subscribeNext { n in
//创建第二个序列
}
为了共享一个序列,你只需要这这样调用
let network = networkWithText(text).shareReplay(1)
就只会进行一次网络请求,两个subscription
共享结果,也就是shareReplay
的意思
自定义可绑定属性
上文,textfield
和button
的状态绑定是手动的,这无疑是不方便的。RxSwift
为我们提供了一种方式,来自定义可绑定属性
创建两个exetnsion
extension UITextField{
var ex_validState:AnyObserver<Bool>{
return UIBindingObserver(UIElement: self) { textfield, valid in
textfield.backgroundColor = valid ? UIColor.clearColor():UIColor.lightGrayColor()
}.asObserver()
}
}
extension UIButton{
var ex_validState:AnyObserver<Bool>{
return UIBindingObserver(UIElement: self) { button, valid in
button.enabled = valid
button.backgroundColor = valid ? UIColor.redColor() : UIColor.darkGrayColor()
}.asObserver()
}
}
然后,上文的代码,就可以简化成三行了,So easy
_ = nameObserable.bindTo(nameTextfield.ex_validState).addDisposableTo(disposeBag)
_ = pwdObserable.bindTo(passwordTextfield.ex_validState).addDisposableTo(disposeBag)
_ = Observable.combineLatest(nameObserable, pwdObserable) {$0 && $1}.bindTo(registerButton.ex_validState).addDisposableTo(disposeBag)
Driver
Driver
是RxSwift
精心制作的,专门提供给UI
层的一个接口。
利用Driver
你可以
- 利用
CoreData
的模型来驱动UI
- 利用
UI
的状态来绑定其他UI
的状态
Driver能够保证,在主线程上监听,因为UIKit不是需要在主线程上操作
Tips:
RxSwift
中做数据绑定有三种
- 利用
BindTo
方法 - 利用
Driver
(强烈建议使用这个,) - 利用
KVO
来手动绑定(很少用到)
回到Driver
上来,上文提到了,对于搜索,我们可以这么做,
let results = query.rx_text
.throttle(0.3, scheduler: MainScheduler.instance) //延迟0.3秒
.flatMapLatest { query in //永远只执行最新的
searchWithText(query)
}
results
.map { "\($0.count)" }
.bindTo(resultCount.rx_text)//绑定label
.addDisposableTo(disposeBag)
results
.bindTo(resultsTableView.rx_itemsWithCellIdentifier("Cell")) { (_, result, cell) in //绑定tableview
cell.textLabel?.text = "\(result)"
}
.addDisposableTo(disposeBag)
那么,这有什么缺陷呢?
假如searchWithText
失败了,那么整个序列就断掉了,后面的绑定不会有任何作用
假如searchWithText
是在后台线程执行的,那么后续绑定是在后台线程上进行的,会崩溃
绑定了两次,意味着会执行两次
于是,我们需要进行额外的操作,来避免上述缺陷。
let results = query.rx_text
.throttle(0.3, scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
.observeOn(MainScheduler.instance) // 保证在主线程(解决缺陷1)
.catchErrorJustReturn([]) // 发生错误,返回空数组(解决缺陷2)
}
.shareReplay(1) // 共享监听,保证只执行一次(解决缺陷3)
results
.map { "\($0.count)" }
.bindTo(resultCount.rx_text)
.addDisposableTo(disposeBag)
results
.bindTo(resultTableView.rx_itemsWithCellIdentifier("Cell")) { (_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.addDisposableTo(disposeBag)
利用Driver
我们可以将上述过程简化
let results = query.rx_text.asDriver() // 转换成Driver序列
.throttle(0.3, scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
.asDriver(onErrorJustReturn: []) // 告诉Driver发生错误怎么办
}
results
.map { "\($0.count)" }
.drive(resultCount.rx_text) // 用Driver绑定,不需要切换到主线程
.addDisposableTo(disposeBag)
results
.drive(resultTableView.rx_itemsWithCellIdentifier("Cell")) { (_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.addDisposableTo(disposeBag)
任何满足以下三个条件的Observer
序列都可以转换为Driver
- 不会因为错误就序列断掉(比如,有错误,但是没有调用
onError
来发送错误)
2.在主线程傻姑娘监听
3.共享side effects
对于,使用者只需要调用asDriver(onErrorJustReturn: [])
就能保证上述三点都实现了
KVO
通常的KVO
,你需要在这个函数里来处理
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
而使用RxSwift
,KVO
变成了这样
view.rx_observe(CGRect.self, "frame")
.subscribeNext { frame in
print("Got new frame \(frame)")
}
或者这样
someSuspiciousViewController
.rx_observeWeakly(Bool.self, "behavingOk")
.subscribeNext { behavingOk in
print("Cats can purr? \(behavingOk)")
}
二者的区别是
在
rx_observe
可以使用地方都可以使用rx_observeWeakly
。rx_observeWeakly
的执行效率要低一点,因为要处理对象的dealloc
关系。除此之外,rx_observeWeakly
还可以用在weak
属性上。
在使用view.rx_observe的时候,有几点要注意
由于KVO是建立在NSObject子类的基础上的,你可以通过如下方法,来让Structs支持KVO
Notification
使用RxSwift
,Notification
变的十分简洁
NSNotificationCenter.defaultCenter()
.rx_notification(UITextViewTextDidBeginEditingNotification, object: myTextView)
.map { /*do something with data*/ }
....
调试
使用Debug
操作符,会log
所有的数据流
let subscription = myInterval(0.1)
.debug("my probe")
.map { e in
return "This is simply \(e)"
}
.subscribeNext { n in
print(n)
}
NSThread.sleepForTimeInterval(0.5)
subscription.dispose(
你可以用自定义操作符的方式,来 Log
extension ObservableType {
public func myDebug(identifier: String) -> Observable<Self.E> {
return Observable.create { observer in
print("subscribed \(identifier)")
let subscription = self.subscribe { e in
print("event \(identifier) \(e)")
switch e {
case .Next(let value):
observer.on(.Next(value))
case .Error(let error):
observer.on(.Error(error))
case .Completed:
observer.on(.Completed)
}
}
return AnonymousDisposable {
print("disposing \(identifier)")
subscription.dispose()
}
}
}
}
调试内存泄漏问题
RxSwift
通过RxSwift.resourceCount
记录资源分配情况,所以通常的调试方式如下
/* 在AppDelegate方法中添加Log
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
*/
_ = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
.subscribeNext { _ in
print("Resource count \(RxSwift.resourceCount)")
}
然后
1.进入相关界面,进行正常操作
2.退出界面
3.观察RxSwift.resourceCount
4.在进入同一个界面,退出
5.观察RxSwift.resourceCount
使用心得
1.时刻牢记,使用RxSwift,尽量把所有的任务(可以理解为方法)抽象成Obserable(序列)和Obserable创建者,监听者
2.能用数据绑定的(bindTo和Driver)的就不要手动绑定
3.一定要熟练RxSwift提供的操作符,要会自定义操作符
RxSwift的优点
-
Composable
可组合,在设计模式中有一种模式叫做组合模式,你可以方便的用不同的组合实现不同的类 -
Reusable
代码可重用,原因很简单,对应RxSwift
,就是一堆Obserable
-
Declarative
响应式的,因为状态不可变,只有数据变化 -
Understandable and concise
简洁,容易理解。 -
Stable
稳定,因为RxSwift
写出的代码,单元测试时分方便 -
Less stateful
“无”状态性,因为对于响应式编程,你的应用程序就是一堆数据流 -
Without leaks
没有泄漏,因为资源管理非常简单