Swift中的专业模式匹配

创建人: Nick Teissler

switch语句是Swift语言不可否认的优势。 switch语句的后面是Swift的模式匹配,它使代码更易读,更安全。 可以获取开关的模式匹配的可读性和功能,并将其应用于代码中的其他位置。

Swift语言参考指定了八种不同的模式。 很难知道在模式匹配表达式中使用的正确语法。 在典型的用例中,您可能需要了解类型信息,解开变量或仅确认可选项为非零。 使用正确的模式,可以避免尴尬的拆包和未使用的变量。

模式匹配中有两个参与者:模式和值。 该值是switch关键字之后的表达式,如果在switch语句之外测试该值,则为=运算符。 模式是case标签后面的表达式。 模式和值使用Swift语言的规则相互评估。 截至2018年7月15日,该参考文献包含一些关于在散文中如何使用模式以及在何处使用模式的错误,但是可以通过一些实验来发现这些错误。[1]

我们将研究在ifguardwhile语句中应用模式,但在进行操作之前,让我们热切关注switch语句的一些非常规用法。

如果尝试匹配的值可能为nil,我们可以使用Optional Pattern来仅在该值不为nil时匹配该值,并作为奖励将其拆开。 在处理遗留的(和一些不太传统的)Objective-C方法和函数时,这特别有用。 从Swift 4.2开始,随着IUO的重新实现! 将成为的代名词? 。 对于没有可空注释的Objective-C函数,您可能必须处理此行为。

这里的示例特别琐碎,因为这种新行为可能来自Swift <4.2。 使用此Objective-C函数:

Swift签名将为: func aLegacyObjcFunction() -> String! ,在Swift 4.1中,此函数将编译:

在Swift 4.2中,您会收到错误消息:“可选类型’String’的值? 没有包装; 你是说用’!’ 要么 ‘?’?”。 不直观的部分是, return aLegacyObjcFunction()仍将编译,因为它会跳过变量分配以及类型推断。 我们应该更优雅地处理此问题, 尤其是如果所讨论的Objective-C函数实际上可以返回nil

这次,我们有意地处理可选项。 请注意,我们不会像往常一样打开包装OptionalString。 就像我们说过if let output = optionalString { ... }

捕获自定义错误类型时,模式匹配可能非常有用且富有表现力。 一种常见的设计模式是使用enum定义自定义错误类型。 这在Swift中特别有效,因为将关联值附加到枚举案例很容易,以提供有关错误的更多详细信息。

在这里,我们使用两种类型的类型铸造模式以及两种类型的枚举用例模式来处理可能引发的任何错误:

在每个catch上方,我们仅根据需要匹配和捕获了信息,没有多余的信息。 现在从switch ,看看我们还能在哪里使用模式匹配。

很多时候,您可能需要进行一次性模式匹配。 可能仅应在给定单个枚举值的情况下应用更改,而您并不关心其他更改。 那时,易读的switch语句突然变得笨拙。

我们可以使用if case来拆包元组值,除非它为非nil:

上面的语句中使用了三种模式! 在顶部是一个Tuple模式,其中包含一个可选模式 (与上面的“ 匹配非null变量”中的模式不同),以及一个狡猾的通配符模式 _ 。 如果我们使用switch stringAndInt { ... } ,则编译器将迫使我们显式地或使用default标签来处理所有可能的情况。

另外,如果guard case套更适合您的目的,则没有什么可改变的:

您可以使用模式来定义whilefor-in循环的停止条件。 这对于范围很有用。 表达式模式使我们能够避免使用传统variable >= 0 && variable <= 10构造[2]:

在所有这些示例中,模式紧随case标签之后,并且值位于= 。 只有在表达式中有一个isasin关键字时,语法才会与此不同。 在这些情况下,如果您认为这些关键字可以代替= ,则结构是相同的。 记住这一点,并且借助编译器的推动,您可以使用所有8种模式,而不必查阅语言参考。

上面的Range匹配表达式模式有一个独特之处,我们在前面的示例中到目前为止还没有看到:它的模式匹配实现不是内置功能,至少不是编译器内置的。 表达式模式使用Swift标准库~=运算符。 ~=运算符是一个免费的通用函数,定义为:

您可以看到Swift标准库Range类型覆盖了此运算符,从而提供了一种自定义行为,用于检查特定值是否在给定范围内。

让我们创建一个实现~=运算符的Regex类型。 这将是NSRegularExpression的薄纸包装,它使用模式匹配来使可读性更强的regex代码更容易理解,在使用奥秘的正则表达式时,我们应该始终感兴趣。

这是我们的Regex结构。 它具有单个NSRegularExpression属性。 可以将其初始化为字符串文字,其结果是,如果我们无法传递有效的正则表达式,则会收到一条失败消息和一个Match-all regex。 接下来,我们实现模式匹配运算符,将其嵌套在扩展中,这样就很清楚我们要在哪里使用该运算符。

我们希望该结构在开箱即用时有用,因此我将定义两个可以处理一些常见正则表达式验证需求的类常量。 电子邮件正则表达式是从Matt Gallagher的Cocoa with Love文章借来的,并检查RFC 2822中定义的电子邮件地址。

如果您在Swift中使用正则表达式,则无法简单地从Stack Overflow Regex帖子中选择复制粘贴。 快速字符串定义转义序列,例如换行符( \n ),制表符( \t )和unicode标量( \u{1F4A9} )。 这些与正则表达式的语法冲突,正则表达式在反斜杠和所有类型的方括号中比较重。 其他语言(例如python)具有方便的原始字符串语法。 原始字符串将按字面意义接受每个字符,并且不解析转义序列,因此可以将正则表达式以其“纯”格式插入。 在Swift中,字符串中任何孤立的反斜杠都表示转义序列,因此,要使编译器接受大多数正则表达式,您将需要转义转义序列以及一些其他特殊字符。 试图将原始字符串带到Swift中,但最终失败了。 随着Swift继续成为一种多平台,多用途的语言,这种功能可能会重新引起人们的兴趣。 在此之前,已经很复杂的电子邮件匹配正则表达式成为了这种ascii艺术怪兽:

我们可以使用一个更简单的表达式来匹配电话号码,这些电话号码是从Stack Overflow借来的,并且如前所述是两次转义的:

现在,我们可以使用方便易读的模式语法来识别电话号码或电子邮件:

您可能想知道为什么看不到上面的~=运算符。 它是Expression Pattern的实现细节,并被隐式使用。

在所有这些奇特的模式中,我们不应忘记使用经典开关的方式。 如果未定义模式匹配~=运算符,则Swift会转而使用switch语句中的==运算符。 重申一下,我们不再处于模式匹配领域。

下面是一个例子。 这里的switch语句被用作委托回调的多路分解器。 它切换了textField变量,它是NSObject的子类。 因此,将平等定义为身份比较,它将检查两个变量的指针值是否相等。 例如,以一个对象作为三个UITextField的委托。 每个文本字段都需要以不同的方式验证其文本。 当用户编辑文本时,委托将为每个文本字段接收相同的回调

并且可以不同地验证每个文本字段。

我们研究了Swift中可用的一些模式,并研究了模式匹配语法的结构。 有了这些知识,就可以使用所有8种样式! 模式有很多好处,它们是任何Swift开发人员工具箱中必不可少的一部分。 这篇文章仍然有内容没有涉及,例如编译器检查的穷举逻辑和结合了where子句的模式的精妙之where

感谢Erica Sadun在她的博客文章Afternoon Whoa中为我介绍了guard case语法,这启发了这一观点

这篇文章的所有示例都可以在本要点中找到。 该代码可以在操场上运行,也可以根据您的需要进行选择。

[1]指南要求具有关联值的枚举,“相应的枚举用例模式必须指定一个元组模式,其中每个关联值都包含一个元素。”只包括没有任何关联值的枚举用例,前提是您不进行编译和匹配需要相关的值。

另一个小的修正是自定义表达式运算符(〜=)可能“仅出现在switch语句大小写标签中”。 上面,我们也在if语句中使用它。 语言语法正确指定了以上两种用法,并且该错误仅存在于散文中。

[2] readLine功能在操场上不起作用。 如果要运行此示例,请从macOS命令行应用程序尝试。


喜欢你读的书吗? 大书呆子牧场 鼓掌。

从快速的欢呼声到站立的鼓掌,鼓掌以显示您对这个故事的欣赏程度。