为什么Swift需要可选型?深入理解Swift的安全机制
什么是可选型
可选类型的意思是这个值可以存在,也可以不存在。这个表达式、函数会返回一个字符串,但是我不敢保证一定会返回字符串,可能因为网络不好,没有任何返回。为了确保安全,不让不安全的类型混入。
var optionalString: String? = "Hello"
这是一个可选的字符串类型,加上一个?,它就可以是字符串,也可以是其他类型,也可以不存在。
传统的nil检查方式
下面是常用的错误处理的方法,在go语言中经常用到:
var optionalString: String? = "Hello"
if optionalString != nil {
print(optionalString!) // 输出: Hello
}
得到一个bool类型,如果true,那么就执行。但是,这样写,并没有进行可选解包,后续还是需要强制解包。例如:
let optionalString: String? = "Hello"
if optionalString != nil {
// optionalString 依然是 String?,需要自己处理
print(optionalString!.count) // ❌ 如果忘记写 ! 或没有判断好,会崩溃
}
可选绑定 - 更安全的解包方式
这里就提出了另外一种写法:
var optionalString: String? = "Hello"
if let optionalString {
print("安全的")
}
if let optionalString = optionalString {
print("还是安全的")
}
if optionalString != nil {
print("就是安全!")
}
前面两种的书写方式叫做可选绑定。if let optionalString 实际上会把 optionalString 的非空值「解包」成一个新的常量(名字默认和原变量相同),只在 if 的作用域里有效。
可选绑定的工作原理
原理是:枚举两种情况,有值和没有值,然后switch模式匹配,有值时就解包将值赋给if let的常量。
所以等价于:
// if let 语法
if let unwrapped = optionalString {
print(unwrapped)
}
// 等价于以下模式匹配
switch optionalString {
case .some(let unwrapped):
print(unwrapped)
case .none:
break
}
// 或者更直接的等价写法
if optionalString != nil {
let unwrapped = optionalString! // 强制解包
print(unwrapped)
}
你注意到了,其实展开后的可选绑定和上一种写法没有区别,也就是说,if let 就是一个语法糖,它隐藏了底层的枚举匹配过程,让代码更简洁易读。
多重绑定
下面是多重绑定的例子:
let optionalName: String? = "John"
let optionalAge: Int? = 25
if let name = optionalName, let age = optionalAge {
print("姓名:(name),年龄:(age)")
}
// 等价于:
// if optionalName != nil && optionalAge != nil {
// let name = optionalName!
// let age = optionalAge!
// print("姓名:(name),年龄:(age)")
// }
guard关键字
guard关键字也可以用于解包,与if let不同的是,guard let 是先解决错误,处理不满足的情况,再执行满足情况。写法如下:
func greet(_ name: String?) {
guard let name = name else {
print("No name provided.")
return
}
// name 在这里已经是非可选的 String
print("Hello, (name)!")
}
guard 的书写方式有所不同,因为是先处理不满足,所以不满足时候,必须退出,不像if let,满足时都可以不用考虑不满足情况了。
guard someCondition else {
// 这里必须写 return、break、continue 或 throw
}
guard let 与 if let 的区别
✅ if let 解包后,得到的常量(或者变量)只在 if 语句块内部有效,出了这个块就不能再访问。
✅ guard let 解包后,得到的常量(或者变量)在 guard 语句之后的整个作用域(比如整个函数体)中都可以继续使用。因为 guard 的设计初衷是「先验证条件、提前退出,后面只处理满足条件的逻辑」。
func printLength(of string: String?) {
guard let string = string else {
print("String is nil.")
return
}
// string 这里是非可选
print("Length is (string.count)")
}
可以肯定的是,guard不满足情况下必须退出。所以必须将语句写在局部代码块中,函数或循环体。否则,写在全局中就不能退出执行下面的满足语句了。
多条件检查
下面是多条件的检查:
func check(_ a: Int?, _ b: Int?) {
guard let a = a, let b = b, a > 0, b > 0 else {
print("条件不满足")
return
}
print("a: (a), b: (b)")
}
使用场景选择
什么情况下使用guard let,什么情况下使用if let?
✅ 如果我们 更重视先验证条件并提前退出(例如「不满足条件就马上 return」) ,就使用
guard let—— 这是 Swift 推崇的「早退出」写法。
✅ 如果我们 只想要在局部作用域里临时解包一个值,不需要提前退出,也不一定要处理错误情况,就使用
if let—— 它可以选择性地处理「没有值」的情况,甚至可以什么都不写。
✅
if let可以只写「有值时的逻辑」,不写 else 块,甚至可以完全不处理 nil;
✅ 而
guard let必须写else块,并且必须在else中退出当前作用域(比如 return、break、throw 等),否则会编译错误。
✅ if let 可以只处理「有值」的情况
if let value = optional {
print("有值: (value)")
}
// 没有 else,也不会报错
✅ guard let 必须处理「无值」情况并退出
guard let value = optional else {
print("没有值,提前退出")
return
}
print("有值: (value)")
一句话记忆
guard let强调「检查条件 → 不满足就走人」if let强调「有值就用,没值就算了」
其他解包方式
解包有几种常见的,强制解包,可选绑定if let和guard let,还有可选链,nil 合并运算符,隐式解包可选。
强制解包
这是强制解包:
let name: String? = "ChatGPT"
print(name!) // 强制取值,如果是 nil 会崩溃
nil合并运算符
这是有默认值的nil合并,当可选值是 nil 时使用默认值。
let nickname: String? = nil
let displayName = nickname ?? "默认昵称"
print(displayName) // "默认昵称"
可选链
这是链式,如果任何一层是 nil,整个表达式返回 nil。
struct Person {
var pet: Pet?
}
struct Pet {
var name: String
}
let p: Person? = Person(pet: Pet(name: "小狗"))
print(p?.pet?.name) // Optional("小狗")
隐式解包可选
这是隐式,注意与强制的区别。
var name: String! = "ChatGPT"
print(name) // 自动当作非可选使用,不需要写 !
隐式,初始化后一定会有值,使用时就像普通变量一样,无需写 ! 或 if let。赋值后不一定马上有值,但是一定会有,百分百。
强制解包 vs 隐式解包
强制解包 是「不管有没有值,我现在就拆」,最暴力;
隐式解包 是「我以后保证会有值,到时候自动拆」,稍微安全,但还是可能崩溃。