本教程在向读者介绍RxSwift库以及如何使用Swift编写响应式的iOS App.
问题来了, 什么是RxSwift呢? 这里有一段介绍:
RxSwift是一个库,通过使用可观察序列和函数式操作符组合异步和基于事件的代码,允许通过调度程序参数化执行。
听起来好像很复杂, 但是请别担心, 如果我们一开始就要去了解编写响应式的程序, 理解程序背后的概念, 以及查看大量的常用术语, 那肯定是让人很头疼的.
但是本教程的目标是: 通过解释如果使用每一个API, 并且简单的介绍它们在iOS app中的实际使用, 然后再慢慢介绍RxSwift的各种API以及Rx的概念.
现在我们将从RxSwift的基本特性开始, 然后逐渐学习中级以及高级的课题, 在学习的过程中, 我们会花大量的时间练习新概念, 让你可以更加容易的掌握RxSwift, Rx是一个非常广泛的课题, 不可能所有东西都包含在本教程中, 刚开始的目的是让你对库有一个坚实的了解, 然后你在根据所学的Rx知识再扩展自己得Rx技能.
现在, 我们从一个简单, 可理解的定义开始, 并在本章节后面讨论反应时编程的课题是, 逐步发展到一个更好更宽广的解释.
RxSwift本质上简化了异步程序的开发, 它允许代码对新数据作出反应, 并且以顺序的, 隔离的方式处理它.
作为一个iOS App开发人员, 这样子的解释更加通俗易懂, 而且可以明确的告诉你RxSwift的作用.
即使现在你仍然不清楚细节, 也可以知道RxSwift就帮助你来编写异步代码, 而且我们都知道要开一个好的, 确定性的异步代码是非常困难的, 如果有其他库可以帮助我们的话, 那肯定是非常受欢迎的.
如果我们试图用一种简单, 实际的语言来解释什么是异步编程, 可能会得到以下内容.
iOS App, 在任何时候都可能在做以下的任何事情, 甚至更多:
- 对按钮点击做出反应
- 显示键盘或者是隐藏键盘
- 从网上下载一张大的照片
- 将数据存储在本地的磁盘中
- 播放音频
所有的这些事情几乎都是在同一时间发生, 当键盘隐藏键盘的时候, 一直到键盘完全的隐藏, 应用程序中的音频是不会暂停的, 是吧?
并且程序中的所有不同的部分都不会互相阻塞执行, 因为iOS提供了各种的API, 允许我们在不同的线程上执行不同的工作, 并在设备不同的CPU核心上执行它们.
然而, 当我们真正去编写这部分异步代码的时候是非常困难的, 尤其是当不同的代码需要处理相同的数据时, 很难想象哪段代码先更新数据, 或者是哪段代码读取最新的值.
苹果在iOS SDK中提供了很多API来帮助我们编写异步代码, 并且我们在项目中也已经使用过这些工具库, 但可能我们并没有考虑过这些工具库对编写移动应用程序是如此的重要.
我们常用的工具库:
- NotificationCenter: 通知中心, 可以在任何地方对任何感兴趣的事件发生时执行一段代码, 比如用户更改了屏幕的方向, 或者是让键盘显示或隐藏.
- Delegate: 允许你定义一个对象代表另一个对象与另一个对象协作. 比如在我们的App委托中, 你定义了一个当新的远程通知到达时应该要做什么, 但是我们并不知道这段代码在什么时候执行, 也不知道它要执行多少次.
- Grand Centeral Dispatch: 帮助我们执行抽象的工作, 我们可以在串行队列中安排顺序执行的代码, 或者在具有不同优先级的不同队列上运行大量的并发任务.
- Closures: 创建可以在代码中传递独立代码片段, 这样其他对象就可以决定是否执行它, 执行多少次以及在什么地方执行它.
由于大多数典型代码都是异步执行某些工作的, 而且所有的UI事件本质上都是异步的, 所以不可能假设整个应用程序代码的执行顺序.
毕竟应用程序的代码根据不同的外部因素, 代码运行的顺序可能完全不同, 比如用户的输入, 网络活动或其他的系统事件(除非有一群机器人没日没夜的在测试我们的应用程序, 这样子我们就可以方方面面都可以考虑到了)
当然, 并不是说编写好的异步代码是不可能的, 毕竟上面所列出来苹果API都是非常先进, 非常专业的, 与其他平台相比, 它们的功能都非常强大.
但问题是, 复杂的异步代码变得非常的难写, 部分原因也是因为苹果的SDK提供了多种API:
如果我们单独的使用Delegate, 或者是Closures, NotificationCenter等等, 由于异步API之间并没有共同的语言, 这样子我们阅读和理解代码并对其执行进行推理都是非常困难的.
为了结束这个话题并将讨论放到更大的话题中, 我们来看看两段不同的代码: 一段是同步代码和一段异步代码.
为数组的每个元素执行操作, 这是我们在开发中经常都会遇到的, 它是一个非常简单但很可靠的应用程序逻辑构建块, 因为它保证了两件事:
- 同步执行
- 当我们在数组上迭代时, 集合是不可变的
花点时间想想这意味着什么, 当我们迭代一个集合时, 我们不需要检查所有元素是否存在, 也不需要回滚以防止另一个现成在集合的开始插入一个元素.
如果你想在for循环的这些方面玩得更多一些, 可以在Playground上试试这个:
数组在for循环中是可变的吗? 循环遍历的集合是否会更改? 所有命令的执行顺序是怎么样的? 如果需要, 可以修改号码吗?
假设每次迭代都是对按钮点击的反应, 也就是说用户每次点击按钮时, 应用程序都会打印出数组中的下一个元素:
请与前面的代码相同的上下文中考虑这段代码, 当用户点击按钮时, 会打印出数组的所有元素吗? 答案是不确定的.
或者, 另一段代码可能会在继续之后再集合的开头插入一个新元素?
假设只有才会更改currentIndex, 但是另一段代码也可能修改currentIndex, 有可能是你同事给你在后面补的刀.
讲到这里, 你可能已经意识到编写异步代码的核心问题:
- 执行工作的顺序
- 共享可变的数据
幸好, 这些都是RxSwift的强项!
接下来, 你将开始了解RxSwift是如何工作的, 以及它解决了哪些问题.
RxSwift中的有些术语与异步, 响应式和函数式编程紧密的绑定在一起, 如果你先理解以下的基本术语, 就会更加容易理解.
一般来说, RxSwift试图解决一下的问题:
状态是比较难定义的, 要理解状态, 可以从一下的实际示例去理解.
当我们启动电脑的时候, 它可以运行的很好, 但是在我们使用了几天或者是几周之后, 它可能会表现得奇怪, 有时候还会耍小脾气, 突然不工作了, 但是电脑的硬件和软件是保持不变的, 一旦我们重启了电脑之后, 同样的硬件和软件又开始正常工作了.
内存中的数据, 存储在磁盘的数据, 响应用户的输入, 从云服务获取数据后留下的所有追踪, 这些所有的东西都是电脑上的状态.
管理应用程序的状态, 特别是在多个异步组件之间共享状态, 是我们在本教程中要去学会处理的问题之一.
命令式编程是一种使用语句改变程序状态的编程规范, 就好像我们逗狗时使用的命令语言一样, "去拿XX", "坐下", "躺下"等等, 使用命令代码告诉应用程序确切的时间和怎么做.
命令式代码与计算机能够理解的代码类似, CPU所做的就是遵循冗长的指令序列, 问题来了, 对于人类来说, 为复杂的异步应用程序编写命令式代码是非常具有挑战性的, 尤其是涉及到共享可变状态时.
例如, 以iOS的ViewController的中的代码为例:
我们不知道这些方法是做什么的, 它们是否要更新ViewController本身的属性? 让人更加不解的是, 它们的调用顺序是否正确, 如果有人无意中交换了这些方法的调用顺序, 并将更改的代码提交了Pull Request, 那么现在所看到的应用程序行为可能就会有所不同.
既然我们队可变状态和命令式编程有了更多的了解, 那么我们就可以将这两件事的大多数问题归结为副作用.
副作用用来表示对代码当前范围之外的状态的任何更改. 例如, 参考上面示例中的最后一段代码可能将某种事情处理程序附加到某些UI组件当中, 这会产生一个副作用, 因为它改变了View的状态, 应用程序在执行之前以一种方式执行, 之后又以另一种方式执行.
每当修改存储在磁盘上的数据或者更新屏幕上的UILabel文本时, 都会产生副作用.
副作用本身其实并不算是坏事, 毕竟产生副作用是任何程序的最终目标, 我们需要在程序执行完成后以某种方式更改世界的状态.
运行一段时间, 什么都不做, 会使应用程序变得非常的无用.
我们要以一种可控的方式去产生副作用, 需要能够确定哪些代码片段会导致副作用, 以及哪些代码片段只处理和输出数据.
RxSwift试图通过处理以下几个概念来解决上面列出的问题.
在命令式编程中, 可以随意改变状态, 在函数式编程中, 我们的目标是最小化引起副作用的代码. 但是我们没有生活在一个完美的世界里, 那么平衡就在两者之间. RxSwift结合了命令式代码和函数式代码的一些最佳方面.
声明式代码允许我们定义行为片段, 只要有相关事件, RxSwift就会运行这些行为, 并且提供一个不可变的, 独立的数据块来处理这些行为事件.
通过这种方式, 我们可以使用异步代码, 但是我们的目标是做出与简单的for循环相同的假设: 我们正在处理不可变的数据, 并且可以以顺序说的, 确定性的方式执行代码.
反应系统是一个相当抽象的数据, 涵盖了web或iOS应用程序, 这些应用程序大多都具备以下特性:
- 响应性: 始终保持最新的UI, 表示应用程序最新的状态.
- 弹性: 每个行为都是独立定义的, 提供灵活的错误恢复.
- 灵活性: 代码处理不同的工作负载, 通常实现一些特性, 如延迟驱动数据的收集, 事件节流和资源共享.
- 消息驱动: 使用消息通信来驱动来提高组件的可重用性和隔离性, 解耦类的生命周期和实现.
现在我们已经很好的理解了RxSwift可以帮助我们解决什么问题和如何的去解决这些问题, 那么现在可以讨论Rx的构建快以及它们何如协同工作了.
响应式变成不是一个新概念, 它已经存在了相当长的时间, 但它的核心概念在十年中有了明显的回归.
在此期间, web应用程序变得更加的复杂, 并面临着管理复杂异步UI的问题, 在服务端, 响应式系统(如上所述)已经成为了必须.
微软的一个团队承担了解决我们在本章中讨论的异步, 可伸缩, 实时应用程序开发问题的挑战. 他们开发了一个独立于公司核心团队的库, 并在2009年前后提供了一个新的客户端与服务端的框架, 称为的响应式扩展Reactive Extensions for .NET(Rx).
在的3.5版本中是可选的插件, 后来在4.0版本之后称为核心库, 自2012年以来, 它一直都是一个开源的组件, 开源代码允许其他语言和平台重新实现相同的功能, 这使得Rx成为扩平台的标准.
今天有RxJS, RxKotlin, Rx.NET, RxScala, RxSwift等等, 所有这些苦都努力实现基于响应式扩展标准的相同行为和相同表达性的API, 最终, 使用RxSwift创建iOS应用程序的开发人员也可以在Web上自由的与使用了RxJS的其他开发人员讨论应用程序的逻辑.
与最初的Rx一样, RxSwift也可以处理目前为止所涉及的所有概念: 处理可变装, 允许我们组合事件的序列, 并改进代码的隔离, 可重用性和解耦等体系结构概念.
让我们重温一下这个定义:
RxSwift找到了传统命令式Cocoa代码和纯粹函数式代码之间的最佳平衡点, 它允许我们通过使用不可变的代码定义确定的, 可组合的方式处理异步输入片段来响应事件.
我们可以通过ReactiveX阅读关于Rx实现家族的更多信息. 这是关于Rx操作符和核心类的文档的中心存储库. 这也可能是你第一个注意到Rx电鳗标志的地方.
在本教程中, 我们将涵盖使用RxSwift开发的基本该你拿, 以及如何在应用程序中使用它们的实际示例.
Rx代码的三个构建快是Observable, Operator和Schedulers, 下面的小节将详细介绍这些内容.
类提供了Rx的基础代码: 拥有异步产生事件的能力, 可以"装载"不可变类型的通用数据, 它允许其他对象订阅该事件, 或者是values, 然后发出另一个对象.
类允许一个或多个观察者实时响应任何事件, 并更新应用程序的UI, 或者处理和利用新传入的数据.
ObservableType协议(符合该协议)非常简单. 一个可观察对象只能发射(和观察者可以接收)三种类型的事件:
- A next event: "carries"(或"next")最新的数据事件, 这是观察者"接收"的方式, 在发出终止事件之前, 可以发出不确定数量的值.
- A completed event: 此事件成功终止事件序列. 它意味着Observable对象成功的完成了它的生命周期, 并且不会发出额外的事件.
- An error event: Observable对象以错误的事件结束, 不会发出额外的事件.
当讨论异步事件随着事件的推移而发出时, 我们可以在时间轴上可视化一个Observable的整数序列, 如下所示:
一个Observable可以发出三个可能事件的简单契约可以是Rx中的任何事情, 因为它非常的普遍, 我们可以用它来创建非常复杂的应用逻辑.
由于Observable契约没有对Observable对象或者观察者的本质做出任何的假设, 因为使用事件序列是最终解耦的实践.
我们永远不需要使用Delegate或者Block来允许类与类之间的通信:
为了了解一些实际情况, 我们将研究两种不同的Observable序列: 有限序列和无限序列.
一些Observable的序列发出零个, 一个或者是多个值, 在稍后的时刻, 要么成功后终止序列, 要么就发生错误后终止序列.
在iOS应用程序中, 考虑从网上下载文件的代码:
- 首先, 开始下载并开始观察传入的数据.
- 然后, 当文件的一部分进入时, 我们将会不断的接收数据块.
- 如果网络中断, 下载将会停止, 链接将超时并出现错误.
- 另外, 如果成功的下载了所有文件的数据, 它将成功后终止序列.
该工作流程准确的描述了经典Observable的生命周期, 代码如下:
返回一个Observable的实例, 该实例在网上传输数据块时发出数据.
我们可以通过提供onError闭包订阅错误, 在该闭包中, 可以显示在UIAlertController中, 或者执行其他的操作.
最后, 处理一个已完成的事件, 我们则需要提供一个onCompleted闭包, 在该闭包中, 我们可以push一个新的UIViewController来显示下载的文件, 或者其他的逻辑处理.
与文件下载活动不同, 这些活动要么自然的终止, 要么就是强制终止, 有一些序列是无限的, 通常UI事件就是这种无限的Observable序列:
例如, 考虑到我们需要对应用程序中的改变设备方向时需要作出反应:
- 我们将我们的类作为观察者, 并添加来自NotificationCenter的UIDeviceOrientationDidChange的通知.
- 然后需要提供一个方法回调来处理方向的更改, 它需要从UIDevice获取当前的朝向并对最新的值作出相应的反应.
这一系列的方向变化并没有一个自然的终点, 只要有设备, 就可能有一系列的方向变化, 此外, 有序序列实际上是无限的, 所以在开始观察它时会有一个初始值.
用户可能从来没有旋转过他们的设备, 但这并不意味着事件序列已经终止了, 它只是没有发出事件而已.
在RxSwift中, 我们可以这样写代码来处理设备的朝向:
是一个虚构的控件属性, 它生成一个Observable(这个很简单, 我们将在下一章节中学习到). 我们订阅它根据当前的方向来更新App UI, 我们可以直接跳过onError和onCompleted, 因为这些事件永远都不会从Observable对象发出.
ObservableType和Observable类实现了大量抽象异步工作的离散部分的方法, 这些方法可以组合在一起实现复杂的逻辑. 因为它们是高度解耦和可组合的, 所以这些方法通常称作为操作符(Operators).
由于这些操作符主要接受异步的输入, 并且只产生输出而不产生副作用, 所以它们可以很容易的组合在一起, 就像拼拼图一样, 并且可以构建更大的画面.
例如: 以简单的数学表达式(1 + 2) * 3 - 4为例:
以一种清晰的, 确定的方式, 我们可以将操作符*, (), +和-按照预定的顺序作为它们输入的数据块, 获取它们输出的结果并继续处理表达式, 直接解析完成为止.
以某种类似的方式, 我们可以将Rx操作符应用Observable对象发出的事件, 以确定的处理输入和输出, 知道表达式解析为最终值, 然后我们就可以使用该值来做其他的操作:
下面是关于一个观察方向变化的例子, 调整后我们就可以看到一些常见的Rx操作符:
每次当生成或值, Rx将对发出的数据块用对应的操作符进行处理.
首先, filter只允许非的值通过. 如果设备处于横向模式, 那么订阅的代码则不会被执行, 因为有filter来抑制这些事件.
在值的情况下, map操作符将获取方向的类型转换为一串衣服穿输出——文本"Portrait is the best!"
最后, 使用subscribe进行订阅, 将我们处理好的字符串, 通过调用一个方法显示在UIAlertController中.
操作符也是可以高度组合的——它们总是将数据作为输入和输出的结果, 因此我们可以轻松地以许多不同的方式将它们链接起来, 从而实现比单个操作符多出更多的功能!
在阅读本教程中, 我们会慢慢了解到更多复杂的操作符, 它们抽象了更复杂的异步代码.
线程调度相当于Rx的分派队列——更加的容易使用.
RxSwift附带了许多预编译的调度器, 涵盖99%的使用场景, 因为RxSwift永远都不希望需要我们自己去创建调度器.
事实上, 这个教程里的前半部分中的大多数示例都非常简单, 通常都是处理观察数据和更新UI, 所以在学习了基础知识之前, 我们不会去研究调度程序.
例如, 希望可以指定在SerialDispatchQueueScheduler上观察下一个事件, 该调度程序使用Grand Central Dispatch在指定队列串行的运行代码.
ConcurrentDispatchQueueScheduler将同时运行我们的代码, 而OperationQueueScheduler将允许我们在指定的操作队列上订阅调度器.
这里说一句谢谢RxSwift, 我们可以在不同的调度器调度相同订阅的不同代码片段, 从而获取最佳的性能.
RxSwift将在订阅(在下面图片的左侧方)和调度程序(在下面图片的右侧方)充当调度器, 将代码块发送到正确的上下文中, 并且允许他们无缝间的处理彼此输出的数据.
要阅读此图, 请按照在不同的调度称重的顺序(1, 2, 3, ...)跟踪彩色的代码片段, 例如:
- 蓝色的网络订阅从一段代码(1)开始, 这段代码运行在基于自定义操作队列的调度程序上.
- 这一块的数据作为下一个工作块(2)的输入, 下一个工作块(2)运行在另一个调度程序上, 该调度程序位于并发后台的GCD队列上.
- 最后, 在主线程的调度程序上调度最后一段蓝色的代码块, 将这些处理好的数据用于UI的更新.
这样子解释起来非常的有趣并且非常的方便, 而且我们也不需要在调度程序上花费太多的精力.
值得一提的是, RxSwift不会以任何的方式改变应用程序的架构, 它主要是处理事件, 异步数据序列和通用通信.
我们可以使用Rx创建应用程序, 方法可以是实现Apple developer文档中定义的MVC(Model-View-Controller)架构, 也可以选择实现MVP(Model-View-Presenter)或者是MVVM(Model-View-ViewModel).
如果你想的话, RxSwift对于实现你自己得单向数据流架构也非常有用.
值得注意的是, 我们绝对不需要从新开始写一个项目, 并且使它成为一个响应式应用程序, 可以慢慢的重构迭代现有项目的各个部分, 或者在为应用程序构建新功能时简单的使用RxSwift.
微软的MVVM体系结构是专门为在提供数据绑定的平台上创建事件驱动软件而开发的, RxSwift和MVVM确实可以非常好的结合在一起, 在本教程的最后, 我们会使用RxSwift去实现这种模式.
MVVM和RxSwift之所以能够很好的结合在一起, 是因为ViewModel允许我们公开Observable属性, 我们可以直接绑定到UIViewController的胶水代码中的UIKit控件, 这会让数据模型绑定到UI非常的容易:
本教程所有的其他示例都会以MVC的架构去实现, 以保证代码简单易懂.
RxSwift是通用的, 并且是与平台无关的Rx规范的实现, 因此它对任何Cocoa或者特定的UIKit的类一无所知.
RxCocoa是RxSwift的伙伴库, 包含了帮助我们开发UIKit和Cocoa的类, 除了提供一些高级类之外, RxCocoa还向许多UI组件添加了响应式扩展, 这样子我们就可以订阅各种UI事件.
例如, 很容易使用RxCocoa订阅UISwitch的状态变化, 代码如下:
RxCocoa添加了rx, 将isOn属性(以及其他属性)订阅到UISwitch类, 以便可以将有用的事件订阅为响应式Observable序列.
此外, RxCocoa将rx的明明空间添加到了UITextField, URLSession, UIViewController等等, 甚至允许我们在这个命名空间下定义属于我们自己的响应式扩展, 在这个教程的后面我们会了解到.
由于RxSwift是开源的, 我们可以通过https://github.com/ReactiveX/RxSwift免费获得.
RxSwift是基于MIT协议下发布的, 健儿颜值, 它允许我们在现有的基础上将库使用在免费或者是商业软件中, 与其他所有经过麻省理工学院授权的软件一样, 版本声明应该包含在我们发布的所有应用程序中.
在RxSwift存储库中又很多值得探索的地方, 除了RxSwift和RxCocoa库之外, 我们还可以找到RxTest和RxBlocking, 他们将允许我们为RxSwift代码编写测试.
除了所有优秀的源代码(绝对值得我们一看), 我们还会找到Rx.playground, 这里面演示需要交互操作符, 还可以看看RxExample, 在这个Demo中, 它在演示的过程中实现了许多的概念.
在项目中包含RxSwift/RxCocoa的最简单方法是通过CocoaPods和Carthage, 甚至还可以使用Swift Package Manager来管理.
本教程使用的是CocoaPods, 哪怕是你以前没有用过也无所谓, 但是请在看本教程的时候确保使用CocoaPods.
我们可以像其他的pod库一样通过CocoaPods安装RxSwift, 正常的Podfile应该是这样子的:
当然, 我们可以只导入RxSwift和RxCocoa, 其他的库如果要使用, 则可以在GitHub里找到.
至于Carthage这里就不做任何的展示了, 有兴趣的朋友可以自行去寻找资料
RxSwift的项目非常的又活力, 不仅仅是因为Rx让我们可以更加简单的创建应用程序, 还因为围绕着这个项目所形成的社区.
RxSwift社区非常友好, 开发, 并且热衷于讨论模式, 常见的技术还有互相帮助.
除了官方所提供的RxSwift库, 我们还可以在Community找到更多由Rx爱好者所提供的项目.
更多的Rx库和实验就像雨后的蘑菇一样, 我们可以在RxSwiftCommunity找到
如果想认识许多对RxSwift感兴趣的人, 也可以通过Slack专用的频道: RxSwift for Slack.
这个频道里有几千名用户在相互帮助或讨论RxSwift以及其他配套库的潜在新特性, 还会有关于RxSwift的博客文章, 会议演讲等等.
通过这篇文章, 我们知道了命令式编程, 函数式编程的一些基本概念, 也初步认识了RxSwift可以给我们带来什么样的好处, 可以更简单, 更省事的开发我们想开发的应用程序.
那么接下来就开始学习RxSwift的基本知识吧.
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/6226.html