Swift从入门到放弃swiftiOS&Mac

Swift 模式匹配总结

2018-07-30  本文已影响68人  巴西炒年糕

Swift 模式匹配总结

基本用法

对枚举的匹配:

在swift中 不需要使用break跳出当前匹配,默认只执行一个case就结束

enum Weather {
    case rain, snow, wind, sunny
}

let todayWeather = Weather.rain

switch todayWeather {
case .rain:
    print("下雨")
case .snow:
    print("下雪")
case .wind:
    print("刮风")
case .sunny:
    print("晴天")
}

一次匹配多个模式:

switch todayWeather {
case .rain, .snow:
    print("天气不太好,出门要打伞")
case .wind:
    print("刮风")
case .sunny:
    print("晴天")
}

枚举匹配时还可以绑定枚举的关联值:

enum Weather {
    case rain(level: Int), snow(level: Int), wind(level: Int), sunny
}

let todayWeather = Weather.rain(level: 1)

switch todayWeather {
case .rain(let level):
    if level > 10 {
        print("大雨")
    }else {
        print("小雨")
    }
case let .snow(level):
    if level > 10 {
        print("大雪")
    }else {
        print("小雪")
    }
case .wind(let _):
    print("刮风")
case .sunny:
    print("晴天")
}

// 这两种写法是等价的
case .rain(let level):
case let .rain(level):

可以使用固定值对枚举关联值进行更进一步的匹配:

// 下面的代码中,首先匹配 .rain中的duration是否为24。如果不满足条件则继续后续case的匹配

enum Weather {
    case rain(level: Int, duration: Int), snow(level: Int), wind(level: Int), sunny
}

let todayWeather = Weather.rain(level: 1, duration: 24)

switch todayWeather {
case .rain(let _, 24):
    print("全天有雨")
case .rain(let level, let _):
    if level > 10 {
        print("大雨")
    }else {
        print("小雨")
    }
case let .snow(level):
    if level > 10 {
        print("大雪")
    }else {
        print("小雪")
    }
case .wind(let _):
    print("刮风")
case .sunny:
    print("晴天")
}

虽然同样是 .rain 但由于关联值的不同,所以被视为两个不同的case

case .rain(let _, 24): // 由于不关心 level,所以使用 _ 来进行占位
case .rain(let level, let _):

配合 Where 使用,加强匹配效果

继续上面的例子,通过where语法进行改造

改造前:

switch todayWeather {
case .rain(let _, 24):
    print("全天有雨")
case .rain(let level, let _):
    if level > 10 {
        print("大雨")
    }else {
        print("小雨")
    }
default:
    break
}

改造后:

switch todayWeather {
case let .rain(_, duration) where duration == 24:
    print("全天有雨")
case let .rain(level, _) where level > 10:
    print("大雨")
case let .rain(level, _) where level < 10:
    print("小雨")
default:
    break
}

对原生类型的匹配

不同于oc,swift 除了 enum 和 int类型之外,还支持多种原生类型的匹配:String,Tuple,Range等

String

let name = "Spiderman"

switch name {
case "Ironman":
    print("钢铁侠")
case "Spiderman":
    print("蜘蛛侠")
default: // 由于无法穷举所有字符串,所以必须添加 default 
    print("不认识")
}

注意:当匹配的类型无法穷举时,必须添加 default

Tuple

注意:switch是按照case顺序从上到下进行匹配,如果同时满足多个case,也只会执行最上面的那个

let point = (x: 10, y: 0)

switch point {
case (0, 0): 
    print("原点")
case (0, _): 
    print("Y轴p偏移")
case (let x, 0):
    print("X轴偏移:\(x)")
case (let x, let y) where x == y: 
    print("X = Y")
default: 
    break
}

元组匹配类似于枚举关联值的匹配

Range

let index = 100

switch index {
case 0...20:
    print("20以内")
case 21:
    print("正好21")
case 30..<100:
    print("30到100之间,不包括100")
default:
    print("其它范围")
}

类型匹配

匹配模式可以应用于类型上,这时我们需要用到两个关键字 is、as (注意:不是as?,尽管它们的机制很相似,但是它们的语义是不同的(“尝试进行类型转换,如果失败就返回 nil” vs “判断这个模式是不是匹配这种类型”))

protocol Animal {
    var name: String { get }
}

struct Dog: Animal {
    var name: String {
        return "dog"
    }
    
    var runSpeed: Int
}

struct Bird: Animal {
    var name: String {
        return "bird"
    }
    
    var flightHeight: Int
}

struct Fish: Animal {
    var name: String {
        return "fish"
    }
    
