功能反应式编程(FRP)的基本构建模块:IOS

每个大型结构都有基本单元,这些基本单元组合在一起,对整个结构有意义。 例如,砖块,水泥,油漆,混凝土等构成建筑物上的基本构建块。 同样地,在我们继续讨论功能性反应式编程的广泛领域之前,如果我们了解将要结合在一起的基本构建模块,以便使我们创建的大量应用程序有意义,那将是非常棒的。 FRP的基本构建模块,从事件流开始。

事件流可以定义为随着时间推移发生的一系列事件。 您可以将其视为异步数组。 下图显示了事件流的简单描述:

如您所见,我们在箭头上表示了时间,该箭头从左到右对齐,向前移动到右边,事件随时间而发生。 在时间轴上间歇性地绘制的彩色气泡(由其名称指示)表示事件 。 我们可以在整个序列中添加一个事件侦听器,并且每当事件发生时, 我们都可以通过做一些事情来与之互动 ; 那是主要思想!

在Swift中,我们还有许多其他类型的序列,例如数组:

假设我们有一个eventStream数组:

  var eventStream = [“ 1”,“ 2”,“ abc”,“ 3”,“ 4”,“ cdf”,“ 6”] 

让我们尝试将eventStream与数组进行比较; 数组是空间中的序列,这意味着eventStream数组中的所有项目现在都存在于内存中; 另一方面,eventStreams没有该属性。 事件可能随着时间而发生,您甚至都不知道所有可能发生的事件以及何时发生。

S o,如果我们必须在数组和事件流之间建立联系,那么我们可以断言,如果[[1],“ 2”,“ abc”,“ 3”,“ 4”,“ cdf”,“ 6” ]值会在一段时间内发生,并且不只是从头开始存在于内存中,前面的数组将像事件流一样,其中事件“ 1”可能在第一秒发生,事件“ 2”可能在第四秒发生,事件“ abc”可能会在第10秒发生,依此类推。

在这里,请注意,事件发生的时间和事件的类型都不是事先知道的。 事件仅在发生时解决。 事件流的好处是它们具有类似于数组的功能。

假设我们的问题是在给定数组中添加所有数字。

数组的解决方案 :如您所见,数组中的元素不是数字; 它们是字符串,因此我们必须在此处进行一些转换,然后遍历循环以过滤掉无法转换为数字的字符串,然后将其余部分(如果它们是有效数字)相加。 我们可以使用for循环遍历数组,但是当我们遍历for循环内的数组时,我们将使用map和filter操作来提供问题的解决方案,这是功能性方法:

第一步

 让result = eventStream.map {//在地图内部,我们将解析数组//元素将所有与整数兼容的元素转换为整数 
}

步骤2:从步骤1的结果数组中过滤所有非数字元素。因此,现在可以像下面这样扩展步骤1中的语句:

 让result = eventStream.map({//在地图内部,我们将...转换为整数 
})。filter {
//过滤所有非整数以形成纯整数数组
}

步骤3:通过使用reduce扩展步骤2,将所有整数值相加以获得总和。 因此,相同的语句可以编写如下:

 让result = eventStream.map({//在地图内部,我们将...转换为整数 
})。filter {
//过滤所有非整数以形成纯整数数组
} .reduce(0,+)

所有这些功能都称为高阶功能 ,而在一行中使用点符号扩展功能的过程称为Chaining

我们可以对事件流执行相同的操作,唯一的区别是间歇时间间隔内事件的可用性。 结果, 事件流的处理将花费自己的时间,并且结果不会像在处理数组时那样立即填充。

确切地说,我们需要更多地了解共享可变状态。 在将这个术语与编程概念联系起来之前,让我们尝试提出一个通用术语的定义; 考虑一个例子。

假设您买了一辆车,第一天就开了车去兜风。 一切都顺利且顺利进行。 要启动汽车,请插入钥匙并顺时针旋转。 在后台,火花塞点燃可控的燃油流,您的发动机便恢复了活力。 您的汽车的这种状态是启动状态。 然后,将档位切换至行驶模式,并用右脚踩下加速器。 瞧! 汽车开始行驶,现在您可以将此汽车称为行驶状态,最终您将把汽车停在目的地,然后汽车的状态将相应改变。

因此,您注意到您的动作或输入,发动机点火,活塞运行等(包括前台或后台的所有活动和过程的总和)构成了汽车的状态,并且由于它可能在多个背景下发生变化流程和用户操作,这是可变的:

在给定的时间点上,有许多因素决定着汽车的状态,有时很难控制汽车的状态,然后您就会崩溃! 好吧,这是汽车修理工需要担心的事情。

将相同的概念映射到我们构建的应用程序,每个应用程序在任何给定的时间实例都具有状态。 该应用程序可能是在后台从Web服务中获取数据,在媒体播放器中播放歌曲,响应用户输入,等等-在任何给定时间点的所有这些动作或过程(同步和异步)统称为“应用程序的状态,与汽车修理工不同,我们有责任在任何时候随时管理应用程序的状态。

由于您现在已经了解了通用和编程环境中的共享可变状态,因此可以将这两个问题中的大多数问题归结为副作用。

