0%

RxSwift系列(一): 第一部分:RxSwift入门 - 1、初识RxSwift

本书旨在向读者介绍RxSwift库并进行编写使用Swift的响应式iOS应用程序。但是RxSwift到底是什么? 这是一个很好的定义:

RxSwift是一个库,用于通过使用可观察的序列和功能样式运算符来组合基于异步和事件的代码,从而允许通过调度程序进行参数化执行。

听起来复杂吗? 不用担心。 编写响应式程序,理解其背后的许多概念以及浏览许多常用的相关术语可能会令人生畏-尤其是如果你尝试一次全部使用它,或者当你没有一次将其介绍时。 结构化的方式。

这就是本书的目标:通过说明如何使用每种API,然后介绍它们在iOS应用中的实际用法,逐步向你介绍各种RxSwift API和Rx概念。

你将从RxSwift的基本功能开始,然后逐步学习中级和高级主题。 随着时间的推移,花一些时间广泛地练习新概念将使到本书结尾时更容易掌握RxSwift。 Rx的主题范围太广,无法在一本书中完全涵盖。 相反,我们旨在为你提供对该库的扎实了解,以便你可以继续自己开发Rx技能。

我们还不太确定RxSwift是什么,是吗? 让我们从一个简单易懂的定义开始,并逐步发展成为更好,更具表现力的定义,当我们在本章后面的“响应式编程”主题中进行讨论时。

本质上,RxSwift通过允许你的代码对新数据做出反应并以顺序,隔离的方式对其进行处理,从而简化了异步程序的开发。

作为iOS应用程序开发人员,与你在本文前面阅读的第一个定义相比,这应该更加清楚,并告诉你有关what RxSwift的更多信息。

即使你仍然不清楚细节,RxSwift仍可以帮助你编写异步代码。 而且你知道开发好的,确定性的异步代码很困难,因此非常欢迎任何帮助!

异步编程简介

如果你试图用一种简单的,脚踏实地的语言来解释异步编程,那么你可能会想到以下内容。

iOS应用随时可能执行以下任何操作或更多操作:

  • 对按钮的点击做出反应
  • 为键盘动画,因为文本字段失去焦点
  • 从互联网下载大照片
  • 将数据位保存到磁盘
  • 播放音频

所有这些事情似乎是同时发生的。 每当键盘动画离开屏幕时,你的应用中的音频就会在动画完成之前不会暂停,对吗?

程序的所有不同部分都不会彼此阻塞执行。 iOS提供了各种API,使你可以在不同的线程上执行不同的工作,并跨设备CPU的不同核心执行它们。

但是,编写真正可以并行运行的代码相当复杂,尤其是当不同的代码位需要处理相同的数据时。 很难争论哪个代码段首先更新数据,或者哪个代码读取最新值。

Cocoa和UIKit异步API

Apple在iOS SDK中提供了许多API,可帮助你编写异步代码。

你已经在项目中使用了这些功能,可能没有再三考虑,因为它们对于编写移动应用程序至关重要。

你可能已经使用了以下大多数功能:

  • NotificationCenter:在感兴趣的事件发生时(例如,用户更改设备的方向或屏幕上显示或隐藏的软键盘)执行代码。

  • 委托模式:使你可以定义一个代表另一个对象或与另一个对象协同工作的对象。例如,在你的应用程序委托中,你定义了当新的远程通知到达时应该发生的情况,但是你不知道何时执行这段代码或执行多少次。

  • GCD:帮助你抽象化工作的执行。你可以安排要在串行队列中顺序执行的代码,也可以在具有不同优先级的不同队列上同时运行多个任务。

  • 闭包:创建可在代码中传递的分离的代码段,以便其他对象可以决定是否执行,执行多少次以及在何种情况下执行。

由于你大多数典型代码将异步执行某些工作,并且所有UI事件本质上都是异步的,因此无法以完整的应用代码执行顺序来进行假设。

