转自: http://blog.youkuaiyun.com/Hello_Hwc/article/details/51859330
前言
RxSwift是Swift函数响应式编程的一个开源库,由Github的ReactiveX组织开发,维护。
RxSwift的目的是让让数据/事件流和异步任务能够更方便的序列化处理,能够使用Swift进行响应式编程
目前,RxSwift在Github上收到了5000+Star,600+fork。
本文的目的
- 介绍RxSwift的核心思想
- 讲解RxSwift的基础使用
- 介绍RxSwift的优点
如果你有时间,建议先读读RxSwift的文档,这会给你一个最基本的认识
本文不会讲解函数式编程,也不会讲解函数响应式编程的概念,计划后面单独出一篇博客来讲解Swift与函数式编程。
本文来自于官方文档的翻译,官方example代码的阅读,以及自己的理解
RxSwift和ReativeCocoa
老一点的iOS开发者应该对ReativeCocoa有一些了解,iOS响应式编程的鼻祖。就个人来看
- ReativeCocoa更适合OC,缺点语法复杂,概念繁多,参考资料少(尤其RAC4),不易理解
- RxSwift对Swift的兼容很好,利用了很多的Swift特性,语法简单,概念清楚
So,个人是非常推荐RxSwift的
Observables/Sequences
先复习下SequenceType。这是Swift中的一个协议,比如Swift中的Array就遵循这个协议,通过这个协议,你可以这样的去操作一个Array
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
也就是说,把Array作为一个序列,然后依次对这个序列进行过滤,映射等操作,也可以通过indexGenerator来一个个的获取序列中的数据。
RxSwift的核心思想和这个类似。
RxSwift的核心是想是 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
的回调(新的信号到来)一共有三种
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
取消监听
Observable分为两种
- 在有限的时间内会自动结束(Completed/Error),比如一个网络请求当作一个序列,当网络请求完成的时候,Observable自动结束,资源会被释放
- 信号不会自己结束,最简单的比如一个Timer,每隔一段时间发送一个新的信号过来,这时候需要手动取消监听,来释放相应的资源,又比如一个label.rac_text是一个Obserable,通常需要这样调用
addDisposableTo(disposeBag)
来让其在deinit,也就是所有者要释放的时候,自动取消监听。
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
当然,除了手动释放,RxSwift提供了一些操作符,比如 takeUntil
来根据条件取消
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
信号处理的顺序
Observable有个隐式的约定,那就是在一个信号处理完成之前,不会发送下一个信号,不管发送信号的线程是并发的or串行的。
比如
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
只会出现
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
不会出现
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
第一个例子
我们监听textfield的文字变化,然后,Log出text,当button点击的时候,取消这次监听
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
RxSwift用extensiton的方式,为UITextfield,UIlabel等控件添加了很多可监听的属性,这里的textfield.rx_text就是一个
效果:随着文字输入,实时Log出textfield的文字,当点击button之后,再输入,则不会Log
操作符(Operators)
在上文的第一个例子里面,你看到了监听信号,并且log出值。事实上,这样直接处理信号的时候是很少的,很多时候,我们需要对信号进行映射,过滤,这时候我们就要用到操作符了。在这个文档里,你可以找到所有的操作符。
关于操作符效果,你可以参见http://rxmarbles.com/的可视化效果,这会给你一个更好的理解
例子二,map,filter,combineLatest
- map 对信号(Element)进行映射处理。比如输入是String,影射到Bool
- filter 对信号(Element)进行过滤处理。返回信号,和输入的信号是同一种类型
- combineLatest 对两种信号的值进行结合。可以返回不同种类的信号。
例如
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
对于,每一个fistTextfield的信号,在字符串开始处增加”first”;对secondTextfield的信号进行过滤,当长度大于3的时候,才会继续传递。对两个信号进行结合,取truple类型,然后打印出来。
所以,当我在fistTextfield中,输入1234,然后secondTextfield中依次输入abcdefg的时候
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
例子三,创建一个Observable
Observerable可以用来处理任务,并且异步返回Event信号(Next,Error,Completion)
比如,这样一个方法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这样调用
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
然后,Log如下
- 1
- 2
- 1
- 2
可以看到,创建一个Observable相当容易,调用Observable.create,在必要的时候发送onNext,onError,onCompleted信号。然后返回一个Disposable用来取消信号
throttle/retry/distinctUntilChanged/flatMapLatest
- throttle 忽略上一个信号的一段时间的变化,也就是说一段时间没有新的信号输入,才会向下发送
- distinctUntilChanged 直到信号改变了再发送
- retry 如果失败,重新尝试的次数
- flatMapLatest 仅仅执行最新的信号,当有新的信号来的时候,取消上一次未执行完的整个序列
最直接的例子就是搜索,通常我们想要
- 用户用一段时间没有输入的时候,在进进行网络请求,不然网络请求太频繁,对客户端和服务器都是负担
- 当新的请求来的时候,如果上一个未完成,则取消上一个
- 如果网络失败,能重新请求几次就更好了
这时候,用RxSwift你得代码会变的非常简单
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
这里简单讲解下作用
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进行监听
比如
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
默认一个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进行监听
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
数据绑定
数据绑定是开发的时候很常见的,比如根据文本的输入动态调整textfield的背景色,动态调整按钮的enable。亦或者根据textfield的输入变化,动态的去反馈到model层。如果你听过MVVM,那你肯定知道,MVVM的难点就是ViewModel与View的数据绑定问题。
不过,使用RxSwift,数据绑定变的十分容易,你甚至可以把数据绑定到tableview和collectionView上去。
例子六,bindTo
很简单,随着Switch的开关,view进行显示/隐藏
只需要一行代码
- 1
- 1
例子七,根据输入,进行View状态绑定
我们想要实现这样的状态
- 用户名至少6位,小于6位,则背景色是灰色,合法则透明
- 密码至少位8位,小于8位,则背景色是灰色,合法则透明
- 当用户名和密码都合法的时候,注册按钮enable,并且背景色变红
信号的处理方式如下,
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
共享监听Sharing subscription-shareReplay
这个是很常用的,比如一个Obserable用做网络请求,通常,当你这样调用的时候,会创建两个序列,也就是会进行两次网络请求,这是不需要的
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
为了共享一个序列,你只需要这这样调用
- 1
- 1
就只会进行一次网络请求,两个subscription共享结果,也就是shareReplay的意思
自定义可绑定属性
上文,textfield和button的状态绑定是手动的,这无疑是不方便的。RxSwift为我们提供了一种方式,来自定义可绑定属性
创建两个exetnsion
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
然后,上文的代码,就可以简化成三行了,So easy
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
Driver
Driver是RxSwift精心制作的,专门提供给UI层的一个接口。
利用Driver你可以
- 利用CoreData的模型来驱动UI
- 利用UI的状态来绑定其他UI的状态
Driver能够保证,在主线程上监听,因为UIKit不是需要在主线程上操作
Tips:
RxSwift中做数据绑定有三种
- 利用BindTo方法
- 利用Driver(强烈建议使用这个,)
- 利用KVO来手动绑定(很少用到)
回到Driver上来,上文提到了,对于搜索,我们可以这么做,
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
那么,这有什么缺陷呢?
- 假如searchWithText失败了,那么整个序列就断掉了,后面的绑定不会有任何作用
- 假如searchWithText是在后台线程执行的,那么后续绑定是在后台线程上进行的,会崩溃
- 绑定了两次,意味着会执行两次
于是,我们需要进行额外的操作,来避免上述缺陷。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
利用Driver我们可以将上述过程简化
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
任何满足以下三个条件的Observer序列都可以转换为Driver
- 不会因为错误就序列断掉(比如,有错误,但是没有调用onError来发送错误)
- 在主线程傻姑娘监听
- 共享 side effects
对于,使用者只需要调用asDriver(onErrorJustReturn: [])
就能保证上述三点都实现了
KVO
通常的KVO,你需要在这个函数里来处理
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
而使用RxSwift,KVO变成了这样
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
或者这样
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
二者的区别是
在rx_observe可以使用地方都可以使用rx_observeWeakly。rx_observeWeakly的执行效率要低一点,因为要处理对象的dealloc关系。除此之外,rx_observeWeakly还可以用在weak属性上。
在使用view.rx_observe的时候,有几点要注意
由于KVO是建立在NSObject子类的基础上的,你可以通过如下方法,来让Structs支持KVO
Notification
使用RxSwift,Notification变的十分简洁
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
DEBUG
调试编译问题
Swift是一个强类型语言,能够在绝大部分场景自动推断出变量的类型。比如
- 1
- 1
但是,有些时候RxSwift的序列处理会报编译错误
比如
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
如果,Swift没办法推断出类型,那么最直接的方式,就是显式的告诉Swift类型是什么
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
调试
使用Debug操作符,会log所有的数据流
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
你可以用自定义操作符的方式,来Log
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
调试内存泄漏问题
RxSwift通过RxSwift.resourceCount
记录资源分配情况,所以通常的调试方式如下
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
然后
- 进入相关界面,进行正常操作
- 退出界面
- 观察RxSwift.resourceCount
- 在进入同一个界面,退出
- 观察RxSwift.resourceCount
使用心得
- 时刻牢记,使用RxSwift,尽量把所有的任务(可以理解为方法)抽象成Obserable(序列)和Obserable创建者,监听者
- 能用数据绑定的(bindTo和Driver)的就不要手动绑定
- 一定要熟练RxSwift提供的操作符,要会自定义操作符
RxSwift的优点
- Composable 可组合,在设计模式中有一种模式叫做组合模式,你可以方便的用不同的组合实现不同的类
- Reusable 代码可重用,原因很简单,对应RxSwift,就是一堆Obserable
- Declarative 响应式的,因为状态不可变,只有数据变化
- Understandable and concise 简洁,容易理解。
- Stable 稳定,因为RxSwift写出的代码,单元测试时分方便
- Less stateful “无”状态性,因为对于响应式编程,你的应用程序就是一堆数据流
- Without leaks 没有泄漏,因为资源管理非常简单