每个函数都在其范围内工作,以获取输入,应用一些逻辑并生成输出。 函数也不能有输入或输出。 例如,一个函数打印出对象的状态。 当系统状态由于执行功能而改变时,会发生副作用。 例如,假设一个名为addAndStoreValue()的函数将两个整数相加并将结果存储在本地数据库中,并引发视图刷新的通知以反映结果值,那么视图中的更改将是该函数由于执行而引起。 换句话说,执行功能后,应用程序的状态就会更改。 这种变化称为副作用

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

您必须认为副作用一点都没有。 实际上,这就是我们编写代码并执行程序的原因。 我们希望一旦程序执行,系统/应用程序的状态就会改变。 您不希望您的程序运行并且不更改移动权利的状态吗? 谁想要这样的应用程序? 想象一下,运行一个应用程序一段时间,并且根本不会改变系统状态,这是完全没有用的,是的!

因此,从到目前为止的讨论中,我们已经了解到需要引起副作用,那么问题出在哪里呢?

副作用的问题是,我们希望控制副作用,因此一旦函数完成执行,就可以预测应用程序的状态。 我们希望控制执行以可预测的方式引起副作用,并在我们的应用开始运行后预测设备的状态。 我们还需要将我们的应用程序分成模块,以识别哪些代码段更改了应用程序的状态以及哪些代码段处理和输出了数据。

RxSwift通过使用声明性编码和创建反应式系统来解决上述问题,两者均在前面进行了介绍。 我们将在接下来的部分中深入研究这些概念。

函数式编程主要避免副作用。 因此,代码变得更具可测试性,因此编写健壮的代码变得更加容易。

编写精良的应用程序可以将导致副作用的代码与程序的其余部分区分开; 通过此测试,该应用变得更容易,功能扩展变得清晰,重构和调试变得简单明了,并且维护此类代码库变得轻松自如。 RxSwift可以起到很大的作用,以隔离预期的副作用。

在讨论不变性之前,让我们首先回答一个问题: 在多线程环境中使用哪种数据类型更安全? 变量数据类型(可以随时间更改),或常量数据类型,一旦填充就不能更改。 让我尝试通过一个示例来解释这个问题-假设您有一个位置坐标的可变字典:

  var locationDictionary = [“纬度”:131.93839,“经度”:32.83838] 

locationDictionary是每五分钟填充一次的用户位置; 此locationDictionary将在不久的将来转换为位置JSON对象,并同步到后端(将数据推送到服务器的API),该位置将从该位置选择并显示到某些Web视图中,或者可能是用于任何其他实时位置更新目的。

想象一下在多线程环境中使用此位置字典的情况; 由于它是一个变量,因此locationDictionary可以在任何给定的时间通过任何线程在任何时间进行更新,从而导致错误的位置更新。 您可能最终将不需要或损坏的数据发送到您的API。

简单解决:将变量设为常量,可以放心,一旦将值填充到字典中,该值就不会更改,因此您将获得可靠的结果。

从前面的讨论中,我们现在可以定义不可变对象以及可变对象是什么:不可变对象是在存在或作用范围内不能更改的对象,而可变对象可以在其范围内更改。

函数式编程提倡不变性的概念; 记住这个例子:

 设数字= [1、2、3、4、5、6、7、8、9] 
让numbersLessThanFive = numbers.filter {$ 0 <5}

您是否注意到我们如何创建一个新的numbersLessThanFive数组,将其作为常量而不是变量? 尝试用命令式编程创建类似的数组; 你可以有一个常数数组吗? 让我们尝试这样做:

 var numberLessThanFive = [Int]() 
用于索引0 .. <numbers.count
{
如果数字[索引] <5
{
numberLessThanFive.append(numbers [index])
}
}

该过程不仅冗长,而且还会导致程序中的数据流丢失,因为在内存中执行的任何其他线程现在都可以更改此数组中的值,因为该数组现在是变量。 在任何编程语言中都鼓励使用不可变数据流,特别是在您尝试构建可能产生大量线程的复杂应用程序时,尤其如此:

由于功能性反应式编程的一个方面包括许多功能性编程,因此在以FRP方式对逻辑进行建模时,处理不可变数据自然而然,因此从一开始就解决了一半的问题。

嗯,这并不像看起来那么简单,但是不断的练习会使您变得完美。 您可以在这里按照我的书练习和掌握FRP的原理和概念。

由于不可变对象在执行期间无法随时间更改,因此这意味着它们会付出一定的代价,并且在某些情况下可能无法重用。 如果当前对象不适合上下文,则必须放置一个不可变对象或填充一个新对象。 如您所见,使用不可变数据类型时,存在某些缺点,但是作为智能程序员, 我们需要在对数据类型进行建模时达到适当的平衡并做出合理的选择:

从上图和我们先前的讨论中,很清楚为什么使用可变状态会导致程序代码中的不确定行为。 如前所述,我们需要在两者之间取得平衡,以保持共享状态以适应当前的问题。 不变的数据类型越多,意味着行为越确定。

在下一个博客中,我将讨论RxSwift的基础,在此之前,请继续关注并喜欢阅读:)

要深入了解反应式概念并在RxSwift中编写IOS应用程序,您可以找到我的书《 Swift 4中的反应式编程》的链接。

感谢您的阅读,如果发现有用,请分享share