毕竟,你的应用代码会根据各种外部因素(例如用户输入,网络活动或其他操作系统事件)而以不同的方式运行。每次用户启动你的应用程序时,代码可能会以完全不同的顺序运行,具体取决于这些外部因素。好吧,除了有大量的机器人测试你的应用程序的情况之外,你可以期望所有事件都将通过精确的,致命的机器人同步发生。)

我们绝对不是在说写好的异步代码是不可能的。毕竟,上面列出的Apple出色的API非常先进,非常专业,并且与其他平台相比,它具有强大的功能。

问题在于,复杂的异步代码变得很难编写,部分原因是Apple的SDK提供了多种API:

Apple's_SDK

使用委托要求你采用一种特定的模式,另一种用于闭包,另一种用于订 NotificationCenter的方法,依此类推。 由于所有异步API都没有通用语言,因此请阅读并理解代码及其执行的推理变得困难。

为了结束本文的讨论并使讨论更加深入,你将比较两段代码:一个同步代码和一个异步代码。

同步代码

你已经做了很多次对数组的每个元素执行操作的事情。 这是应用程序逻辑的非常简单但可靠的构建块,因为它保证了两件事:它同步执行,并且在你对其进行迭代时,集合是不可变的。

花一点时间考虑一下这意味着什么。 遍历集合时,无需检查所有元素是否仍然存在,也无需回退以防其他线程在集合开始时插入元素。 你假设总是在循环开始时对整个集合进行迭代。

如果你想更多地使用for循环的这些方面,请在playground上尝试一下:

1
2
3
4
5
6
var array = [1, 2, 3]
for number in array {
print(number)
array = [4, 5, 6]
}
print(array)

数组在for体内可变吗? 循环迭代的集合是否会发生变化? 所有命令的执行顺序是什么? 如果需要,可以修改代码吗?

异步代码

考虑类似的代码,但假设每次迭代都是对按钮轻击的反应。 当用户反复点击按钮时,应用程序将打印出数组中的下一个元素:

1
2
3
4
5
6
7
8
9
10
var array = [1, 2, 3]
var currentIndex = 0
// This method is connected in Interface Builder to a button
@IBAction func printNext(_ sender: Any) {
print(array[currentIndex])

if currentIndex != array.count - 1 {
currentIndex += 1
}
}

在与上一个代码相同的上下文中考虑此代码。当用户点击按钮时,是否会打印阵列的所有元素?你真的不能说。另一段异步代码可能会删除最后一个元素,然后再进行打印。

或者,在继续进行之后,另一段代码可能会在集合的开头插入一个新元素。

另外,你假设只有printNext(_ :)会更改currentIndex,但是另一段代码也可能会修改currentIndex-也许是在编写上述函数后的某个时候添加的一些巧妙代码。

你可能已经意识到编写异步代码的一些核心问题是:
a)各项工作的执行顺序,以及
b)共享的可变数据。

幸运的是,这些是RxSwift的一些强项!

接下来,你需要一个很好的入门语言,以帮助你开始理解RxSwift的工作原理以及解决的问题。最终,这将使你摆脱这一温和的介绍,并在下一章中编写你的第一个Rx代码。

异步编程词汇表

RxSwift中的某些语言与异步,反应式和/或功能性编程紧密地联系在一起,因此,如果你首先了解以下基本术语,将会更加容易。

通常,RxSwift尝试解决以下问题:

1.状态,特别是共享的可变状态

状态有些难以定义。 要了解状态,请考虑以下实际示例。

当你启动笔记本电脑时,它可以正常运行,但是在使用了几天甚至几周后,它可能会开始异常或突然挂起并拒绝与你通话。硬件和软件保持不变,但状态有所改变。重新启动后,相同的硬件和软件组合将可以正常工作更多。

内存中的数据,存储在磁盘上的数据,对用户输入做出反应的所有工件,从云服务中获取数据后剩余的所有痕迹-这些总和就是笔记本电脑的状态。

管理应用程序的状态,尤其是在多个异步组件之间共享时,是本书中将学习的问题之一。

