本篇目标

理解可选类型的本质,掌握可选绑定、解包方式,学会使用 do-catch、throws、defer 处理错误,能编写健壮的 Swift 代码。

一、可选类型基础

可选类型表示一个值可能为空。

var name: String? = "Tom"
var age: Int? = nil

语法:

  • String? 等价于 Optional<String>。

  • nil 表示没有值。

  • 非可选类型不能赋值为 nil。

var a: String = nil       // 报错
var b: String? = nil      // 正确

为什么需要可选类型:

  • 转换可能失败:Int("abc") 返回 nil。

  • 字典取值可能不存在。

  • 网络请求可能失败。

  • API 返回值可能为空。

let str = "123"
let number = Int(str)     // Optional(123)

let str2 = "abc"
let number2 = Int(str2)   // nil

二、强制解包

用 ! 强制解包,如果值为 nil 会崩溃。

var name: String? = "Tom"
print(name!)  // Tom

危险写法:

var name: String? = nil
print(name!)  // 运行时崩溃

建议:

  • 尽量不用 ! 强制解包。

  • 只有确定不为 nil 时才用。

  • 用可选绑定替代。

三、可选绑定

用 if let 或 guard let 安全解包。

if let:

var name: String? = "Tom"

if let unwrappedName = name {
    print("Hello, \(unwrappedName)")
} else {
    print("name 为 nil")
}

简写(Swift 5.7+):

if let name {
    print("Hello, \(name)")
}

guard let:

func greet(name: String?) {
    guard let name = name else {
        print("name 为 nil")
        return
    }
    print("Hello, \(name)")
}

guard 与 if 的区别:

特性

if let

guard let

作用域

只在 if 块内

函数剩余部分都可用

必须退出

不需要

else 中必须退出

适用场景

分支判断

参数校验

多个可选绑定:

let name: String? = "Tom"
let age: Int? = 25

if let name = name, let age = age {
    print("\(name),\(age) 岁")
}

带条件:

if let name = name, name.count > 2 {
    print("名字长度超过 2")
}

四、可选链

用 ?. 安全访问可选值的属性或方法。

class Person {
    var name: String
    var address: Address?

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

class Address {
    var city: String

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

let person = Person(name: "Tom")
let city = person.address?.city  // nil

可选链的特点:

  • 任意一层为 nil,整个表达式返回 nil。

  • 返回值始终是可选类型。

  • 可以链式调用。

person.address?.city?.count  // Optional<Int>

可选链调用方法:

person.address?.printInfo()

五、nil 合并运算符

用 ?? 提供默认值。

let name: String? = nil
let displayName = name ?? "匿名"
print(displayName)  // 匿名

链式使用:

let a: String? = nil
let b: String? = nil
let result = a ?? b ?? "默认值"
print(result)  // 默认值

注意:

  • ?? 左边是可选类型。

  • 右边是默认值,类型要与解包后一致。

六、隐式解包可选类型

用 ! 声明,使用时自动解包。

var name: String! = "Tom"
print(name)  // 不需要 !

使用场景:

  • IBOutlet 连接。

  • 初始化后一定会赋值的属性。

class ViewController {
    @IBOutlet var label: UILabel!
}

风险:

  • 值为 nil 时崩溃。

  • 尽量少用,优先用可选绑定。

七、可选类型的比较

可选类型可以用 == 和 != 比较。

let a: Int? = 1
let b: Int? = 1
let c: Int? = nil

print(a == b)   // true
print(a == c)   // false
print(c == nil) // true

注意:

  • 不能直接用 >、< 比较可选类型。

  • 需要先解包。

八、错误处理基础

Swift 用 throws 表示函数可能抛出错误。

enum FileError: Error {
    case notFound
    case noPermission
    case invalidFormat
}

func readFile(path: String) throws -> String {
    if path.isEmpty {
        throw FileError.notFound
    }
    return "文件内容"
}

调用 throws 函数:

do {
    let content = try readFile(path: "test.txt")
    print(content)
} catch {
    print("读取失败:\(error)")
}

九、do-catch

用 do-catch 捕获并处理错误。

do {
    let content = try readFile(path: "")
    print(content)
} catch FileError.notFound {
    print("文件不存在")
} catch FileError.noPermission {
    print("没有权限")
} catch {
    print("其他错误:\(error)")
}

catch 的用法:

  • 可以针对特定错误类型捕获。

  • 最后一个 catch 捕获所有错误。

  • error 是隐式变量。

十、try 的三种形式

try:必须放在 do-catch 中,或函数声明为 throws。

do {
    let content = try readFile(path: "test.txt")
} catch {
    print(error)
}

try?:失败返回 nil。

let content = try? readFile(path: "")
// content 是 String? 类型

try!:失败时崩溃。

let content = try! readFile(path: "test.txt")

对比:

形式

失败行为

返回类型

try

抛出错误

正常类型

try?

返回 nil

可选类型

try!

崩溃

正常类型

建议:

  • 优先用 try + do-catch。

  • 简单场景可用 try?。

  • 尽量不用 try!。

十一、throws 函数与传播

函数可以声明为 throws,把错误向上抛。

func loadConfig() throws -> String {
    let content = try readFile(path: "config.json")
    return content
}

调用方处理:

do {
    let config = try loadConfig()
} catch {
    print(error)
}

throws 与 rethrows:

rethrows 表示只有闭包参数抛错时才抛错。

func perform(_ action: () throws -> Void) rethrows {
    try action()
}

十二、defer

defer 在离开当前作用域时执行,无论是否抛错。

func process() {
    defer {
        print("清理资源")
    }
    print("开始处理")
    print("处理中")
}

process()
// 开始处理
// 处理中
// 清理资源

执行顺序:

  • 多个 defer 按逆序执行。

  • 常用于文件关闭、锁释放、资源清理。

func openFile() {
    defer { print("关闭文件") }
    defer { print("解锁") }
    print("打开文件")
}
// 打开文件
// 解锁
// 关闭文件

十三、Result 类型

Result 是 Swift 提供的枚举,用于表示成功或失败。

enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}

使用示例:

func fetchData(completion: (Result<String, Error>) -> Void) {
    let success = true
    if success {
        completion(.success("数据"))
    } else {
        completion(.failure(FileError.notFound))
    }
}

fetchData { result in
    switch result {
    case .success(let data):
        print("成功:\(data)")
    case .failure(let error):
        print("失败:\(error)")
    }
}

应用场景:

  • 异步回调。

  • 网络请求封装。

  • 不想用 throws 的场景。

十四、自定义错误

错误类型遵循 Error 协议。

enum NetworkError: Error {
    case noConnection
    case timeout
    case invalidResponse(code: Int)
}

带关联值的错误:

enum ApiError: Error {
    case httpError(statusCode: Int, message: String)
    case parsingError(String)
    case unknown
}

do {
    throw ApiError.httpError(statusCode: 404, message: "Not Found")
} catch ApiError.httpError(let code, let message) {
    print("HTTP \(code):\(message)")
} catch {
    print(error)
}

LocalizedError:

提供本地化描述。

enum LoginError: LocalizedError {
    case invalidAccount
    case wrongPassword

    var errorDescription: String? {
        switch self {
        case .invalidAccount: return "账号无效"
        case .wrongPassword: return "密码错误"
        }
    }
}

十五、避坑指南

  • 尽量不用强制解包:用可选绑定或 ?? 替代。

  • try! 慎用:出错会崩溃,只用于确定不会失败的场景。

  • guard 与 if let 选择:参数校验用 guard,分支逻辑用 if let。

  • defer 执行顺序:多个 defer 逆序执行。

  • 可选链返回值:始终是可选类型,可能嵌套 Optional。

  • 错误类型命名:用 -Error 结尾,语义明确。

  • 错误传播:throws 函数要把错误往上抛,或本地处理。

十六、检查清单

  • [ ] 理解可选类型的本质

  • [ ] 能使用 if let、guard let 安全解包

  • [ ] 会使用可选链和 ??

  • [ ] 理解隐式解包可选类型

  • [ ] 能定义 throws 函数

  • [ ] 会使用 do-catch、try?、try!

  • [ ] 会使用 defer 清理资源

  • [ ] 能自定义错误类型

  • [ ] 了解 Result 类型