Swift - Enums

「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战

毫无疑问,Swift 的枚举实现是该语言必须提供的最受欢迎和最强大的功能之一。 事实上,Swift 枚举超越了基于整数的常量的简单枚举,并且支持关联值和复杂的模式匹配等内容,这使得它们成为解决许多不同类型问题的理想选择。

然而,有些枚举情况可以说是可以避免的,因为它们可能会导致我们遇到一些棘手的情况,或者让我们的代码感觉不像我们想要的那样“惯用”。 让我们看一些这样的案例,以及如何使用 Swift 的一些其他语言特性来重构它们。

缺失值

例如,假设我们正在开发一个播客应用程序,并且我们已经使用枚举实现了我们的应用程序支持的各种类别。 该枚举当前包含每个类别的案例,以及两个用于根本没有类别的播客的特殊案例(无),以及可用于一次引用所有类别的类别(全部 ):

image.png

然后,在实现过滤等功能时,我们可以使用上述枚举对用户在 UI 中选择的 Category 值进行模式匹配(封装在 Filter 模型中):

image.png

乍一看,上面两段代码可能看起来非常完美。 但是,如果我们考虑一下,我们目前添加了一个特定的 none 案例来表示缺少类别的事实可以说有点奇怪,因为 Swift 确实有一个为此量身定制的内置语言功能 目的——可选项。

因此,如果我们改为将 Podcast 模型的类别属性转换为可选项,那么我们将完全免费获得表示缺失类别的支持——此外,我们现在可以利用 Swift 可选项支持的所有功能(例如 if let 语句 ) 处理此类缺失值时:

image.png

上述更改真正有趣的是,我们之前在 Podcast.Category 上使用的任何详尽的 switch 语句仍然会像以前一样继续工作——因为事实证明 Optional 类型本身也是一个 使用 none case 表示缺少值的枚举——这意味着类似以下函数的代码可以保持完全不变(除了将其参数修改为可选参数):

image.png

上面的工作要归功于 Swift 编译器的一点魔法,当它们用于模式匹配上下文(例如 switch 语句)时,它会自动扁平化选项,这让我们既可以处理 Optional 类型本身的情况,也可以处理我们定义的情况。 自己的 Podcast.Category 枚举,都在同一个语句中。

Domain-specific enums

接下来,让我们把注意力转向我们的 Podcast.Category 枚举的所有情况,想想也有点奇怪。 毕竟,一个播客不可能同时属于所有类别,因此所有案例只有在过滤的上下文中才有意义。

因此,与其将这种情况包含在我们的主要类别枚举中,不如创建一个特定于过滤域的专用类型。 这样,我们可以实现非常简洁的关注点分离,并且由于我们使用嵌套类型,我们可以让我们的新枚举使用相同的类别名称,只是这一次它将嵌套在我们的过滤器模型中——就像这样:

image.png

上述方法的真正好处在于,我们现在可以使用 Swift 的模式匹配功能来实现我们的整个过滤逻辑——通过打开过滤后的类别,然后使用 where 子句为每个案例附加额外的逻辑:

image.png

完成上述所有更改后,我们现在可以继续从主 Podcast.Category 枚举中删除 none 和 all 案例——为我们的应用程序支持的每个类别留下一个更直接的列表:

image.png

自定义case和自定义类型

当谈到像 Podcast.Category 这样的枚举时,(在某些时候)引入某种可用于处理一次性案例的自定义案例,或者通过优雅地处理可能添加到服务器的案例来提供前向兼容性是非常常见的 - 未来的一面。

实现这一点的一种方法是使用具有关联值的案例——在我们的案例中,一个表示自定义类别的原始值的字符串,如下所示:

image.png

不幸的是,虽然关联值在其他情况下非常有用,但这并不是其中之一。 首先,通过添加这种情况,我们的枚举不再支持字符串,这意味着我们现在必须编写自定义编码和解码代码,以及将实例与原始字符串转换的逻辑。

因此,让我们探索另一种方法,将 Category 枚举转换为 RawRepresentable 结构,这再次让我们利用 Swift 的内置逻辑来编码、解码和处理此类类型的字符串转换:

image.png

由于我们现在可以从我们想要的任何自定义字符串中自由地创建 Category 实例,因此我们可以轻松地支持自定义和未来的类别,而无需任何额外的代码。 然而,为了确保我们的代码保持向后兼容,并便于引用我们任何内置的、当前已知的类别——让我们还使用静态 API 扩展我们的新类型,以实现所有这些事情:

image.png

尽管上述更改确实需要添加一些额外的代码,但我们现在最终得到了一个更加灵活的设置,几乎完全向后兼容。 事实上,我们需要做的唯一更新是对类别值执行详尽开关的代码。

例如,我们之前看到的 title 函数之前打开了这样一个值,以返回与给定类别匹配的标题。 由于我们无法在编译时获得每个类别值的详尽列表,因此我们现在必须使用不同的方法来计算这些标题。 例如,在这种特殊情况下,我们可以将此视为将这些字符串移动到 Localizable.strings 文件的绝佳机会,然后像这样解析我们的标题:

image.png

作为一个快速的奖励提示,上述基于结构的方法的一个缺点是我们现在必须为每个静态属性手动定义底层字符串原始值,但我们可以使用 Swift 的 #function 关键字来解决这个问题。 由于该关键字将自动替换为调用其封装函数的函数的名称(或者,在我们的例子中,属性),这将为我们提供与使用枚举时相同的自动原始值映射:

image.png

有了上面的实用程序,我们现在可以简单地在每个内置类别 API 中调用 autoNamed(),Swift 会自动为我们填充这些原始值:

image.png

不过,值得注意的是,在使用基于#function的技术时,我们必须小心不要重命名上述任何静态属性,因为这样做也会改变该属性类别的底层原始值。 但是,在使用枚举时也是如此,另一方面,我们现在还防止了手动定义每个原始字符串时可能发生的拼写错误和其他错误。

Guess you like

Origin juejin.im/post/7074601054679072781