增强您的功能游戏— Map和FlatMap技巧

Swift已经问世了三年多了,随着我对语言的了解,我尝试将更多的函数式编程技术集成到我的代码中。 来自Objective-C的广泛背景,我承认我在第一次学习Swift时确实不了解mapflatMap 。 最终,我对这些概念很满意,可以开始使用它们来替换我的一些for循环。 直到最近,我才真正认为mapflatMap有用。 直到我参加了Neem Serra的Map和FlatMap Magic演讲,才尝试! 在Swift纽约2017年大会上,我的眼睛向世界展示了mapflatMap的其他伟大用途。 这篇文章中的大部分内容都是基于Neem的介绍,因此请确保在有机会的情况下查看她在Realm Academy上的演讲!

地图

我可以为map提出的最基本定义是,它将数据转换为其他类型。 通常,它用于遍历集合并转换每个元素,因此您可以执行诸如从Int数组创建String数组之类的操作。

平面图

map一样, flatMap将数据转换为其他类型。 FlatMap还具有“删除图层”的特殊属性。一个示例是删除嵌套层,其中将二维数组“展平”为一维数组。 另一个示例是从填充有可选元素的数组中删除nil元素。

地图和FlatMap基础

设定

就像Neem的演讲一样,我将展示使用纸杯蛋糕的地图flatMap技术。 这是我们将使用的模型:

如您所见,该模型非常简单。 纸杯蛋糕具有flavor属性,有一种方法可以恢复该风味。 实际上,不需要getFlavor()方法,因为我们可以直接访问flavor属性。 但是对于我们的示例,我们将其保留不变。

接下来,我们将创建一些Cupcake并将其粘贴在cupcakes数组中:

其余示例将使用此基本设置来说明不同的地图flatMap技术。 文章结尾处还提供了一个游乐场。

地图—旧方法

这段代码看起来熟悉吗? 我知道我第一次学习Swift时就以这种风格编写代码感到内。 在这里,我们将Cupcake的数组转换为String的数组。

地图—新方法

使用map ,我们可以将上面的4行代码简化为一行。 整齐!

地图—错误的方式

您实际上应该只使用map变换元素。 虽然从技术上讲可以使用map 生成元素,但应尽可能避免这样做的冲动。 一个好的经验法则是注意忽略闭包参数。 如果您正在编写类似.map { _ in ... } (请注意下划线 ),则可能是您正确使用map

使用可选的映射

地图也可以用于可选项。 作为参考,让我们看一下在不首先包装它们的情况下尝试使用它们时通常如何表现。 您肯定会看到编译器错误,抱怨该可选项未解包。

您可以使用map来解决此问题,使用它来展开可选的选项(如果存在),并为您提供使用值的机会。

重要的是要注意上面的newCount变量的类型仍然是Int?Optional 。 考虑它的一种简单方法是,当您传递可选的map时map会将其展开,并允许您使用闭包内部的值进行操作。 然后, 地图将其重新包装为可选包装,并将其退还给您。

FlatMap —旧方法

当处理嵌套数组或多维数组时,当我们需要将其缩减为一维数组时,我们的代码通常看起来像这样。

FlatMap —新方法

由于flatMap可以“删除图层”,因此我们可以在一行中执行相同的操作。

带有可选的FlatMapping

map一样, flatMap也可以与可选参数一起使用。 当您将可选参数传递给flatMap时flatMap将解开可选参数(如果存在),使您有机会使用该值做某事,或者返回nil。 一个巧妙的技巧是使用nil-coalescing运算符,然后提供一个默认值,以防展开失败。

假设在某些日子里有“特色蛋糕”,成为当日的风味。 每当没有“特色蛋糕”时,我们都会说香草将成为当今的默认口味。 我们可以使用flatMap在单行中对此场景进行建模。

请注意, flavorOfTheDay的类型不是可选的。 由于我们使用nil-coalescing运算符来提供默认值, flavorOfTheDay只是一个String

地图技巧

添加可选子视图-旧方法

UIViewaddSubview(…)方法采用非可选的UIView因此您通常会发现自己必须首先解开可选的视图。

添加可选子视图-新方法

由于map仅在存在可选值时才运行其关闭,因此我们可以将上述代码简化为一行。 if let这个简单的操作就没有更多了!

字符串插值-旧方法

假设我们需要向用户显示纸杯蛋糕的数量。 如果numberOfCupcakes变量为nil ,我们想显示文本“未知数”。否则,我们需要生成一个String描述盒子中有多少个蛋糕。 没有map ,我们可能会这样做。

