跳到主要内容

错误处理

问题

Swift 的错误处理机制有哪些?throwsdo-catchResulttry?/try! 各自什么场景使用?

答案

Error 协议

Swift 中错误类型需遵循 Error 协议(一个空协议),通常用枚举定义:

enum NetworkError: Error {
case invalidURL
case timeout
case serverError(statusCode: Int)
case decodingFailed(underlying: Error)
}

// 添加描述
extension NetworkError: LocalizedError {
var errorDescription: String? {
switch self {
case .invalidURL: return "无效的 URL"
case .timeout: return "请求超时"
case .serverError(let code): return "服务器错误:\(code)"
case .decodingFailed(let err): return "解码失败:\(err.localizedDescription)"
}
}
}

throws / do-catch

// 声明可能抛出错误的函数
func fetchUser(id: Int) throws -> User {
guard id > 0 else {
throw NetworkError.invalidURL
}
// ...
return User(id: id, name: "Alice")
}

// 调用:必须用 do-catch 处理
do {
let user = try fetchUser(id: -1)
print(user.name)
} catch NetworkError.invalidURL {
print("URL 无效")
} catch NetworkError.serverError(let code) where code >= 500 {
print("服务器内部错误")
} catch {
// 隐式的 error 变量
print("其他错误:\(error)")
}

try? 和 try!

// try?:错误转为 nil
let user = try? fetchUser(id: 1) // Optional<User>

// 结合 guard
guard let user = try? fetchUser(id: 1) else {
print("获取用户失败")
return
}

// try!:强制解包,失败则崩溃
let user = try! fetchUser(id: 1) // 确信不会失败时使用
警告

try! 在失败时直接崩溃,仅在 100% 确定不会出错时使用(如加载 Bundle 中的已知资源)。

rethrows

rethrows 标记的函数只在传入的闭包会抛错时才需要 try

// map 的签名使用了 rethrows
func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

// 传入不抛错的闭包:不需要 try
let doubled = [1, 2, 3].map { $0 * 2 }

// 传入抛错的闭包:需要 try
let parsed = try ["1", "a"].map { str -> Int in
guard let n = Int(str) else { throw ParseError.invalidNumber }
return n
}

Result 类型

用于异步回调或不使用 throws 的场景:

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

// 异步回调
func fetchData(completion: @escaping (Result<Data, NetworkError>) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(.timeout))
return
}
guard let data = data else {
completion(.failure(.serverError(statusCode: 0)))
return
}
completion(.success(data))
}.resume()
}

// 使用
fetchData { result in
switch result {
case .success(let data):
print("Got \(data.count) bytes")
case .failure(let error):
print("Error: \(error)")
}
}

// Result 的便捷方法
let result: Result<Int, Error> = .success(42)
let value = try? result.get() // Optional(42)
let mapped = result.map { $0 * 2 } // .success(84)
let flatMapped = result.flatMap { n in
n > 0 ? .success(n) : .failure(SomeError())
}

Result 与 throws 互转

// throws → Result
let result = Result { try fetchUser(id: 1) }

// Result → throws
let user = try result.get()

Typed Throws(Swift 6.0+)

Swift 6.0 引入了类型化的 throws:

// 指定具体的错误类型
func parse(_ input: String) throws(ParseError) -> AST {
guard !input.isEmpty else {
throw .emptyInput
}
// ...
}

// 调用者知道具体的错误类型
do {
let ast = try parse("")
} catch {
// error 的类型是 ParseError(而非 any Error)
switch error {
case .emptyInput: print("空输入")
case .syntaxError(let line): print("语法错误:行 \(line)")
}
}

错误处理最佳实践

场景推荐方式
同步函数可能失败throws + do-catch
异步回调Result<T, Error>
async 函数async throws
不关心错误详情try? 转 Optional
确定不会失败try!(谨慎使用)
编程错误(不应发生)fatalError / preconditionFailure
调试断言assert / assertionFailure

常见面试问题

Q1: Swift 的错误处理和 OC 的 NSError 有什么区别?

答案

Swift throwsOC NSError
语法throws + do-catchNSError ** 指针参数
类型安全编译器强制处理可以忽略 error 参数
错误类型任何 Error 协议类型NSError(errorDomain + code)
性能接近零开销(正常路径)创建 NSError 对象有开销

Swift 的错误处理是编译时检查,不会被意外忽略。

Q2: throws 和 Result 如何选择?

答案

  • throws:适合同步函数、async 函数。调用者必须处理错误,代码更简洁
  • Result:适合异步回调场景(completion handler),值和错误绑定在一起

随着 async/await 的普及,异步场景也推荐用 async throws 替代 Result 回调。

Q3: fatalError、preconditionFailure、assertionFailure 的区别?

答案

Release 模式Debug 模式用途
fatalError❌ 崩溃❌ 崩溃不可恢复的编程错误
preconditionFailure❌ 崩溃❌ 崩溃前置条件违反
assertionFailure✅ 继续执行❌ 崩溃仅调试时检查

Q4: defer 语句在错误处理中有什么作用?

答案

defer 确保代码块在当前作用域退出时执行(无论正常返回还是抛出错误),类似 try-finally:

func processFile() throws {
let file = openFile()
defer { closeFile(file) } // 保证文件被关闭

try readData(from: file) // 即使抛错,defer 也会执行
try parseData()
}

相关链接