为了自己更好的学习而转发:
转发自:http://blog.csdn.net/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
let array = [1,2,3,4,5]
let array2 = array.filter({$0 > 1}).map({$0 * 2})//4 6 8 10
var indexGenerator = array2.generate()
let fisrt = indexGenerator.next() // 4
let seoncd = indexGenerator.next() //6
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的回调(新的信号到来)一共有三种
enum Event<Element> {
case Next(Element) // 新的信号到来
case Error(ErrorType) // 信号发生错误,序列不会再产生信号
case Completed // 序列发送信号完成,不会再产生新的信号
}
protocol ObserverType {
func on(event: Event<Element>) //监听所有的信号
}
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,也就是所有者要释放的时候,自动取消监听。
class Observable<Element> {
func subscribe(observer: Observer<Element>) -> Disposable //调用Disposable的方法来取消
}
1
2
3
4
1
2
3
4
当然,除了手动释放,RxSwift提供了一些操作符,比如 takeUntil来根据条件取消
sequence
.takeUntil(self.rx_deallocated) //当对象要释放的时候,取消监听
.subscribe {
print($0)
}
1
2
3
4
5
1
2
3
4
5
信号处理的顺序
Observable有个隐式的约定,那就是在一个信号处理完成之前,不会发送下一个信号,不管发送信号的线程是并发的or串行的。
比如
someObservable
.subscribe { (e: Event<Element>) in
print("Event processing started")
// processing
print("Event processing ended")
}
1
2
3
4
5
6
1
2
3
4
5
6
只会出现
Event processing started
Event processing ended
Event processing started
Event processing ended
Event processing started
Event processing ended
1
2
3
4
5
6
1
2
3
4
5
6
不会出现
Event processing started
Event processing started
Event processing ended
Event processing ended
1
2
3
4
1
2
3
4
第一个例子
我们监听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)
}
}
}
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 对两种信号的值进行结合。可以返回不同种类的信号。
例如
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)")
}
1
2
3
4
5
1
2
3
4
5
对于,每一个fistTextfield的信号,在字符串开始处增加”first”;对secondTextfield的信号进行过滤,当长度大于3的时候,才会继续传递。对两个信号进行结合,取truple类型,然后打印出来。
所以,当我在fistTextfield中,输入1234,然后secondTextfield中依次输入abcdefg的时候
combineLatest:("first1234abcd", 13)
combineLatest:("first1234abcd3", 14)
combineLatest:("first1234abcd", 13)
combineLatest:("first1234abcde", 14)
combineLatest:("first1234abcdef", 15)
combineLatest:("first1234abcdefg", 16)
1
2
3
4
5
6
1
2
3
4
5
6
例子三,创建一个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
})
}
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
这样调用
_ = createObserveable(test).subscribe({ (event) in
switch event{
case .Next(let value):
print(value)
case .Completed:
print("Completed")
case .Error(let error):
print(error)
}
})
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
然后,Log如下
Optional("{\n a = b;\n 1 = 2;\n}")
Completed
1
2
1
2
可以看到,创建一个Observable相当容易,调用Observable.create,在必要的时候发送onNext,onError,onCompleted信号。然后返回一个Disposable用来取消信号
throttle/retry/distinctUntilChanged/flatMapLatest
throttle 忽略上一个信号的一段时间的变化,也就是说一段时间没有新的信号输入,才会向下发送
distinctUntilChanged 直到信号改变了再发送
retry 如果失败,重新尝试的次数
flatMapLatest 仅仅执行最新的信号,当有新的信号来的时候,取消上一次未执行完的整个序列
最直接的例子就是搜索,通常我们想要
用户用一段时间没有输入的时候,在进进行网络请求,不然网络请求太频繁,对客户端和服务器都是负担
当新的请求来的时候,如果上一个未完成,则取消上一个
如果网络失败,能重新请求几次就更好了
这时候,用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)
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进行监听
比如
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")
}
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进行监听
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()
}
}
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进行显示/隐藏
只需要一行代码
_ = mySwitch.rx_value.bindTo(testView.rx_hidden)
1
1
例子七,根据输入,进行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")
}
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用做网络请求,通常,当你这样调用的时候,会创建两个序列,也就是会进行两次网络请求,这是不需要的
let network = networkWithText(text)
let subscription1 = network
.subscribeNext { n in
//创建第一个序列
}
let subscription2 = network
.subscribeNext { n in
//创建第二个序列
}
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
为了共享一个序列,你只需要这这样调用
let network = networkWithText(text).shareReplay(1)
1
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()
}
}
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
_ = 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)
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上来,上文提到了,对于搜索,我们可以这么做,
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)
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是在后台线程执行的,那么后续绑定是在后台线程上进行的,会崩溃
绑定了两次,意味着会执行两次
于是,我们需要进行额外的操作,来避免上述缺陷。
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)
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我们可以将上述过程简化
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)
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,你需要在这个函数里来处理
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
1
2
3
4
1
2
3
4
而使用RxSwift,KVO变成了这样
view.rx_observe(CGRect.self, "frame")
.subscribeNext { frame in
print("Got new frame \(frame)")
}
1
2
3
4
1
2
3
4
或者这样
someSuspiciousViewController
.rx_observeWeakly(Bool.self, "behavingOk")
.subscribeNext { behavingOk in
print("Cats can purr? \(behavingOk)")
}
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变的十分简洁
NSNotificationCenter.defaultCenter()
.rx_notification(UITextViewTextDidBeginEditingNotification, object: myTextView)
.map { /*do something with data*/ }
....
1
2
3
4
1
2
3
4
DEBUG
调试编译问题
Swift是一个强类型语言,能够在绝大部分场景自动推断出变量的类型。比如
let a = 10 //Int
1
1
但是,有些时候RxSwift的序列处理会报编译错误
比如
images = word
.filter { $0.containsString("important") }
.flatMap { word in
return self.api.loadFlickrFeed("karate")
.catchError { error in
return just(JSON(1))
}
}
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
如果,Swift没办法推断出类型,那么最直接的方式,就是显式的告诉Swift类型是什么
images = word
.filter { (s: String) -> Bool in s.containsString("important") } //输入是 string ,返回Bool
.flatMap { (word: String) -> Observable<JSON> in
return self.api.loadFlickrFeed("karate")
.catchError { (error: NSError) -> Observable<JSON> in
return just(JSON(1))
}
}
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
调试
使用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(
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
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()
}
}
}
}
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记录资源分配情况,所以通常的调试方式如下
/* 在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
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 没有泄漏,因为资源管理非常简单