什么是模式?
一、字面量(Literal)
了解模式之前,我们先看下什么是字面量。
1 | var age = 10 |
上面代码中的10
,false
,"Jack"
就是字面量。
1.1. 字面量类型
常见字面量的默认类型:
1 | public typealias IntegerLiteralType = Int |
可以通过typealias
修改字面量的默认类型(一般也没必要修改):
1 | public typealias FloatLiteralType = Float |
Swift自带的绝大部分类型,都支持直接通过字面量进行初始化(不需要直接调用初始化器):
1 | Bool、Int、Float、Double、String、Array、Dictionary、Set、Optional等 |
1.2. 字面量协议
Swift自带类型之所以能够通过字面量初始化,是因为它们遵守了对应的协议。
1 | Bool : ExpressibleByBooleanLiteral |
示例代码:
1 | var b: Bool = false // ExpressibleByBooleanLiteral |
1.3. 字面量协议应用
示例代码一:
Bool
类型直接赋值给Int
类型的变量,会直接报错:
如果要不报错,只需要给Int
添加一个遵守协议的扩展即可:
1 | extension Int : ExpressibleByBooleanLiteral { |
示例代码二:
1 | class Student: ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByStringLiteral, CustomStringConvertible { |
示例代码三:
1 | struct Point { |
二、模式(Pattern)
模式是用于匹配的规则,比如switch
的case
、捕捉错误的catch
、if/guard/while/for
语句的条件等。
Swift中的模式有:
- 通配符模式(Wildcard Pattern)
- 标识符模式(Identifier Pattern)
- 值绑定模式(Value-Binding Pattern)
- 元组模式(Tuple Pattern)
- 枚举Case模式(Enumeration Pattern)
- 可选模式(Optional Pattern)
- 类型转换模式(Type-Casting Pattern)
- 表达式模式(Expression Pattern)
2.1. 通配符模式
_
匹配任何值_?
匹配非nil
值
示例代码:
1 | enum Life { |
case .human(let name, _):
中的_
就是匹配任意值。
case .animal(let name, _?):
中的_?
就是匹配非nil
值。
2.2. 标识符模式
给对应的变量、常量名赋值。
1 | var age = 10 |
2.3. 值绑定模式
把对应位置的值绑定到变量/常量上。
1 | let point = (3, 2) |
2.4. 元组模式
本质也是值绑定和通配符模式。
示例代码一(数组):
1 | let points = [(0, 0), (1, 0), (2, 0)] |
示例代码二(case):
1 | let name: String? = "idbeny" |
示例代码三(字典):
1 | var scores = ["jack" : 98, "rose" : 100, "kate" : 86] |
2.5. 枚举Case模式
if case
语句等价于只有1个case
的switch
语句。
示例代码一:
1 | let age = 2 |
if case 0...9 = age
可以理解为是拿出age
的值和case
后面的条件进行匹配。
下面的代码完全等价上面示例代码的if case
:
1 | switch age { |
示例代码二:
1 | let age = 2 |
示例代码三:
1 | let points = [(1, 0), (2, 1), (3, 0)] |
2.6. 可选模式
示例代码一:
1 | let age: Int? = 42 |
示例代码二:
1 | let ages: [Int?] = [nil, 2, 3, nil, 5] |
上面示例二的代码和下面的代码等效:
1 | for item in ages { |
示例代码三:
1 | func check(_ num: Int?) { |
2.7. 类型转换模式
主要是is
和as
的用法。
示例代码一:
1 | let num: Any = 6 |
case is Int
仅仅是判断num
是否为Int
类型,编译器不会自动强转,依然认为num
是Any
类型。
如果需要强转并且判断类型,可以使用as
:
1 | switch num { |
此时的n
是Int
类型,但是num
依然是Any
类型。如果num
不是Int
类型就会跳过当前case
,匹配下一个case
。
示例代码二:
1 | class Animal { |
上面的示例中,如果匹配case is Cat
时,怎样才能执行jump
方法?因为animal
是Animal
类型。
可以对animal
进行强制转换:
1 | (animal as? Cat)?.jump() |
2.8. 表达式模式
表达式模式用在case
中:
1 | let point = (1, 2) |
通过汇编查看上面示例代码,发现示例程序是用~=
运算符做匹配:
其实,在Swift中,一些复杂
switch
匹配会用到~=
运算符,但并不是所有的switch
都是用到该运算符。
可以通过重载运算符,自定义表达式模式的匹配规则。
2.8.1. 自定义表达式模式
示例代码一:
1 | struct Student { |
使用示例代码一:
1 | var stu = Student(score: 72, name: "idbeny") |
stu
是怎么和Int
、Rang
进行匹配的呢?重写~=
运算符。
基本上是固定写法(返回值必须是Bool
):
1 | // pattern: case后面的类型 |
示例代码二(if case
本质就是switch
):
1 | if case 60 = stu { |
示例代码三:
1 | var info = (Student(score: 70, name: "daben"), "及格") |
示例代码四:
1 | extension String { |
示例代码五:
1 | func isEven(_ i: Int) -> Bool { |
还可以定义更多自定义操作符:
1 | prefix operator ~> |
>, =, >=, <, <=
运算符都是中缀运算符,为了不影响原有的运算符特性,在原有运算符前面加一个~
符号成为一个新的运算符。
2.8.2. where
可以使用where
为模式匹配增加匹配条件。
示例代码一(case
):
1 | var data = (10, "Jack") |
示例代码二(for
):
1 | var ages = [10, 20, 12, 55, 30] |
示例代码三(protocol
):
1 | protocol Stackable { |
示例代码四(func
):
1 | func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable { |
示例代码五(extension
):
1 | extension Container where Self.Stack.Element : Hashable { } |