2.命令式编程

命令式编程是一种使用范例来更改程序状态的编程范例。 就像你在与狗一起玩耍时使用命令式语言一样-“抓紧! 躺下! 假死!” —你使用命令式代码来告诉应用程序确切的时间和方式。

命令式代码类似于你的计算机可以理解的代码。 CPU所做的只是遵循冗长的简单指令序列。 问题是,为复杂的异步应用程序编写命令性代码对人类来说是一个挑战,尤其是在涉及共享可变状态时。

例如,使用以下代码,该代码位于iOS视图控制器的viewDidAppear(_ :)中:

1
2
3
4
5
6
7
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setupUI()
connectUIControls()
createDataSource()
listenForChanges()
}

这些方法无济于事。 它们会更新视图控制器本身的属性吗? 更令人不安的是,它们的调用顺序是否正确? 也许有人无意间交换了这些方法调用的顺序,并将更改提交给了源代码管理。 现在,由于交换了呼叫,该应用程序的行为可能有所不同。

3.副作用

既然你对可变状态和命令式编程有了更多的了解,就可以将与这两件事有关的大多数问题归结为副作用。

副作用表示对代码当前范围以外的状态所做的任何更改。 例如,考虑上面示例中的最后一段代码。 connectUIControls()可能会将某种事件处理程序附加到某些UI组件。 这会导致副作用,因为它会更改视图的状态:在执行connectUIControls()之前,该应用程序的行为与之不同。

每当你修改存储在磁盘上的数据或更新屏幕上的标签文本时,都会引起副作用。

副作用本身并不坏。 毕竟,引起副作用是任何程序的最终目标! 你需要在程序完成执行后以某种方式更改世界的状态。

运行一段时间,什么也不做,会导致一个相当无用的应用程序。

Side_Effects

产生副作用的重要方面是以受控方式进行的。 你需要能够确定哪些代码段会产生副作用,以及哪些代码会简单地处理和输出数据。

RxSwift尝试通过解决以下几个概念来解决上面列出的问题。

4.声明性代码

在命令式编程中,你可以随意更改状态。 在函数式编程中,你的目标是尽量减少导致副作用的代码。 由于你没有生活在一个完美的世界中,所以平衡就在中间。 RxSwift结合了命令式代码和功能性代码的一些最佳方面。

声明性代码可让你定义行为。 每当有相关事件发生时,RxSwift都会运行这些行为,并提供不可变的,隔离的数据供你使用。

这样,你就可以使用异步代码,但是可以像在简单的for循环中一样进行假设:你正在处理不可变数据,并且可以按顺序,确定性的方式执行代码。

5.响应系统

响应性系统是一个相当抽象的术语,涵盖具有以下大多数或全部质量的Web或iOS应用程序:

  • 响应式:始终保持用户界面为最新,代表最新的应用程序状态。
  • 弹性:每种行为都是独立定义的,可以灵活地恢复错误。
  • 弹性:代码处理各种工作负载,通常实现诸如惰性拉动驱动的数据收集,事件限制和资源共享之类的功能。
  • 消息驱动:组件使用基于消息的通信来提高可重用性和隔离性,将生命周期与类的实现分离。

现在,你已经对RxSwift帮助解决的问题以及如何解决这些问题有了很好的了解,现在该讨论Rx的组成部分以及它们如何一起发挥作用。

RxSwift的Foundation

响应式编程不是一个新概念;它已经存在了相当长的时间,但是在过去十年中,它的核心概念却卷土重来。

在那个时期,Web应用程序变得越来越复杂,并且面临着管理复杂的异步UI的问题。在服务器端,反应性系统(如上所述)已成为必需。

Microsoft的团队面临着解决本章中讨论的异步,可扩展的实时应用程序开发问题的挑战。他们在图书馆工作,独立于公司的核心团队,有时2009年,提供了一个新的客户端和服务器端框架,称为.NET(Rx)Reactive Extensions。

