原文地址
模式匹配在 swift 里是随处可见的,虽然switch case是匹配模式最常见的用法,但是 Swift 有多种类型的模式,这些模式可以混合甚至在switches外部使用,从而写出真正酷而短的代码。我特别感兴趣的事模式匹配可以用作于各种各样的事情。
乍一看下面的代码,很容易把模式匹配看做是简单的等值检查
1 | switch 80 { |
2 | case 100: |
3 | // |
4 | case 80: |
5 | //匹配, 因为 80 == 80 |
6 | default: |
7 | break |
8 | } |
鉴于下面的代码这种情况,你可能会认为像 case "eighty" 这样的将不会被编译,毕竟”eighty”和 80 不是相同的类型,如果你现在尝试一下的确会编译不通过
1 | if case "eighty" = 80 { |
2 | //error: expression pattern of type 'String' cannot match values of type 'Int' |
3 | } |
但事实并非如此,在项目开发中,你可能已经注意到某些类型之间存在着特殊的交互,例如Ranges它们及其相关类型
1 | switch 80 { |
2 | case 0...20: |
3 | break |
4 | case 21...50: |
5 | break |
6 | case 51...100: |
7 | //匹配, 因为 80 在 51...100 之间 |
8 | default: |
9 | break |
10 | } |
究其原因是~=这个匹配模式运算符,这个操作符看起来在常规项目里没什么用(你之前可能已经看到过这个确切的数字范围内的例子),但是他在 swift 内部使用很多,而且他是用于确认 case 语句
很多情况下,这个运算符是一个简单的等值检查的包装器(例如 Int),但是 Range 有一个特殊的实现,当针对其自己的关联类型使用时,允许他在模式匹配时具有自定义的行为
1 | extension RangeExpression { |
2 | @inlinable |
3 | public static func ~= (pattern: Self, value: Bound) -> Bool { |
4 | return pattern.contains(value) |
5 | } |
6 | } |
并且由于~=的作用域是全局的,为了编写自己的模式匹配的逻辑,你可以重载它。
例如,要让”eighty”匹配 80,你需要做的是,重载一个~=,使它将 String 模式和 Int 值匹配
1 | func ~= (pattern: String, value: Int) -> Bool { |
2 | if pattern == "eighty" { |
3 | return value == 80 |
4 | } else if pattern == "not eighty" { |
5 | return value != 80 |
6 | } else { |
7 | return false |
8 | } |
9 | } |
10 | |
11 | switch 80 { |
12 | case "eighty": |
13 | //编译通过并且匹配 |
14 | case "not eighty": |
15 | // |
16 | default: |
17 | break |
18 | } |
现在假设我的 APP 收到了字符串形式的深层链接,我需要知道这个深层链接属于哪一个控制器
1 | enum AppTab: String { |
2 | case home |
3 | case orderHistory |
4 | case profile |
5 | } |
6 | |
7 | let deepLink = DeepLink(path: "home", parameters: [:]) |
这里有很多种方法来做这到这个功能,包括向 DeepLink 添加属性,如 correspondingTab 或者对 DeepLink 进行子类化,也可以使用自定义模式匹配,只需要一行代码而且无需修改 DeepLink。
1 | func ~= (pattern: AppTab, value: DeepLink) -> Bool { |
2 | return value.path.hasPrefix(pattern.rawValue) |
3 | } |
4 | |
5 | switch deepLink { |
6 | case .home: |
7 | homeViewController.handle(deepLink: deepLink) |
8 | case .orderHistory: |
9 | historyViewController.handle(deepLink: deepLink) |
10 | case .profile: |
11 | profileViewController.handle(deepLink: deepLink) |
12 | default: |
13 | break |
14 | } |
这允许你可以绕过将更广泛的类型映射到更具体的类型,例如如何将 Int 映射到工作日枚举。在这种情况下,您的后端将返回一周作为一个 Int 或一个 String,并使用自定义模式匹配,您可以使用这种更广泛的类型,同时仍然将其视为映射到更特定的枚举类型。当您想尝试使用而不想引用某些方法或类型时,这可能是有用的:
1 | enum WeekDay: Int { |
2 | case sunday |
3 | case monday |
4 | case tuesday |
5 | case wednesday |
6 | case thursday |
7 | case friday |
8 | case saturday |
9 | } |
10 | |
11 | func ~= (pattern: WeekDay, value: Int) -> Bool { |
12 | return pattern.rawValue == value |
13 | } |
14 | |
15 | // Server returns: |
16 | // { nextHoliday: { weekDay: 5 } } |
17 | |
18 | if case .friday? = nextHoliday?.weekDay { |
19 | print("Woohoo!") |
20 | } |
创建自定义模式是编写更干净代码的一种简单方法,不需要太多的努力,因为您可以通过 cases 直接跳到这一点而无需为类型添加其他属性 - 同时确保您的代码不会变得难以理解。
我很想知道你是否做过类似重载过~=这样的事情,如果你有任何建议,意见和反馈,请随时与我联系,@rockthebruno