Swift 模式匹配总结

作者: 巴西炒年糕 | 来源:发表于2018-07-30 16:28 被阅读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类型的匹配支持很友好,可简化部分判断逻辑

    相关文章

      网友评论

        本文标题:Swift 模式匹配总结

        本文链接:https://www.haomeiwen.com/subject/dhhisftx.html