[译] iOS 里的 MVVM 和 RxSwift

在本文中,我将介绍 iOS 编程中的 MVVM 设计模式以及 RxSwift。本文分为两部分,第一部分简要介绍了设计模式和 RxSwift 的基础知识,而在 第二部分 里,有一个实现了 MVVM 和 RxSwift 的示例项目。html

设计模式:


首先,咱们为何要使用设计模式呢?简而言之,就是为了不咱们的代码乱成一团,固然这不是惟一的缘由,其中有一个缘由是可测试性。设计模式有不少,咱们能够指出几个很是受欢迎的模式:MVC、MVVM、MVPVIPER。下面的图片将这几个设计模式的分布协做性,可测试性和易用性进行了比较。前端

Compare of design patterns ( from NSLondon )

这些设计模式都有本身的优缺点,但最终它们都能使咱们的代码更清晰、简单而且易于阅读。本文重点介绍 MVVM,我但愿你能在阅读完 第二部分 后着手实现它。react

让咱们简单介绍一下 MVC,而后继续讨论 MVVMandroid


MVC:

苹果官方建议使用 MVC 进行 iOS 编程,若是你有必定的 iOS 开发经验,你可能会熟悉 MVC。这个模式由 Model、ViewController 组成,其中 Controller 负责将 Model 链接到 View。理论上看起来 View 和 Controller 是两个不一样的东西,但在 iOS 的世界中,不幸的是,大多数状况下它们是一回事。固然,在小型项目中,一切彷佛都符合规律,可是一旦你的项目变得庞大,Controller 因实现了大部分业务逻辑而变得臃肿,这会致使代码变得混乱,可是若是你能正确编写 MVC,并尽量地把 Controller 里的东西解耦,大多数状况下这个问题将获得解决。ios

MVC from apple docs
官方文档中的 MVC

MVVM:

picture from github

MVVM 表明 Model、ViewViewModel,其中,View 和业务逻辑实现了 Controller,View 以及动画,ViewModel 里则是 api 的调用。实际上 ViewModel 这层是 Model 和 View 之间的接口而且它给 View 提供数据。有一点要注意的是,若是你在 ViewModel 的文件中看到如下代码,那你多是在某处犯了一个错误:git

import UIKit
复制代码

这是由于 ViewModel 不该该和 View 有任何牵连,在 第二部分 中咱们将借助一个例子来研究这篇文章。github

RxSwift:


MVVM 的一个特性是数据和 View 的绑定,而 RxSwift 就很完美地实现了这一点。固然,您也可使用 delegate,KVO 或闭包执行此操做,但 Rx 的有一个特性就是,它是一种思想,在不少语言里通用,所以它与编程语言关系并不大。你能够在 这里 找到它支持的语言列表。如今在这一部分咱们将解释 RxSwift 的基础知识,固然,它们也是 Rx 世界的基础知识。而后在 第二部分 中,咱们将凭借 MVVM 使用 RxSwift 建立一个项目。编程

响应式编程:

既然 RxSwift 是基于响应式编程的,那这到底是什么意思呢?swift

在计算机中,响应式编程或反应式编程(Reactive programming)是一种面向数据流和变化传播的编程范式。这意味着能够在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值经过数据流进行传播。—— 维基百科后端

也许你在读完后对本段的任何内容仍是不怎么了解,那下面咱们就经过如下的例子来进一步理解它:

假设你如今有三个变量(a,b,c):

var a: Int = 1
var b: Int = 2
var c: Int = a + b // 输出 3
复制代码

如今若是咱们将 a 从 1 改成 2 而且咱们打印 c,它的值仍然是 3。可是在响应式编程的世界中一切都变得不同了,c 的值取决于 ab,这意味着若是你把 a 从 1 改成 2,那 c 的值就会自动从 3 变为 4 而不须要你自行更改。

var a: Int = 1
var b: Int = 2
var c: Int = a + b // 输出 3
a = 2
print("c=\(c)")
// 输出 c=3
// 在响应式编程中 c=4
复制代码

如今让咱们开始学习 RxSwift 的基础知识:

在 RxSwift(固然还有其余 Rx)的世界中,一切事物都是事件流,其中包括 UI 事件和网络请求等等。请切记这一点,我将用现实生活中的例子来解释:

你的手机是一个 可观察对象(Observable),它会产生一些事件,例如铃声或者推送通知等,这会让你引发注意,事实上你订阅(subscribe)了你的手机,并决定如何处理这些事件,好比你有时候删除或者查看一些通知,事实上这些事件是一些 信号(signal),而你是作出决定的 观察者(Observer)

下面让咱们来代码来实现它:

Observable 和 Observer(订阅者):

在 Rx 世界中,一些变量是 Observable,而另外一些是 Observer(或订阅者)。

所以 Observable 是通用的,若是它确遵循了 ObservableType 协议,你能够监听你想要的任何类型。

如今让咱们定义一些 Observable:

let helloObservableString = Observable.just("Hello Rx World")
let observableInt = Observable.of(0, 1, 2)
let dictSequence = Observable.from([1: "Hello", 2: "World"])
复制代码