它是.NET 3.5的可安装附件,后来成为.NET 4.0中的内置核心库。自2012年以来,它一直是开源组件。代码的开源允许其他语言和平台重新实现相同的功能,从而使Rx成为跨平台标准。

今天,你有了RxJS,RxKotlin,Rx.NET,RxScala,RxSwift等。 所有这些库都基于Reactive Extensions标准,努力实现相同的行为和相同的表达API。 最终,使用RxSwift创建iOS应用的开发人员可以在Web上使用RxJS与其他程序员自由讨论应用逻辑。

与原始Rx一样,RxSwift也可以处理你到目前为止介绍的所有概念:它处理可变状态,允许你编写事件序列,并改进了体系结构概念,例如代码隔离,可重用性和去耦。 让我们回顾一下这个定义:

RxSwift在传统的命令式可可代码和纯粹的功能代码之间找到了完美的结合。 它允许你通过使用不可变的代码定义以确定的,可组合的方式处理异步输入,从而对事件做出反应。

你可以在http://reactivex.io上了解有关Rx实现系列的更多信息。 这是有关Rx运算符和核心类的文档的中央存储库。 它可能也是你第一个注意到Rx徽标的地方,即电鳗(在本书的封面上可以找到稍微逼真的图像):

注意:我个人认为这是技术虾,但是研究表明它实际上是电鳗。(Rx项目以前称为Volta。)

在本书中,你将涵盖使用RxSwift开发的基本概念,以及有关如何在应用程序中使用它们的实际示例。

Rx代码的三个构建块是可观察对象(observables),运算符(operators)和调度程序(schedulers)。 以下各节详细介绍了这些内容。

可被观察的对象(Observables)

Observable类提供了Rx代码的基础:异步产生一系列事件的能力,这些事件可以“携带” T类型的通用数据的不可变快照。简而言之,它允许其他对象或使用者进行订阅随时间推移另一个对象发出的事件或值。

Observable类允许一个或多个观察者实时响应任何事件并更新应用的UI,或者以其他方式处理和利用新的和传入的数据。

ObservableType协议(Observable符合)非常简单。

一个Observable只能发出(观察者可以接收)三种类型的事件:

  • 下一个事件(next event):“携带”最新(或“下一个”)数据值的事件。这就是观察者“接收”价值的方式。一个Observable可能会发出不确定数量的这些值,直到发出终止事件为止。

  • 完成事件(complete event):此事件成功终止事件序列。这意味着Observable成功完成了其生命周期,并且不会发出其他事件。

  • 错误事件(error event):Observable因错误而终止,并且不会发出其他事件。

在讨论随时间推移发出的异步事件时,你可以在时间轴上可视化可观察到的整数序列,如下所示:

Observable

一个Observable可以发出的三个可能事件的简单约定是Rx中的任何事物。 由于它是如此通用,因此你甚至可以使用它来创建最复杂的应用程序逻辑。

由于可观察合约没有对可观察者或观察者的性质做任何假设,因此使用事件序列是最终的解耦实践。

你无需使用委托协议或注入闭包以允许你的类彼此对话。

Observable

要了解一些现实情况,你将研究两种不同的可观察序列:有限和无限。

有限的可观察序列

一些可观察到的序列发出零,一个或多个值,并且在稍后一点成功终止或错误终止。

在iOS应用中,考虑从Internet下载文件的代码:

  • 首先,你开始下载并开始观察传入的数据。
  • 然后,随着文件的一部分进入,你反复接收数据块。
  • 如果网络连接断开,下载将停止并且连接将超时并出现错误。
  • 或者,如果代码下载了所有文件的数据,则将成功完成。

该工作流程准确地描述了典型可观察对象的生命周期。 看下面的相关代码:

1
2
3
4
5
6
7
8
9
10
API.download(file: "http://www...")
.subscribe(onNext: { data in
// Append data to temporary file
},
onError: { error in
// Display error to user
},
onCompleted: {
// Use downloaded file
})