    var depth: Int
}

let animals = [Dog.init(runSpeed: 55), Bird.init(flightHeight: 2000), Fish.init(depth: 100)]

for animal in animals {
    switch animal {
    case let dog as Dog:
        print("\(dog.name) can run \(dog.runSpeed)")
    case let fish as Fish:
        print("\(fish.name) can dive depth \(fish.depth)")
    case is Bird:
        print("bird can fly!")
    default:
        print("unknown animal!")
    }
}

自定义类型匹配

通常情况下,我们自定的类型是无法进行模式匹配的,也就是不能在 switch/case 语句中使用。如果想要达到可匹配的效果,那么就有必有了解一下匹配操作符 ~=

struct BodyFatRate {
    var weight: Float
    var fat: Float
}

let player = BodyFatRate(weight: 180, fat: 30)

func ~=(lhs: Range<Float>, rhs: BodyFatRate) -> Bool {
    return lhs.contains(rhs.fat / rhs.weight)
}

switch player {
case 0.0..<0.15:
    print("难以置信")
case 0.15..<0.2:
    print("健康")
case 0.21..<0.99:
    print("该减肥了")
default:
    break
}

上面的代码中,我们重载的~=操作符,简单的实现了体脂率BodyFatRate和range的匹配。该方法一共接收两个参数并返回一个bool类型的匹配结果。第一个参数lhs为case值,是体脂率的范围。第二个参数为switch传入的值player。两个参数的意义千万不要搞混了。

关于 Optional 匹配

当switch传入的值为optional时,如果不想解包,可以使用x?(相当于Optional.some(x))语法糖来匹配可选值。

let optionalValue: Int? = 5

switch optionalValue {
case 1?:
    print("it's one")
case 2?:
    print("it's two")
case .none:
    print("it's nil")
default:
    print("it's others")
}

上面的代码中,optionalValue相当于 Optional.some(5),所以也需要同Optional.some(x)进行比较。如果case中的值没有加上 ?则会报错:expression pattern of type 'Int' cannot match values of type 'Int?'。当 optionalValue 为nil时,则与 .none 匹配。在Swift中,Int型被认为是无法穷举的,故必须有default。

一些简介高效的匹配语法

除了上面的常规的模式匹配方式,还有一些简洁而高效的匹配语法。在简化了代码结构的同时,也能提高开发效率。

if case let

某些场景下,我们只想与特定的一个case进行匹配。这时可以使用 if case let x = y { … } 形式的语法。这种方式等同于 switch y { case let x: … }。文章一开始的例子:

enum Weather {
    case rain(level: Int), snow(level: Int), wind(level: Int), sunny
}

let todayWeather = Weather.rain(level: 1)

当我们只想判断是否是雨天并打印雨的等级时,一般的写法是这样子:

switch todayWeather {
case let .rain(level):
    print("雨的等级:\(level)")
default:
    break
}

使用 if case let 后:

if case let .rain(level) = todayWeather {
    print("雨的等级:\(level)")
}

显然这种写法更加简洁紧凑,可读性也有一定提高。在这基础之前还可以配合where来使用。

if case let where

if case let .rain(level) = todayWeather where level < 5 {
    print("下小雨")
}

现在上面这种写法会报错:expected ',' joining parts of a multi-clause condition
if case let .rain(level) = todayWeather where level < 5 ,应该改成如下形式:

if case let .rain(level) = todayWeather, level < 5 {
    print("下小雨")
}

guard case let

与if case let 对应的也有 guard case let,用法就不多说了。

for case

当需要对数组元素进行模式匹配时,就可以使用 for case 语法。比如有下面一组天气

let weatherInWeek = [Weather.rain(level: 1),
                     Weather.snow(level: 9),
                     Weather.sunny,
                     Weather.rain(level: 7),
                     Weather.snow(level: 9),
                     Weather.sunny,
                     Weather.snow(level: 3)]

想要筛选出下大雨的天气,level > 5

for case let .rain(level) in weatherInWeek where level > 5 {
    print("下大雨")
}

总结

Swift中的匹配模式要比OC中强大的多。归纳起来大概分为以下几点:

  1. 除了可以匹配枚举类型外,支持更多的原生类型匹配,包过Int、String、Float、Tuple、Range等
  2. 可以对类型进行匹配,配合 as、is 来使用
  3. 重载匹配操作符~=,可以支持自定义类型的匹配操作
  4. 可结合where、if、guard、for来使用,使得代码简洁优雅而高效
  5. 对Optional类型的匹配支持很友好,可简化部分判断逻辑
上一篇下一篇

猜你喜欢

热点阅读