RXSwift — 设计模式和基本概念
在本文中,我将简要介绍了RxSwift的设计模式和基本概念。
首先,为什么要用设计模式?
我们使用设计模式是为了避免我们的代码臭长臃肿难以维护。当然这不是唯一的原因,还有一个原因是可测试性。实际上有很多设计模式,我们可以将一些流行的模式比如MVC、MVVM、MVP和VIPER,用下面一张图来比较了他们在分布、可测试性和易用性间的区别。
当然每种设计模式都有其优缺点,但使用设计模式会使我们的代码更干净、更简单、更易于阅读。本文主要关注MVVM。
让我们先简单看一下MVC,然后再看MVVM。
MVC:
如果你是一个iOS开发老鸟,你肯定很熟悉MVC(苹果建议在iOS编程中使用MVC)。此模式由模型(Model)、视图(View)和控制器(Controller)组成,其中控制器负责将模型连接到视图。理论上,视图和控制器似乎是两个不同的东西,但在iOS开发中,两者却变成了一个东西。在小项目中使用MVC游刃有余,但是一旦你的项目变大,控制器内代码量剧增,就会变得一团糟。
MVCMVVM
MVVMMVVM代表模型(Model)、视图(View)、视图模型(ViewModel),其中控制器(Controller)、视图和动画(animations)都归到视图(View)中,而业务逻辑、API调用都归到视图模型(ViewModel)中。实际上,视图模型(ViewModel)这一层是模型(Model)和视图(View)之间的接口,它将按需要提供数据以供查看。注意,ViewModel文件中不应包含UIKit的内容:
import UIKit
因为ViewModel不应该从视图中知道任何东西。
RxSwift
MVVM的特性之一是绑定数据和视图,这使得它与RxSwift兼容。当然你可以用委托,KVO或闭包来处理,但学RxSwift的好处就是,如果你学习一种语言的RX,你可以以很小的学习成本学习其他语言,因为Rx以类似的概念架构了其他语言。
Reactive programming:
RxSwift是基于响应性编程的,那响应式编程是什么玩意?
下面来通过一个例子理解它:
假设你有三个变量(a,b,c)。
var a:Int = 1
var b:Int = 2
var c:Int = a + b // output: 3
现在如果我们把a从1变成2,然后输出c,发现它的值仍然是3。
var a:Int = 1
var b:Int = 2
var c:Int = a + b // output: 3
a = 2
print("c=\(c)")
//output: c=3
但是在响应式编程中情况就会有所不同,c值取决于a和b,这意味着如果你把a从1变到2,c值是自动从3变到4的,你没有必要自己改变它,也就是说c的值跟a和b已经绑定了,a或者b的更改变动也会自动更新c的值,这就是响应式。
现在让我们开始RxSwift的征程。
在RxSwift的世界中,所有事情都是事件流(包括UI事件、网络请求……),举个现实生活中的例子来解释一下:
你的电话是一个可观测的Observable对象,它会产生一系列事件,比如响个不停,推送消息通知等等,这些都会引起你的关注,而你相当于订阅了你的手机,并决定如何处理这些事件,比如查看消息通知、回绝一些电话等等,事实上你的电话就是一个可观测的Observable对象,它发出的这些事件就是信号signals,而你就是一个观察者observer。
Observables 和 observers (subscribers):
在Rx世界中,一些变量是可观察的Observables,另一些是观察者Observers(或订阅者subscribers),我们可以从任何类型创建Observable对象,只要它符合ObservableType协议。
现在我们来创建Observable:
let helloObsarvableString = Observable.just("Hello Rx World")
let obsarvableInt = Observable.of(0,1,2)
let dictSequence = Observable.from([1:"Hello",2:"World"])
在上面代码中,我们分别创建了三个Observable对象:
- 使用just创建的字符串Observable对象。
- 使用of创建的Int整型Observable对象。
- 使用from创建的字典Observable对象。
现在我们可以订阅这些可被观察的对象,这样我们就可以从它们发出的信号中读取值。
helloObsarvableString.subscribe { (event) in
print(event)
}
//output:
//next(Hello Rx World)
//completed
在上图中,我们有三个可观察的对象,第一个是Int类型,它发出6个值,从1到6,然后完成了。第二个是字符串类型的,它发出' a,b,c,d,e,f ',然后发生了一些错误,最终也完成了。最后是手势类型的,它发出一连串的tap,它还没有完成,还在继续。
在Rx世界中,对于每个观测到的在其持续时间内发出0到…的事件数(上面的例子),这些事件枚举由3个可能值组成:
- .next(value: T)
- .error(error: Error)
- .completed
当Observable添加值时,.next
事件就会被调用,而.next
事件关联的value
属性(上面例子中1到6个数字,a到f,以及手势点击)就会被传递给订阅者(观察者)。
当observable发生一个错误 ❌时,就会发送错误事件,而observable序列也就完成了。
如果observable完成,则发出.completed
事件。
如果我们想取消订阅并从一个可观察对象取消订阅,我们可以调用dispose
方法,或者如果你想在视图deinits时调用这个方法,你应该创建一个DisposeBag类型的变量。否则可能导致内存泄漏。
let disposeBag = DisposeBag()
helloObsarvableString.subscribe { (event) in
print(event)
}.disposed(by: disposeBag)
现在我们来了解一些RX的操作符,体会体会。我们对一个Int类型的可观察序列进行订阅,并对它进行如下几种变换:
Map
对于变化的信号,在它到达它的订阅者之前,你可以使用map方法做一些变换处理。
我们创建一个可观察的Int类型序列,它包含3个数字的值:2、3、4,现在我们在订阅它之前先通过map,将它发出的值都乘上10,最后订阅它并打印新的值:
Observable<Int>.of(2,3,4).map { value in
return value * 10
}.subscribe(onNext: { print($0) }).disposed(by: disposeBag)
Filter
这个操作符的作用是,在订阅前加上过滤条件。
还是上面的例子,我们在map完得到原值的10倍后,再过滤出大于25的值:
Observable<Int>.of(2,3,4).map { $0 * 10 }.filter{ $0 > 25 }.subscribe(onNext: { print($0) }).disposed(by: disposeBag)
//output: 30 40
FlatMap
如果你有两个可观察Observable对象,你想把它们合并成一个可观察Observable对象,就可以使用flatMap,比如在下面的例子中,可观察到的A序列和可观察到的B序列结合,得到新的可观察到的序列:
//FlatMap
let sequenceA = Observable<Int>.of(1,2)
let sequenceB = Observable<Int>.of(1,2)
let sequenceOfAB = Observable.of(sequenceA,sequenceB)
sequenceOfAB.flatMap { return $0 }.subscribe(onNext: { print($0) }).disposed(by: disposeBag)
DistinctUntilChanged or Debounce
这两种方法是搜索中最有用的方法之一。例如,用户想要搜索一个单词,你可能会在用户每次键入一个字符时都去调用search API,发送请求。但是,如果用户快速地键入,你就会向服务器调用许多不需要的请求。正确的做法是在用户停止输入时再调用search API发送请求。要解决这个问题,此时我们就可以使用Debounce函数:
usernameOutlet.rx.text.debounce(0.3, scheduler: MainScheduler.instance).subscribe(onNext: { [unowned self] text in
self?.search(wirhQuery: text)
}).addDisposableTo(disposeBag)
上面的代码的意思是,如果用户名文本框内输入的字段在0.3秒内发生变化,那么这些信号就不会被送达订阅者,因此也就不会调用到闭包中的搜索请求方法,只有当用户输入的时间在过了0.3秒之后,订阅者才会接收到信号,并调用搜索请求方法。
DistinctUntilChanged函数对变化很敏感,这意味着如果两个信号得到相同的信号,直到信号不发生变化,它才会发送给订阅者。
小结
以上介绍的概念只是RxSwift中的一部分,还有很多概念还没有介绍到,以后会慢慢通过实践项目总结出来。