在上面例子中,咱们分别定义了 Observable 类型的 String,Int 和 Dictionary,如今咱们应该 订阅 咱们的 Observable,这样咱们就能够从发出的信号中读取信息。

helloObservableString.subscribe({ event in
    print(event)
})
// 输出:
// next(Hello Rx World)
// completed
复制代码

你可能在想输出为何会出现 nextcompleted,为何 ‘hello world’ 就不能好好打印一个字符串,我得说这就是 Observable 最重要的特性:

实际上每一个 Observable 都是一个 序列,与 Swift 里 Sequence 的主要区别在于 Observable 的值能够是异步的。若是你不理解这点并不重要,可是但愿你能理解下面的描述,我以图的方式呈现了这一特性:

sequence of events

在上面的图中,咱们有三个 Observable,第一行是 Int 类型,从 1 数到 6。在第二行是 String,从 ‘a’ 到 ‘f’,随即发生了一些错误。最后一行是 Observable 类型的手势,它尚未完成,还在继续。

这些显示 Observable 变量事件的图像叫作大理石图。想要了解更多信息,您能够访问 这个网站 或从 App Store 下载 这个 App(它也是开源的 👍😎,这里 有 App 的源代码)。

在 Rx 世界中,对于每一个 Observable,都是由 3 种可能的枚举值组成:


  1. .next(value: T)

  2. .error(error: Error)

  3. .completed

当 Observable 添加值时,调用 next 并经过相关的 value 属性(1 到 6,‘a’ 到 ‘f’)将值传递给 Observer(或订阅者)。

若是 Observable 遇到了错误❌,则发出错误事件而后完成(‘f’ 以后的 X)。

若是 Observable 完成,则调用 completed 事件(6 以后)。

若是你想要取消订阅一个 Observable,咱们能够调用 dispose 方法,或者若是你想在你的 View deinit 的时候调用这个方法你应该使用 DisposeBag 在你的类反初始化时来进行你想要的操做。在这里强调一点,若是你忘记使用 dispose 的话会致使内存泄漏☠️💀。例如,你应该这样订阅 Observable:

let disposeBag = DisposeBag()
helloObservableString.subscribe({ event in
    print(event)
}).disposed(by: disposeBag)
复制代码

如今让咱们看看将 Rx 与函数式编程相结合有多完美。假设你有 Observable 的 Int 而且你订阅了它,如今 Observable 会给你一堆 Int,你可使用不少方法改变来自 Observable 的发出信号,例如:


Map:

你可使用 map 方法让信号在到达订阅者以前作出一些改变。例如,咱们有 Observable 的 Int,它发出了 2,3,4 三个数字,如今咱们想要它们在传给订阅者以前都乘以 10,咱们能够这么作:

map marble

Observable<Int>.of(2, 3, 4).map { value in
        return value * 10
    }.subscribe(onNext: {
        print($0)
    }).disposed(by: disposeBag)
// 输出:20 30 40
复制代码

Filter:

您可能又会想是否能让它们在传给订阅者以前过滤掉一些值,例如,过滤掉示例中大于 25 的数字:

Observable<Int>.of(2, 3, 4).map { value in
        return value * 10
    }
    .filter( return $0 > 25 )
    .subscribe(onNext: {
        print($0)
    }).disposed(by: disposeBag)
// 输出:30 40
复制代码

FlatMap:

又好比您有两个 Observable 对象,而且您但愿将它们合二为一:

在上面的例子中,Observable A 和 Observable B 被组合在一块儿并造成一个新的 Observable:

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 或 debounce:

这两个方法是搜索中最有用的方法之一。例如,用户想要搜索单词,您可能在用户输入每一个字符时都调用搜索 API。若是用户快速键入,这样的话你就会进行不少没必要要的请求。为了达到此目的,正确的方法应该是在用户中止键入时调用搜索 API。这时您可使用 debounce:

usernameOutlet.rx.text
    .debounce(0.3, scheduler: MainScheduler.instance)
    .subscribe(onNext: { [unowned self] text in
        self?.search(withQuery: text)
    }).addDisposableTo(disposeBag)
复制代码

在上面的例子中,若是用户名 TextField 的内容在 0.3 秒内发生变化,则这些信号不会到达订阅者,所以不会调用搜索方法。只有当用户在 0.3 秒后中止输入,订阅者才会收到信号并调用搜索方法。

distinctUntilChanged 功能对变化很敏感,这意味着若是两个信号在信号没有变化以前获得相同的信号,它将不会被发送给用户。


Rx 世界比你想象的要大得多,我在 文章的第二部分 中讲述了我认为须要的一些基本概念,里面有一个使用 RxSwift 的实际项目。

来自 raywenderlich 的 RxSwift 入门文章很是棒,我强烈推荐阅读。

你可能不会在文章中注意到 RxSwift,由于它是 Swift 的高级概念之一,你可能天天都要阅读不一样的文章才能弄明白它。您能够经过 此连接 看到 RxSwift 部分中的几篇好文章。

你能够经过 文章的第二部分 将 Rx 引入到 MVVM 的实际项目中,由于经过实例你将更好、更容易地理解 RxSwift 的概念。

你能够经过 Twitter 或者发送 email 来联系到本文做者,邮箱是 mohammad_z74@icloud.com ✌️

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索