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中强大的多。归纳起来大概分为以下几点:
- 除了可以匹配枚举类型外,支持更多的原生类型匹配,包过Int、String、Float、Tuple、Range等
- 可以对类型进行匹配,配合 as、is 来使用
- 重载匹配操作符~=,可以支持自定义类型的匹配操作
- 可结合where、if、guard、for来使用,使得代码简洁优雅而高效
- 对Optional类型的匹配支持很友好,可简化部分判断逻辑