Swift 作为现代、高效、安全的编程言语,其背后有很多高级特性为之支撑。
『 Swift 最佳实践 』系列对常用的言语特性逐个进行介绍,助力写出更简练、更高雅的 Swift 代码,快速完成从 OC 到 Swift 的改变。
该系列内容首要包括:
- Optional
- Enum
- Closure
- Protocol
- Generics
- Property Wrapper
- Error Handling
- Advanced Collections
- Pattern Matching
- Metatypes(.self/.Type/.Protocol)
- High Performance
ps. 本系列不是入门级语法教程,需求有必定的 Swift 根底
本文是系列文章的第九篇,首要介绍 Swift Pattern Matching,其功用强壮,合理运用它们能够写出非常简练、高雅的代码。
Overview
何为 Pattern?
Apatternrepresents the structure of a single value or a composite value.
— Swift Docs – Patterns
Pattern 表明一个或一组值的「结构」、「特征」。
说实话,Swift 官方的这个解释并不明晰易懂 。
直观讲,能够将 Pattern 类比为「正则表达式」。
所谓 Pattern Matching,就是一次 Pattern 与 Value 的「较量」,其目的有 2 个:
- Destructure values,从 Value 中依据指定的 Pattern 提取值;
-
Matching,判别 Value 与 Pattern 是否匹配,首要用于
switch...case
、if/guard
、for...in
以及do...catch
,其亦是本文的主角。
在 Swift 中有 8 种类型的 Pattern:
- Wildcard Pattern
- Identifier Pattern
- Value-Binding Pattern
- Enumeration-Case Pattern
- Optional Pattern
- Type Casting Pattern
- Expression Pattern
- Tuple Pattern
其中:
- Wildcard Pattern、Identifier Pattern 以及 Value-Binding Pattern 首要用于提取值 (Destructure values);
- Enumeration Case Pattern、Optional Pattern、Type Casting Pattern 以及 Expression Pattern 用于匹配 (Matching);
- Tuple Pattern 归于 Pattern 的组合,即将多个 Pattern 组合成一个元组。
下面就上述 8 种 Pattern 分别打开介绍。
Wildcard Pattern
Wildcard Pattern,通配符方式:
- 用下划线(underscore)
_
来表明 - 能够匹配任何值
- 通常用于不关怀具体值的场景
如:
for _ in 1...10 {
// do something,repeat 10 times
}
let points = [(0, 1), (1, 1), (0, 2)]
//
for case (0, _) in points {
// all points with an x-coordinate of 0
}
Identifier Pattern
Identifier Pattern,标识符方式:
- 能够匹配任何值
- 将匹配到的值绑定到一个变量 (
var
) 或常量 (let
) 名上
如:
//
let someValue = 1
var someValues = [1, 3, 5, 10, 7, 9]
//
for someValue in someValues {
// do something
}
Value-Binding Pattern
Value-Binding Pattern,值绑定方式,与 Identifier Pattern 非常相似,或者能够说 Identifier Pattern 是其子方式。如:
let point = (3, 2)
switch point {
// Bind x and y to the elements of point.
case let (x, y):
print("The point is at ((x), (y)).")
}
Enumeration-Case Pattern
Enumeration-Case Pattern,enum-case 方式能够说是我们最熟悉的匹配方式了,没有之一。
关于有 Associated-Value 的 case,在 switch 时能够经过 Value-Binding Pattern 获取相关值,实际上是「 Enumeration-Case Pattern 」 + 「 Value-Binding Pattern 」的组合方式:
enum SomeEnum {
case a(Int)
case b(String)
}
let someEnum = SomeEnum.a(1)
switch someEnum {
case .a(let aValue): //
print(aValue)
case .b(let bValue):
print(bValue)
}
关于 Optional 类型的枚举值也能够直接经过 switch...case
进行匹配,而无需先进行 Optional unwrapping:
//
let someEnum: SomeEnum? = SomeEnum.a(1)
switch someEnum {
case .a(let aValue):
print(aValue)
case .b(let bValue):
print(bValue)
case nil: //
print(""nil)
}
Optional Pattern
Optional Pattern,可选值方式:
- 由于 Optional value 本质上是个 enum,故 Optional Pattern 是 Enumeration-Case Pattern 的语法糖
- 其方式是:
case let name?
如:
let someOptional: Int? = 42
// Match using an optional pattern.
if case let x? = someOptional {
print(x)
}
// Equivalent to ==>
// Match using an enumeration case pattern.
if case .some(let x) = someOptional {
print(x)
}
if case let x? = someOptional
这种写法简直是多此一举 ?
下面这种写法不香吗:
if let someOptional {}
Optional Pattern 的首要应用场景在 for...in
、switch...case
:
如:
-
遍历 Optional-Values 的数组
let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5] // Match only non-nil values. // for case let number? in arrayOfOptionalInts { print("Found a (number)") } // Found a 2 // Found a 3 // Found a 5
-
switch…case 匹配 Optional-Value
let optionalValue: Int? = 0 switch optionalValue { case 0?: // print("Zero") case 1?: // print("One") case 2?: // print("Two") case nil: print("None") default: print("Other") }
Type Casting Pattern
Type Casting Pattern,类型转换方式,其包括 is-pattern
、as-pattern
2 个子类型:
-
is-pattern,判别 value 的运行时类型是否是指定的类型或其子类型,其格式:
is <#type#>
如:
switch someValue { case is String: print("") case is SomeClass: print("") default: print("") }
do { try test() } catch is SomeError { // ... } catch {}
-
as-pattern,除了将运算结果绑定到变量上,其他与
is-pattern
一样,其格式:<#pattern#> as <#type#>
如:
switch someValue { case let str as String: // str's type is String print("") case let someClass as SomeClass: // someClass's type is SomeClass print("") default: print("") }
do { try test() } catch let error as SomeError { // do something } catch { }
Expression Pattern
Expression Pattern,表达式方式,Pattern 是一个表达式,其功用非常强壮:
-
匹配运算是经过
~=
操作符完结的func ~= (pattern: PatternType, value: ValueType) -> Bool
-
PatternType
与ValueType
相同时,~=
的默许完成用==
进行判等操作 - 若
PatternType
与ValueType
不相同,则需求显式地重载~=
操作符
-
正是因为有了 Expression Pattern,不仅能够对 enum 进行 switch...case
操作,任何类型的值都能够进行 switch...case
操作,如:
let someValue = 10
switch someValue {
case 0: // 0 is an expression
print("")
case 1:
print("")
default:
print("")
}
Tuple Pattern
Tuple Pattern,元组方式,将多个方式组合成一个元组,如:
let point = (1, 2)
switch point {
case (0, is Int): // (Expression Pattern, Type Casting Pattern)
print("")
case (1, let y):
print("")
default:
print("")
}
以上是关于 Swift Patterns 的根底介绍,下面介绍这些 Patterns 在开发中的应用。
典型用法
switch…case vs. if-case
switch...case
都能够转换成 if-case
-
switch...case
结构:switch <#value#> { case <#pattern1#>: // do something case <#pattern2#>: // do something ... }
-
if-case
结构:if case <#pattern1#> = <#value#> { // do something } else if <#pattern2#> = <#value#> { // do somethind }
如:
switch someEnum {
case .a(let aValue) where aValue >= 1:
print(aValue)
case .b(let bValue):
print(bValue)
default:
print("")
}
switch...case
–> if-case
:
if case .a(let aValue) = someEnum, aValue >= 1 {
print(aValue)
} else if case .b(let bValue) = someEnum {
print(bValue)
} else {
print("")
}
当然,if-case
一般用于只关怀其中一个 case 的情况,如有多个 case 需求处理,首选 switch...case
。
Matching Custom Tuple
若有多个值有相关操作,经过「 自定义元组 + Pattern Matching 」 能够写出非常高雅的代码,如:
-
判别一个点是否是坐标原点:
老实巴交 :
func descPoint(_ point: (Double, Double, Double)) -> String { if point.0 == 0 && point.1 == 0 && point.2 == 0 { return "Origin Point" } else { return "Normal Point" } }
小聪明 :
func descPoint(_ point: (Double, Double, Double)) -> String { if case (0, 0, 0) = point { // return "Origin Point" } return "Normal Point" }
还能够经过 Wildcard Pattern(_) 完成部分匹配 (Partial matches):
func descPoint(_ point: (Double, Double, Double)) -> String { switch point { case (0, 0, 0): return "Origin Point" case (_, 0, 0): // return "On the x-axis" case (0, _, 0): // return "On the y-axis" case (0, 0, _): // return "On the Z-axis" default: return "Normal Point" } }
也能够经过 Value-Binding Pattern 完成部分匹配,并提取对应的值:
func descPoint(_ point: (Double, Double, Double)) -> String { switch point { case (0, 0, 0): return "Origin Point" case (let x, 0, 0): // x return "On the x-axis, x = (x)" case (0, let y, 0): return "On the y-axis, y = (y)" case (0, 0, let z): return "On the Z-axis, z = (z)" case (let x, let y, let z): return "Normal Point, x = (x), y = (y), z = (z)" } }
-
借助 Optional Pattern 完成多个 Optional Values 的相关操作 :
func authenticate(name: String?, password: String?) { switch (name, password) { case let (name?, password?): print("User: (name) Password: (password) ") case let (name?, nil): print("Password is missing . User: (name)") case let (nil, password?): print("Username is missing . Password: (password)") case (nil, nil): print("Both username and password are missing ") } }
Swift 规范库中
Optional.==
的完成 swift/Optional.swift:extension Optional : Equatable where Wrapped : Equatable { public static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l == r case (nil, nil): return true default: return false } } }
-
Calculated Tuples,tuple 除了静态定义,如:
let point = (x: 0, y: 0)
还能够运行时动态生成,如:
let numbers = (number + 1, number + 2)
完成 Fizz buzz test:
func fizzbuzzTest(number: Int) -> String
办法:老实巴交 :
func fizzbuzzTest(number: Int) -> String { if number % 3 == 0, number % 5 == 0 { return "FizzBuzz" } else if number % 3 == 0 { return "Fizz" } else if number % 5 == 0 { return "Buzz" } else { return String(number) } }
小聪明 :
func fizzbuzzTest(number: Int) -> String { switch (number % 3 == 0, number % 5 == 0) { case (true, true): return "FizzBuzz" case (true, false): return "Fizz" case (false, true): return "Buzz" default: return String(number) } }
在 Alamofire 中判别
Request.State
是否能够从一个状态转换成另一个状态时也有相似的操作:public enum State { case initialized case resumed case suspended case cancelled case finished /// Determines whether `self` can be transitioned to the provided `State`. func canTransitionTo(_ state: State) -> Bool { switch (self, state) { // case (.initialized, _): return true case (_, .initialized), (.cancelled, _), (.finished, _): return false case (.resumed, .cancelled), (.suspended, .cancelled), (.resumed, .suspended), (.suspended, .resumed): return true case (.suspended, .suspended), (.resumed, .resumed): return false case (_, .finished): return true } } }
for-case
经过 for-case 以 Pattern Matching 的方式遍历调集:
-
处理调集中特定的值,如:
let ages = [10, 18, 20, 12, 18, 9] for case 18 in ages { print("18!") // repeat 2 times }
-
处理 Custom Tuple 调集,如:
let tom = (name: "Tom", password: "goi09ko") let antony = (name: "Antony", password: "po09un") let edward = (name: "Edward", password: "okh8yd") let francis = (name: "Francis", password: "y7ub5g") let users = [tom, antony, edward, francis] for case let (name, password) in users { // Value-Binding Pattern // do something } for case let (name, "okh8yd") in users { // Value-Binding Pattern + Expression Pattern // do something } for case ("Francis", "y7ub5g") in users { // Expression Pattern // do something }
-
处理调集中特定类型的值,如:
let views: [UIView] = [...] // for case let view as UILabel in views { print("UILable") }
常见老实巴交的写法 ❌:
for view in views { guard let label = view as? UILabel else { return } print("UILable") }
-
处理 Optional-Value 调集:
let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5] // Match only non-nil values. // for case let number? in arrayOfOptionalInts { print("Found a (number)") } // Found a 2 // Found a 3 // Found a 5
Matching ranges
如 Swift 最佳实践之 Advanced Collections 所述,Range 也能够用于 Pattern Matching,如:
switch score {
case 0..<60:
print("Failed.")
case 60..<85:
print("OK.")
default:
print("Good!")
}
还可用于组合方式中,如:
let student = (name: "Audrey", score: 90)
switch student {
case let (name, 0..<60):
print("(name) failed.")
case let (name, 60..<85):
print("(name) OK.")
case let (name, _):
print("(name) good.")
}
overload ~=
如上,score
与 Range
类型并不相同,但仍是能够对它们进行 Expression Pattern Matching,其原因是 RangeExpression
重写了 ~=
:
extension RangeExpression {
//
public static func ~= (pattern: Self, value: Bound) -> Bool {
return pattern.contains(value)
}
}
switch score {
case 0..<60: // equivalent to: 0..<60 ~= score
print("Failed.")
...
}
我们也能够依据需求重写 ~=
,如下 User
重写了 ~=
,故能够将其与 ClosedRange
进行 Pattern Matching:
struct User {
let name: String
let age: Int
}
extension User {
//
static func ~= (range: ClosedRange<Int>, user: User) -> Bool {
return range.contains(user.age)
}
}
let user = User(name: "Edward", age: 28)
//
switch user {
case 0...18:
print("youngster!")
case 19...65:
print("middle-aged!")
default:
print("elderly!")
}
⚠️ 重写
~=
关于代码的可读性可能会形成必定的影响,需求谨慎运用!
where
Pattern Matching 能够经过 where
子句增加相关的束缚,一般用于 switch...case
、for-in
以及 do...catch
。
-
for-in-where
:for number in numbers where number % 2 == 0 { print(number) }
for view in views where view is UILabel { print("UILable") } // 需求留意的是,上述 view 的类型仍是 UIView,而非 UILabel // 下面这种写法,view 的类型才是 UILabel for case let view as UILbael in views { print("UILable") }
多个条件能够经过
&&
连接:for name in names where name.hasPrefix("Tom") && name.count == 10 { print(name) }
-
do...catch
:enum SomeError: Error { case bar(Int) case baz(Int) } do { try doSomething() } catch SomeError.bar(let code) where code > 0 { // do something } catch let error as SomeError where error.localizedDescription == "hhh" { // do something } catch is SomeError { // do something } catch {}
-
switch...case
:enum TaskStatus { case notStarted case inProgress(Double) case completed } let taskStatus = TaskStatus.inProgress(percent: 0.9) switch taskStatus { case .inProgress(let percent) where percent >= 0.9: print("Almost completed!") case .inProgress(let percent) where percent >= 0.5: print("More than half!") case .inProgress(let percent): print("Just begun, percent = (percent)!") case .notStarted: print("Oh, has not started!") case .completed: print("Good, completed!") }
除了 enum 相关值能够用 where 束缚,一般的 Expression Pattern 也能够用 where 束缚,如核算 Fibonacci:
func fibonacci(position: Int) -> Int { switch position { case let n where n <= 1: return 0 case 2: return 1 case let n: return fibonacci(position: n - 1) + fibonacci(position: n - 2) } }
小结
Swift 提供了 8 种类型的 Pattern,它们能够用于 if-case
、guard-case
、for-case-in
、switch...case
以及 do...catch
等。
合理地运用它们能够写出高效、简练、高雅的代码!
参考资料
Swift Docs – Patterns
Introduction to Patterns and Pattern Matching in Swift
Deep dive into Pattern matching with ~= operator
Pattern Matching
Pattern Matching in Swift – Ole Begemann
Pattern matching in Swift – Swift by Sundell
Pattern Matching in Swift – AndyBargh.com
Pattern Matching in Swift – Coding Explorer Blog
medium.com/swlh/source…
appventure.me/guides/patt…
Writing Custom Pattern Matching in Swift