API.download(file :)返回一个Observable 实例,当大量数据通过网络进入时,该实例发出Data值。

你通过提供onNext闭包来订阅下一个事件。 在下载示例中,你将数据追加到存储在磁盘上的临时文件中。

你通过提供onError闭包来订阅错误。 在结尾处,你可以在警报框中显示error.localizedDescription或执行其他操作。

最后,要处理已完成的事件,请提供onCompleted闭包,在其中可以推入新的视图控制器以显示下载的文件或应用程序逻辑指示的其他任何内容。

无限的可观察序列

与文件下载或类似活动(这些活动应该自然终止或强制终止)不同,还有其他一些序列是无限的。 UI事件通常是无限的可观察序列。

例如,考虑需要对应用程序中的设备方向更改做出反应的代码:

•你将类作为观察者添加到NotificationCenter的UIDeviceOrientationDidChange通知中。

•然后,你需要提供一个方法回调来处理方向更改。 它需要从UIDevice获取当前方向,并相应地对最新值做出反应。

这种方向变化的序列没有自然的终点。 只要有设备,就有可能发生方向变化的序列。 此外,由于该序列实际上是无限的,因此在你开始观察该序列时,总是会有一个初始值。

Observable

用户可能从不旋转设备,但这并不意味着事件序列已终止。 这只是意味着没有事件发出。

在RxSwift中,你可以编写如下代码来处理设备方向:

1
2
3
4
5
6
7
8
9
UIDevice.rx.orientation
.subscribe(onNext: { current in
switch current {
case .landscape:
// Re-arrange UI for landscape
case .portrait:
// Re-arrange UI for portrait
}
})

UIDevice.rx.orientation是一个虚构的控件属性,它会产生一个 Observable(这很容易编写自己的代码;你将在文中学习方法)。 你订阅它并根据当前方向更新你的应用程序UI。 你跳过onError和onCompleted参数,因为这些事件永远不会从该可观察对象发出。

操作符(Operators)

ObservableType和Observable类的实现包括大量抽象抽象异步工作的方法,这些方法可以组合在一起以实现更复杂的逻辑。由于它们高度分离且可组合,因此这些方法通常被称为运算符。

由于这些运算符大多采用异步输入并且仅产生输出而不会引起副作用,因此它们可以像拼图块一样轻松地组合在一起,并可以制作出更大的图片。

例如,采用数学表达式:(5 + 6)* 10-2。

你可以按照明确的确定性方式,将运算符*,(),+和-按其预定义的顺序应用于作为输入的数据片段,获取其输出并继续处理表达式,直到解析出来。

以某种类似的方式,你可以将Rx运算符应用于Observable发出的事件,以确定性地处理输入和输出,直到表达式解析为最终值为止,然后可以使用该表达式引起副作用。

这是上一个有关观察方向变化的示例,已调整为使用一些常见的Rx运算符:

1
2
3
4
5
6
7
8
9
10
UIDevice.rx.orientation
.filter { value in
return value != .landscape
}
.map { _ in
return "Portrait is the best!"
}
.subscribe(onNext: { string in
showAlert(text: string)
})

每次UIDevice.rx.orientation生成.landscape或.portrait值时,Rx都会将几个运算符应用于该发出的数据。

Observable

首先,过滤器只会让非.landscape的值通过。 如果设备处于横向模式,则订阅代码将不会执行,因为过滤器将抑制这些事件。

对于.portrait值,地图运算符将采用Orientation类型输入并将其转换为String输出-文本“ Portrait is best!”。

最后,通过订阅,你可以订阅产生的下一个事件,这次事件带有一个String值,然后调用一种方法来在屏幕上显示带有该文本的警报。

运算符也很容易组合-它们始终将数据作为输入并输出结果,因此你可以轻松地以多种不同方式链接它们,从而实现比单个运算符自己可以做的更多的工作!

在阅读本书时,你将学到更复杂的运算符,这些运算符抽象出更多涉及异步工作的部分。

调度程序(Schedulers)

