Swift 模式匹配总结

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类型的匹配支持很友好,可简化部分判断逻辑
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容