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...caseif/guardfor...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...inswitch...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-patternas-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
    
    • PatternTypeValueType 相同时,~= 的默许完成用 == 进行判等操作
    • PatternTypeValueType 不相同,则需求显式地重载 ~= 操作符

正是因为有了 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 ~=

如上,scoreRange 类型并不相同,但仍是能够对它们进行 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...casefor-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-caseguard-casefor-case-inswitch...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