调度程序与Rx等效于调度队列-仅在类固醇上使用,更容易使用。

RxSwift附带了许多预定义的调度程序,它们涵盖了99%的用例,并希望你不必再创建自己的调度程序。

实际上,本书上半部分的大多数示例都非常简单,并且通常用于观察数据和更新UI,因此,在你介绍了基础知识之前,你根本不会研究调度程序。话虽如此,调度程序非常强大。

例如,你可以指定要在SerialDispatchQueueScheduler上观察下一个事件,该事件使用Grand Central Dispatch在给定队列上串行运行代码。

ConcurrentDispatchQueueScheduler将同时运行你的代码,而OperationQueueScheduler将允许你在给定的OperationQueue上安排订阅。

多亏了RxSwift,你可以在不同的调度程序上调度同一预订的不同工作,以实现最佳性能。

RxSwift将充当你的订阅(位于左侧)和调度程序(位于右侧)之间的调度程序,将工作片段发送到正确的上下文,并无缝地允许它们彼此使用。

Observable

要阅读此图,请按照在不同调度程序中进行调度的顺序(1、2、3,…)遵循有色工作。 例如:

•蓝色网络订阅以一段代码(1)开始,该代码在基于自定义OperationQueue的调度程序上运行。

•该块输出的数据用作下一个块(2)的输入,该块在不同的调度程序上运行,该调度程序在并发后台GCD队列中。

•最后,在主线程调度程序上调度了最后一个蓝色代码(3),以便使用新数据更新UI。

即使它看起来非常有趣并且非常方便,但现在也不要太在意调度程序。 在本书的后面,你将再次与他们联系。

APP架构

值得一提的是,RxSwift不会以任何方式改变你应用的架构;它主要处理事件,异步数据序列和通用通信协定。

你可以通过实现Apple开发人员文档中定义的MVC(模型-视图-控制器)架构来使用Rx创建应用。如果你愿意,也可以选择实施MVP(Model-View-Presenter)架构或MVVM(Model-ViewViewModel)。

如果你想这样做,RxSwift对于实现你自己的单向数据流体系结构也非常有用。

需要注意的是,你绝对不必从头开始一个项目就可以使其成为一个响应式应用程序。你可以迭代重构现有项目的片段,或者在为应用程序构建新功能时简单地使用RxSwift。

微软的MVVM体系结构是专门为在提供数据绑定的平台上创建的事件驱动软件开发的。RxSwift和MVVM绝对可以很好地配合使用,在本书结尾处,你将研究该模式以及如何使用RxSwift实施该模式。

MVVM和RxSwift一起使用的原因是ViewModel允许你公开Observable 属性,你可以将这些属性直接绑定到View控制器的粘合代码中的UIKit控件。 这使得将模型数据绑定到UI非常容易表示和编码:

Observable

本书中的所有其他示例都使用MVC架构,以使示例代码保持简单易懂。

RxCocoa

RxSwift是通用的,与平台无关的Rx规范的实现。因此,它对任何可可或UIKit特定的类一无所知。

RxCocoa是RxSwift的配套库,其中包含专门用于UIKit和Cocoa开发的所有类。 除了提供一些高级类之外,RxCocoa还向许多UI组件添加了响应式扩展,以便你可以开箱即用地订阅各种UI事件。

例如,使用RxCocoa订阅UISwitch的状态更改非常容易,如下所示:

1
2
3
4
toggleSwitch.rx.isOn
.subscribe(onNext: { isOn in
print(isOn ? "It's ON" : "It's OFF")
})

RxCocoa将rx.isOn属性(以及其他属性)添加到UISwitch类中,以便你可以将有用事件订阅为反应性Observable序列。

Observable

此外,RxCocoa将rx命名空间添加到UITextField,URLSession,UIViewController等中,甚至允许你在此命名空间下定义自己的反应式扩展,你将在本系列的后面部分中详细了解。

坚持原创技术分享,您的支持将鼓励我继续创作!

欢迎关注我的其它发布渠道