Swift 中的 switch 如何匹配正确的 case ?

| Swift

 

内容概览

  • 前言
  • 使用 switch 匹配自定义类型的实例
  • 遵循 Equatable 协议
  • 自定义 ~= 运算符
    • ~= 运算符
    • ~= 运算符与 == 运算符
    • 尽量不要自定义 ~= 运算符
  • 总结

 

前言

 

switch 是我们在使用 Swift 进行开发时经常用到的语句。基于 switch,我们可以很惬意地匹配枚举类型实例。然而,可能有一些朋友像我一样不明白 switch 的匹配原理。

如果您也想弄清楚 switch 的匹配原理,那么 Ficow 希望这篇文章可以帮到您。

 

使用 switch 匹配自定义类型的实例

 

为了弄清楚 switch 如何工作,我们先定义一个自定义类型。不使用枚举类型可以排除干扰,因为这样可以免去编译器自动为我们做的很多工作。

先定义自定义类型,然后使用 switch 来匹配自定义类型的zopz实例:

struct Container {
    let id: String
}

let container1 = Container(id: "1")
let container2 = Container(id: "2")

switch container1 {
case container2:
    print("container2")
case container1:
    print("container1")
default:
    print("default")
}

代码写好了,然而 Xcode 却给出了错误提示:

根据这个错误提示,我们可以得到以下信息:

  • switch 匹配基于 ~= 运算符;
  • switch 中进行匹配的值,其类型需要遵循 Equatable

现在,我们其实可以有两种方式来为这个自定义类型解决 switch 匹配问题。

 

遵循 Equatable 协议

 

让 struct 遵循 Equatable 协议非常简单:

struct Container: Equatable { // 遵循 Equatable 协议
    let id: String
}

let container1 = Container(id: "1")
let container2 = Container(id: "2")

switch container1 {
case container2:
    print("container2")
case container1:
    print("container1")
default:
    print("default")
}

修改后运行这个代码片段,Xcode 上输出了:

container1

如果 Container 为 class,编译器不会自动为其生成 == 方法。我们可以自行定义这个方法:

final class Container: Equatable {
    let id: String

    static func == (lhs: Container, rhs: Container) -> Bool {
        return lhs.id == rhs.id
    }

    init(id: String) {
        self.id = id
    }
}

Ficow 推荐您采用这种方式来解决 switch 匹配问题,为什么呢? 答案在后文中,请您继续阅读~

 

自定义 ~= 运算符

 

~= 运算符

 

看到 ~= 运算符,可能会有朋友想起类似这样的代码 200...299 ~= response.statusCode

其实,Swift 官方文档中有讲解关于 ~= 运算符的内容:

我们可以利用 ~= 运算符来进行范围匹配:

print(200...299 ~= 404)

 

现在,我们可以为 Container 类型自定义 ~= 运算符:

struct Container {
    let id: String

    static func ~= (lhs: Self, rhs: Self) -> Bool {
        return lhs.id == rhs.id
    }
}

也可以这样定义:

func ~= (lhs: Container, rhs: Container) -> Bool {
    return lhs.id == rhs.id
}

struct Container {
    let id: String
}

但是,您不能同时采用两种方式来自定义 ~= 运算符,编译器会无法确定最终的结果。

 

~= 运算符与 == 运算符

如果您同时定义了 ~= 运算符和 == 运算符,编译器会采用 ~= 运算符来进行 switch 匹配:

struct Container: Equatable {
    let id: String

    static func ~= (lhs: Self, rhs: Self) -> Bool {
        print("~=")
        return lhs.id == rhs.id
    }

    static func == (lhs: Self, rhs: Self) -> Bool {
        print("==")
        return lhs.id == rhs.id
    }
}

let container1 = Container(id: "1")
let container2 = Container(id: "2")

switch container1 {
case container2:
    print("container2")
case container1:
    print("container1")
default:
    print("default")
}

运行结果如下:

~=
~=
container1

这里的 switch 用 ~= 运算符进行了 2 次比对。

 

尽量不要自定义 ~= 运算符

假如,我们在框架 A 中定义了 Container 类型,然后在框架 B 中使用:

// framework A
public struct Container: Equatable {
    let id: String

    public static func == (lhs: Self, rhs: Self) -> Bool {
        print("==")
        return lhs.id == rhs.id
    }
}

// framework B
let container1 = Container(id: "1")
let container2 = Container(id: "2")

switch container1 {
case container2:
    print("container2")
case container1:
    print("container1")
default:
    print("default")
}

但是,现在我们想在框架 B 中改写 Container 类型的 switch 匹配逻辑。如果我们没有在框架 A 中为 Container 类型定义 ~= 运算符,此时我们可以直接在框架 B 中为Container 类型的扩展添加 ~= 运算符:

extension Container {
    static func ~= (lhs: Self, rhs: Self) -> Bool {
        print("~=")
        return lhs.id == rhs.id
    }
}

如果我们提前在框架 A 中为 Container 类型定义了 ~= 运算符,此时编译器将会阻止我们进行重复的定义:

不在框架 A 中提前为 Container 类型定义 ~= 运算符,这样我们就可以给调用方留下一定的决策空间。如果您不希望给调用方留下决策空间,您可以反其道而行之~ 😹

 

总结

 

如果您需要修改 switch 中的匹配逻辑,那就让该类型遵循 Equatable 协议并重写 == 运算符即可!
如无必要,请不要自定义 ~= 运算符。

如果您有任何建议或者疑问,欢迎您给我留言~

 

参考内容:

Swift - Expression Pattern

 

觉得不错?点个赞呗~

本文链接:Swift 中的 switch 如何匹配正确的 case ?

转载声明:本站文章如无特别说明,皆为原创。转载请注明:Ficow Shen's Blog

评论区(期待你的留言)