为什么 Swift 要求我们解开可选值?

356 阅读5分钟

为什么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 隐式解包

强制解包 是「不管有没有值,我现在就拆」,最暴力;

隐式解包 是「我以后保证会有值,到时候自动拆」,稍微安全,但还是可能崩溃。