您可能熟悉三元运算符 。 也许我们可以用它来简化事情。

不幸的是,使用三元运算符违反了我们的要求。 当numberOfCupcakes不为nil ,将出现不希望的输出,因为将使用对option的完整描述而不是其值( Optional(0)而不是0 )。

可以使用nil-coalescing运算符进行修复。 通过添加?? 0 ?? 0 ,它允许自动解开numberOfCupcakes的值,如果解开失败,则退回到默认值0 。 但是,这有点难看。 因为当numberOfCupcakesnil三元运算符将导致代码短路到“未知数”,所以将永远不会执行使用nil-coalescing运算符创建的代码路径。

字符串插值-新方法

通过结合使用mapnil-coalescing运算符,我们可以满足我们的要求,同时避免创建“不可运行的”代码路径。

初始化快捷方式—旧方法

如前所述,我们可以使用map将数组中的元素转换为其他类型。 使用缩写符号,可以将隐式$0参数传递到所需类型的初始化程序中。

初始化捷径-新方法

由于函数是Swift中的一等公民 ,因此我们可以直接传递init函数以将其作为参数进行映射 ,而完全避免编写闭包。

我仍然对在整个代码中采用这种技术持怀疑态度。 一方面,这是对map的一种很好的简化。 另一方面,它不能在所有情况下都使用,因此会在整个代码库中对地图的使用造成轻微的不一致。

FlatMap技巧

筛选在初始化时失败的对象—旧方法

如果您来自像我这样的Objective-C背景,这是另一段看起来很熟悉的代码。 我们有一些标识符,这些标识符表示需要从包中加载到数组中的图像。 由于UIImageimage(named:)初始化程序是有故障的 ,因此在创建图像时需要使用if let

筛选在初始化失败的对象—新方法

让我们看看使用map会发生什么。

由于map尝试解开可选的包装然后重新包装,因此我们实际上返回了一组可选的图像— [UIImage?] 。 不完全是我们想要的。

由于flatMap删除了一个图层,因此它将返回可选值(如果存在)或nil 。 这意味着images数组的类型为[UIImage] 。 每当初始化程序失败时,什么都不会添加到数组中,而是将其添加到具有nil值的Optional

在附加的运动场中,该数组为空,因为实际上没有包含任何图像资源。

获取屏幕单元格的数据—旧方法

有时,您需要获取与表或集合视图中显示的单元格关联的数据,而这些单元格没有直接引用相应的IndexPath 。 通常, if let使用UITableViewindexPath(for:)方法来解开IndexPath ,就可以使用。 一旦获得了单元格的索引,通常就可以将row属性用作数据数组的索引。

获取屏幕单元格的数据-新方法

FlatMap的转换可选属性(如果存在)或返回nil 属性可以提供替代实现。 如果indexPath(for:)方法返回有效的IndexPath ,我们可以使用它来访问flatMap闭包内的相应数据。

为什么要使用Map和FlatMap?

发挥作用

函数式编程很酷,而且由于Swift的发布,我们直到最近才能够使用该工具。

函数式编程的一个重要方面是避免可变状态的能力。 这不仅可以帮助避免潜在的错误,而且我认为它的阅读效果更好,需要更少的总体认知开销。

当您看到let ,您会立即知道该变量的值在其余范围内都不会改变。 当需要实际定义变量的值时,我发现使用flatMap转换可选变量,然后使用nil-coalescing运算符定义默认值非常容易。 对我来说,单行比通过if / else块的多行跟踪变量的值更容易理解。

视觉显式链接

视觉上显式链接是指将多个map和flatMap操作链接在一起以定义连续变化的流的能力,这些变化随时间推移而转换。

从Neem的演示文稿中借用一张幻灯片,说明是将盒子组合起来,取出零个纸杯蛋糕,将纸杯蛋糕上霜,然后加一点。

结果是Cupcake s的数组。 如果您在仔细阅读,可能还注意到了print(cupcakes)语句实际上应该是print(deliciousness) 。 😉

结论

Swift游乐场可作为GitHub Gist使用,其中包含此处显示的所有示例。 它是用Swift 4 / Xcode 9编写的。

除了将数组转换为不同类型以外, MapflatMap还有更多用例。 希望我已经向您介绍了至少一个很酷的新技巧,您可以在Swift代码中开始采用它。 您是否知道mapflatMap还有其他重大用途? 如果是这样,请随时发表评论,并帮助我们所有人增强我们的功能游戏!