├── 00-why-rxswift-and-mvvm.md ├── 01-observable-and-operator.md ├── 02-what-is-subject.md ├── 03-use-rxcocoa-one.md ├── 04-use-rxcocoa-two.md └── README.md /00-why-rxswift-and-mvvm.md: -------------------------------------------------------------------------------- 1 | # Why RxSwift and MVVM? 2 | 3 | ## Old MVC 4 | MVC 其实是 Apple 推荐的设计模式:[Model-View-Controller](https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html): 5 | 6 | ![ExpectedMVC.png](https://i.loli.net/2017/08/31/59a82af4a5d43.png) 7 | 8 | 但是呢,在实际使用中,经常会发展成这个样子: 9 | 10 | ![Reality.png](https://i.loli.net/2017/08/31/59a82af531c9c.png) 11 | 12 | 1. View 常常持有 Model(你可以检查一下自己的代码,View 是否可以在别处直接复用?) 13 | 2. View 和 Controller 严重地绑在一起,直接导致在一个 ViewController 中充斥着大量的 UI 和业务逻辑代码 14 | 15 | 其后果是难以维护,最终失去控制。那么,是否存在一种更好的设计方案呢? 16 | 17 | ## New MVVM(feat.RxSwift) 18 | MVVM 可以比较好的解决 MVC 中暴露出来的问题,它的模式是这样的: 19 | 20 | ![MVVM.png](https://i.loli.net/2017/08/31/59a829d908db8.png) 21 | 22 | 其优点有: 23 | 1. UI 和业务逻辑解耦,代码均匀的分散,使用灵活 24 | 2. MVVM 提供了更好的封装性,业务逻辑放在了 ViewModel 中,View 只关心 UI(以及必要的渲染数据) 25 | 26 | 其中比较核心的是 ViewModel ,它在 View/ViewController 和 Model 之间搭建起了一个“桥梁”。具体来说: 27 | - ViewModel 是拥有 Model 的,那么当 Model 变化的时候,ViewModel 也会随之改变;而一旦 ViewModel 改变,View 也同样需要更新(值得注意的是,这是单向绑定,唐巧在他的[被误解的 MVC 和被神化的 MVVM](http://blog.devtang.com/2015/11/02/mvc-and-mvvm/)说这是双向绑定是有问题的)。 28 | - View 产生的行为(例如用户的点击动作)也需要传递给 ViewModel 去处理。 29 | - 那么在哪里进行这些绑定呢?答案是:ViewController。 30 | 31 | 桥梁上要是有“动车”就好了:MVVM 配合一个绑定机制效果就好了。而在 Swift 领域,RxSwift 算是首选(备选有 ReactiveCocoa 等)。 32 | 33 | RxSwift 的强大之处将在之后的文章中详细阐述,以下提供了相关链接供上手学习: 34 | 35 | 1. [RxSwift 官方 repo](https://github.com/ReactiveX/RxSwift) 36 | 2. [RxSwift: Reactive Programming with Swift](https://store.raywenderlich.com/products/rxswift) Ray Wenderlich 出的一本不可多得的好书 37 | 3. [View Models at Kickstarter](https://talk.objc.io/episodes/S01E47-view-models-at-kickstarter) 38 | 4. [介绍 MVVM](https://github.com/nixzhu/dev-blog/blob/master/2014-06-10-mvvm.md) Nix 早期翻译的一篇关于 MVVM 的文章 39 | 5. [使用 RxSwift 进行响应式编程](https://academy.realm.io/cn/posts/altconf-scott-gardner-reactive-programming-with-rxswift/) 40 | -------------------------------------------------------------------------------- /01-observable-and-operator.md: -------------------------------------------------------------------------------- 1 | # 关于 Observable 和 Operator 的一些认识 2 | 3 | 关于 RxSwift 的入门学习,在[官方 GitHub 仓库](https://github.com/ReactiveX/RxSwift)上已经提供了很多文档。另外,同样有对应的[中文文档](https://beeth0ven.github.io/RxSwift-Chinese-Documentation/)释出。 4 | 5 | 本篇是谈一下我对 Observable 和 Operator 的一些认识。先提一点题外的,关于 side effects。 6 | 7 | ## Side effects 8 | Side effects 指的是由于更改了当前作用域之外的共享状态值,从而产生的“副作用”。例如,你可能在实际项目里写这样的代码: 9 | ```swift 10 | override func viewDidLoad(_ animated: Bool) { 11 | super.viewDidLoad(animated) 12 | 13 | setupUI() 14 | bindUI() 15 | listenForChanges() 16 | } 17 | ``` 18 | 在 bindUI 中,你可能给特定的 UI 控件绑定了相应的动作。那么,这就产生了一个 side effect:你的 app 在 bindUI 之前以及之后产生了不一样的行为。但是需要明确的是,产生 side effect 并非是不好的!毕竟我们就是需要这些 side effects 从而达到我们想要的效果。我们只是需要对这些 side effects 有一个合理的控制(in a controlled way)。 19 | 20 | 再延伸一点,在 **命令式编程(Imperative Programming)** 中,常会更改外部的可变状态值。而在**函数式编程**中,是不会更改外部的可变状态值的,从而也就不会产生 side effects。而在 RxSwift 中,结合了二者的优点,通过其典型的注册(Subscribe)的方式,以有顺序、声明式的方式对不可更改的数据做处理,提供了解决异步编程难题的一种优雅方案。在深入探讨 RxSwift 之前,需要对 Observable 和 Operator 有个清晰的认识。 21 | 22 | ## Observable 23 | Observable 应当是 RxSwift 里最核心的概念。它可以发出(emit)三种事件(event): 24 | 25 | ![Observable.png](https://i.loli.net/2017/09/03/59ac24b0d80fa.png) 26 | 27 | 值得注意的一点是,Observable 只有被 subscribe 之后才会真正地发出事件。另外,Observable 接收到 terminated 事件的时候,当前对应的 Subscription 会被 dispose 掉。 28 | 29 | ## Operator 30 | [ReactiveX](http://reactivex.io/) 提供了众多 Operator 运算符,RxSwift 当然也不例外。这些运算符基本涵盖了日常开发时的运用场景,例如 map, filter, skip 等等,详细的介绍以及实例在[官网](http://reactivex.io/documentation/operators.html)上也有。在这里着重比较一下 map 和 flatMap 在 Swift 和 RxSwift 中的不同。 31 | ### Map & FlatMap 32 | #### Map 33 | 在 Swift 中,map 是 Sequence 协议中的方法,其作用是对 Sequence 中的元素按照转换函数分别进行映射,并返回映射后的 Sequence。拿 Array 举例: 34 | ```Swift 35 | [1,2,3].map { (num) -> String in 36 | return "\(num)" 37 | } 38 | ``` 39 | 上述 map 函数将会返回 ["1","2","3"]。 40 | 41 | 在 RxSwift 中,map 将 Observable 中发出 next 事件中的 element 按照给定的转换函数进行映射,并返回映射后的 Observable。用图来解释就是: 42 | 43 | ![map.png](https://i.loli.net/2017/09/03/59ac24cb23029.png) 44 | 45 | #### FlatMap 46 | 关于 Swift 中 flatMap 是什么,我推荐看[flatMap 温顾知新 —— 参照 Swift 源码实现讲解](http://www.jianshu.com/p/7deadf85a5a5)这篇文章,解释地十分详细;进阶版解释可以看[Swift 烧脑体操(四) - map 和 flatMap](http://www.infoq.com/cn/articles/swift-brain-gym-map-and-flatmap#)。 47 | 48 | 个人觉得 RxSwift 中的 flatMap 稍微复杂了一点,ReactiveX 的官方文档是这么定义它的: 49 | 50 | > FlatMap: transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable 51 | 52 | 比方说,当一个 Observable 拥有一个 Observable 的子属性时,flatMap 会对子 Observable 的 element 进行变换,然后将变换后的子 Observable 重新展平(flatten)成一个 Observable 并返回(根据定义,当然也可以是一个 Observable 发出的普通的值在 flatMap 中被转换成 Observable,然后这些 Observable 被展平成一个 Observable)。举一个例子: 53 | 54 | 我们有一个 Student 模型,它的结构是这样的: 55 | ```swift 56 | struct Student { 57 | var score: Variable 58 | } 59 | ``` 60 | 在下面的代码中,定义了两个学生 ryan 和 charlotte。而 student 这个 Subject 它是 Student 类型的(Subject 是什么在以后会提到,你现在只需要知道它既是一个 Observer 也是一个 Observable),并对其进行了 flatMap 以及 subscribe,其作用是打印出这个学生的成绩: 61 | ```swift 62 | let ryan = Student(score: Variable(80)) 63 | let charlotte = Student(score: Variable(90)) 64 | 65 | let student = PublishSubject() 66 | 67 | student.asObservable() 68 | .flatMap { 69 | $0.score.asObservable() 70 | } 71 | .subscribe(onNext: { 72 | print($0) 73 | }) 74 | .addDisposableTo(disposeBag) 75 | ``` 76 | 在之后的时间里,如果发生了下面的事件: 77 | ```swift 78 | student.onNext(ryan) 79 | ryan.score.value = 85 80 | 81 | student.onNext(charlotte) 82 | ryan.score.value = 95 83 | 84 | charlotte.score.value = 100 85 | ``` 86 | 将会打印出 80,85,90,95,100。也就是说,最终的效果是,它同时可以“监听”多个 Observable 中的子 Observable 产生的变化,用图表示是这个样子(为了方便手绘): 87 | 88 | ![flatmap.png](https://i.loli.net/2017/09/04/59ac29331c790.png) 89 | 90 | 那么如果你想要只“监听”最新加入的 Observable 的子 Observable 应该怎么办呢?RxSwift 提供了 [flatMapLatest](https://beeth0ven.github.io/RxSwift-Chinese-Documentation/content/decision_tree/flatMapLatest.html) 方法,也就是说,改用 flatMapLatest 后上面的输入将会打印出 80,85,90,100。 91 | 92 | 特别的,flatMap 还适合解决异步返回的问题:(例子来源于[靛青K](https://medium.com/@DianQK/rxswift-%E4%B8%8B%E7%9A%84-map-%E4%B8%8E-flatmap-d0b319aef819)) 93 | ```swift 94 | Observable.just(1) 95 | .map { $0 * 2 } 96 | .flatMap { value -> Observable in 97 | return Observable.create { observer in 98 | DispatchQueue.main.async { 99 | observer.onNext(String(value)) 100 | observer.onCompleted() 101 | } 102 | } 103 | } 104 | ``` 105 | 这也是为什么在 RxSwift 的实际使用中,flatMap 出镜率比较高的原因之一。 106 | 107 | ### CombineLatest & Zip 108 | 比较完了 RxSwift 中的 flatMap 和 Map,再看另一对出场率很高的 combineLatest 以及 zip。在实际开发过程中,常会遇到这样的情况:我们得等到两个或两个以上的 Observable 接受到输入之后,再进行输出。 109 | #### CombineLatest 110 | 先看 CombineLatest 的示例图: 111 | ![combineLatest.png](https://i.loli.net/2017/09/06/59af441b5e43e.png) 112 | 113 | 分别拿到两个 Observable 最新发出的 element,并通过 CombineLatest 中的函数(也就是下面代码中的 resultSelector)转成另一个值,最后返回包含这个值的 Observable。(从图中可以知道,只要有最新的 element 都会发出事件) 114 | 115 | 直接看 CombineLatest 的源码定义: 116 | ```swift 117 | public static func combineLatest 118 | (_ source1: O1, _ source2: O2, resultSelector: @escaping (O1.E, O2.E) throws -> E) 119 | -> Observable { 120 | return CombineLatest2( 121 | source1: source1.asObservable(), source2: source2.asObservable(), 122 | resultSelector: resultSelector 123 | ) 124 | } 125 | ``` 126 | #### Zip 127 | 直接用一张图解释区别吧。(在这时真的感受到一图胜千言...) 128 | 129 | ![zip.png](https://i.loli.net/2017/09/06/59af468078ad1.png) 130 | 131 | 其实还有一个类似的 withLatestFrom,可以自己查阅 [reactivex.io](http://reactivex.io/documentation/operators.html) 上的文档。 132 | 133 | -------------------------------------------------------------------------------- /02-what-is-subject.md: -------------------------------------------------------------------------------- 1 | # 我理解的 Subject 2 | 3 | 再谈 Subject 是什么之前,先回头看一下 Observable。 4 | 5 | ## 再看 Observable 6 | Observable,字面意思:“可观测的”。最常见的用例是: 7 | ```swift 8 | anObservable.subscribe(onNext: { (element) in 9 | // Handle with element 10 | }, onError: { (error) in 11 | // Do something when error occured 12 | }, onCompleted: { 13 | // Do something when completed 14 | }).addDisposableTo(bag) 15 | ``` 16 | Observable 通过 subscribe,在闭包中对相应事件(onNext, onError, onCompleted 等)进行处理。 17 | 18 | 那么问题来了,这个 Observable 是在什么时候发出这些事件的呢? 19 | 20 | 来看看 Observable 的创建方法 create: 21 | ```swift 22 | Observable.create { (observer) -> Disposable in 23 | observer.onNext("1") 24 | observer.onCompleted() 25 | observer.onNext("2") 26 | return Disposables.create() 27 | } 28 | ``` 29 | (在这里,observer 是 AnyObserver 类型(不是 Observer!),它可以将值加入到 Observable 的 event sequence 中) 30 | 通过上述代码,我们用了最原始的创建方法创建了一个 Observable。(当然被 Subscribe 了之后,第二个 next 事件是不会发出的,因为已经被 terminated) 31 | 32 | 通过看源码可以发现,Observable 的 just 以及 from 等一些快捷创建 Observable 的方法等,本质上都是对原始创建方法的封装。 33 | 34 | ## 还有 Observer 35 | Observer,“观察者”。它和 Observable 的关系是: 36 | 37 | An observer subscribes to an Observable. 38 | (在 RxCocoa 中,An observable binds to an observer.) 39 | 40 | > 通俗的来说,Observer 会根据 Observable 发出的相应事件做出相应的行为。 41 | 42 | 关于二者的关系,画了一个示意图: 43 | 44 | ![Image-1.jpg](https://i.loli.net/2017/09/08/59b16d006db04.jpg) 45 | 46 | ## Subject 47 | 如果理清了 Observer 和 Observable 的关系,那么就明白了什么是 Subject : 48 | 49 | **Subject 既是一个 Observer 也是一个 Observable。** 50 | 51 | 分开来看:因为 Subject 是 Observer,那么它可以订阅到(Subscribe to)一个或多个 Observable 上;又因为它是 Observable,那么它可以传递(emit)它观察到的事件或值(当 next 事件时)。 52 | 53 | Subject 既是一个 Observer 也是一个 Observable 解决了什么问题呢?有了 Subject,那么可以它可以在运行时接收事件(Observer 的特性);又可以传递观察到的事件,根据不同的事件做出相应的行为(Observable 的特性)。 54 | 55 | 在后续文章中将会结合 MVVM 设计模式以及具体的实例,再谈。 56 | 57 | Stay tuned! 58 | 59 | -------------------------------------------------------------------------------- /03-use-rxcocoa-one.md: -------------------------------------------------------------------------------- 1 | # 善于使用 RxCocoa(一) 2 | 3 | [RxCocoa](https://github.com/ReactiveX/RxSwift/tree/master/RxCocoa) 是 RxSwift 对 Cocoa 和 Cocoa Touch Framework (以下统称 Cocoa API) 的封装。 4 | 5 | ## 例子 6 | 先看一个具体的例子。譬如有一个搜索框 searchCityName(它是一个 UITextField),可以根据用户的最新的输入发送网络请求。 7 | 8 | ```swift 9 | let search = searchCityName.rx.text 10 | .filter { ($0 ?? "").characters.count > 0 } 11 | .flatMapLatest { text in 12 | // 下面的代码对 text 发送 api 请求,并返回 Observable 13 | return Api.shared.currentWeather(city: text ?? "Error") 14 | .catchErrorJustReturn(Weather.empty) 15 | } 16 | .observeOn(MainScheduler.instance) 17 | .shareReplay(1) 18 | ``` 19 | 20 | 我们得到的 search 的类型是一个 Observable\。 21 | 22 | 为什么使用 flatMapLatest?因为在一般情况下,用户在输入一个地完整的地名之前,往往都会输入其它的字符。那么我们需要的是获取最新的输入,并对其进行网络请求的封装。之后我们只关心这个网络请求的结果。 23 | 24 | ## bind(to:) 25 | bind(to:) 是 RxCocoa 对 RxSwift 里的 subscribe(\_:) 的封装。需要明确的一点是,在 RxCocoa 中的绑定都是**单向**的。在实际使用中,bind(to:) 可以将一个 Observable 绑定到一个 Observer 上(准确的说其实是绑定到一个实现 ObserverType 协议的实体上。) 26 | 27 | 继续看上面的例子,我们对拿到的 search 进行处理,将返回的 cityName 绑定到 cityNameLabel 上: 28 | 29 | ```swift 30 | search.map { $0.cityName } 31 | .bindTo(cityNameLabel.rx.text) 32 | .addDisposableTo(bag) 33 | ``` 34 | 35 | 值得注意的是,这是一个往 UI 控件上的绑定行为,RxCocoa 会检测这一过程是否在主线程进行,如果不在主线程,那么将会闪退。 36 | 37 | ## Units 38 | 39 | RxCocoa 中还有一些特别的 Observable,称为 Units。Units 更适合用来处理与 UI 相关的绑定,它有以下特点: 40 | 41 | - Units 不会发出 error 事件 42 | - Units 默认在主线程上被观察 43 | - Units 默认在主线程上注册 44 | - Units share side effects 45 | 46 | 总结来说,在使用 Units 时会更放心,因为你可能忘记的操作它都帮你做好了。 47 | 48 | Units 主要分两种: 49 | 50 | 1. ControlProperty 和 ControlEvent 51 | 2. Driver 52 | 53 | ControlProperty 既是一个 ObservableType 也是 ObserverType,它主要对 Cocoa API 的属性进行封装,例如 UITextField.rx.text。ControlEvent 是一个 ObservableType,它封装了 Cocoa API 中的一些事件,例如按钮点击。 54 | 55 | Driver 是一个 Observable,由于它是一个 Units,那么它具备 Units 的所有特点。 56 | 57 | 我们可以使用 asDriver(onErrorJustReturn:) 方法将一个 Observable 转成一个 Driver,也可以使用 just, empty 等方法单独生成一个 Driver(和 Observable 类似)。onErrorJustReturn 参数接收一个当 Observable 发出 error 事件时的默认值。这样得到的 Driver 也就不会产生 error 事件。 58 | 59 | 说了这么多,之前文中的代码可以进一步这么优化: 60 | 61 | 1. 当用户点击 enter 键的时候再进行网络请求,减少不必要的请求次数 62 | 2. 使用 Units 精简代码 63 | 64 | 结果如下: 65 | 66 | ```swift 67 | let search = searchCityName.rx.controlEvent(.editingDidEndOnExit).asObservable() 68 | .map { self.searchCityName.text } // 将 Observable 转成 Observable 69 | .flatMap { text in 70 | return Api.shared.currentWeather(city: text ?? "Error") 71 | } 72 | .asDriver(onErrorJustReturn: Api.Weather.empty) 73 | ``` 74 | 75 | 特别的,Driver 的绑定使用 drive(:): 76 | ```swift 77 | search.map { $0.cityName } 78 | .drive(cityNameLabel.rx.text) 79 | .addDisposableTo(bag) 80 | ``` 81 | 82 | 是不是简洁、合理了很多? 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /04-use-rxcocoa-two.md: -------------------------------------------------------------------------------- 1 | # 善于使用 RxCocoa(二) 2 | 使用 RxSwift 进行 iOS 开发,一个难以避开的话题即是 RxSwift 如何与 UITableView 以及 UICollectionView 配合。值得庆贺的是,RxCocoa 已经给我们 3 | 提供了方案,并且在大多数情况下,它可以为我们减少很多不必要的代码书写。 4 | 5 | 在本文中,为了方便起见,仅以 UITableView 举例。 6 | 7 | ## 基本用法 8 | 使用 RxCocoa 来构造 UITableView,你不需要再设置 delegate 和 dataSource。 9 | 10 | 例如,你只需要这样就可以成功地在 UITableView 上展示数据: 11 | 12 | ```swift 13 | lazy var tableView: UITableView = { 14 | let tv = UITableView(frame: .zero, style: .plain) 15 | tv.register(cellType: UITableViewCell.self) 16 | return tv 17 | }() 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | setupUI() // 进行了界面的布局 23 | 24 | // 其中 fetchGoals 返回了一个 Observable<[CustomType]> 25 | viewModel.fetchGoals() 26 | .bind(to: tableView.rx.items) {tv, row, element in 27 | let cell: UITableViewCell = tv.dequeueReusableCell(for: IndexPath(row: row, section: 0)) 28 | cell.textLabel?.text = element.title 29 | return cell 30 | } 31 | .addDisposableTo(bag) 32 | 33 | // cell 上的点击事件 34 | tableView.rx.modelSelected(CustomType.self).subscribe(onNext: { (customItem) in 35 | // Handle with custom item 36 | }).addDisposableTo(bag) 37 | } 38 | ``` 39 | 40 | Then you are all set。 41 | 42 | ## 进阶 43 | 44 | RxCocoa 目前只能处理单个 Section 的情况。如果你在寻求多个 Section 的方案,那么你需要 [RxDataSources](https://github.com/RxSwiftCommunity/RxDataSources)。 45 | 46 | RxDataSources 是 RxSwiftCommunity 下的开源项目,它提供了对 UITableView 和 UICollectionView 更高级的支持。具体用法也很简单,在这就不具体介绍了,可以看它们的 README。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RxNotes 2 | 3 | 专注于 iOS 开发中 MVVM 设计模式的具体实践,另外提到了一点 RxSwift。 4 | 5 | - [Why RxSwift and MVVM?](https://github.com/caiyue1993/RxNotes/blob/master/00-why-rxswift-and-mvvm.md) 6 | - [关于 Observable 和 Operator 的一些认识](https://github.com/caiyue1993/RxNotes/blob/master/01-observable-and-operator.md) 7 | - [我理解的 Subject](https://github.com/caiyue1993/RxNotes/blob/master/02-what-is-subject.md) 8 | - [善于使用 RxCocoa(一)](https://github.com/caiyue1993/RxNotes/blob/master/03-use-rxcocoa-one.md) 9 | - 善于使用 RxCocoa(二) 10 | - 重要的垃圾回收 11 | - 谈谈 ViewModel 的结构设计 12 | --- 13 | 持续写作中。Stay tuned! 14 | 15 | 有任何改进的建议欢迎提 PR,转载请注明出处:)。 16 | 17 | Weibo:[@CaiYue_](http://weibo.com/caiyue233/) 18 | 19 | Twitter: [@caiyue5](https://twitter.com/caiyue5) 20 | --------------------------------------------------------------------------------