Swift 中 Result 类型全解析:从基础到进阶

发布于:2025-07-03 ⋅ 阅读:(28) ⋅ 点赞:(0)

在现代 iOS 开发中,Swift 的 Result 类型是处理同步与异步错误的一大利器。相比传统的 throws / do-catch 语法,它更清晰、结构化,也更易于组合式编程。
本文将带你从 Result 的基础定义出发,逐步深入其在实际项目中的多种应用,包括:

  • Result 类型结构和基本用法
  • 使用 Result(catching:) 简化抛出函数调用
  • 使用 get() 还原抛出异常
  • map / flatMap / mapError 组合式编程
  • async/await 的结合
  • 高阶封装与扩展函数

什么是 Result 类型?

Swift 在 5.0 引入了标准库的 Result 枚举,用于统一表示一个操作是成功还是失败:

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

这种设计使得我们可以显式处理成功和失败的情况,并能将其作为值传递。


基础用法

例如我们封装一个网络请求:

enum NetworkError: Error {
    case invalidURL
    case requestFailed
}

func fetchData(from urlString: String) -> Result<Data, NetworkError> {
    guard let url = URL(string: urlString) else {
        return .failure(.invalidURL)
    }

    // 假设这里成功返回
    let data = Data()
    return .success(data)
}

调用者可以显式处理成功与失败:

switch fetchData(from: "https://example.com") {
case .success(let data):
    print("获取到数据:\(data.count) bytes")
case .failure(let error):
    print("失败:\(error)")
}

Result(catching:):让 throws 函数更优雅

传统写法:

do {
    let value = try someThrowingFunction()
    print(value)
} catch {
    print(error)
}

改写为:

let result = Result { try someThrowingFunction() }

这等价于:

let result: Result<ReturnType, Error>
do {
    let value = try someThrowingFunction()
    result = .success(value)
} catch {
    result = .failure(error)
}

这是 Swift 标准库为 Result 提供的一个便捷初始化方式:

public init(catching body: () throws -> Success)

配合 switch.get() 解包:

do {
    let value = try result.get()
    print(value)
} catch {
    print(error)
}

这段代码的重点在于:result 是一个 Result<Success, Failure> 类型,你通过 .get() 方法来从中提取值。

.get() 是 Result 提供的一个方法:

func get() throws -> Success

• 如果是 .success(value),就返回 value
• 如果是 .failure(error),就会 throw error

也就是说,它把 Result 恢复成一个“会抛出错误”的函数调用。

它等价于这样写:

switch result {
case .success(let value):
    print(value)
case .failure(let error):
    print(error)
}

或者:

if case let .success(value) = result {
    print(value)
} else if case let .failure(error) = result {
    print(error)
}

.get() 允许你在使用 Result 的同时,兼容 throws 风格的控制流。这在以下场景特别有用:

  1. 在函数中继续“throw”出去:
func loadUser() throws -> User {
    let result = Result { try JSONDecoder().decode(User.self, from: data) }
    return try result.get() // 自动把错误 throw 出去
}

不想写 switch,只想“顺着抛出去”,那 .get() 就特别方便。

  1. 在某些 try-catch 中临时转回 throws 风格:
do {
    let user = try result.get()
    print("用户名:", user.name)
} catch {
    print("解包失败:", error)
}

这段代码可读性也很好,就像你调用了一个 throws 函数一样。


函数组合:map / flatMap / mapError

map

仅在 .success 情况下转换结果:

let result: Result<Int, Error> = .success(2)
let squared = result.map { $0 * $0 } // .success(4)

flatMap

链接另一个 Result

func square(_ input: Int) -> Result<Int, Error> {
    return .success(input * input)
}

let chained = result.flatMap(square) // .success(4)

mapError

将错误转换成另一种类型:

let errorMapped = result.mapError { error in
    return MyCustomError(reason: error.localizedDescription)
}

与 async/await 协作

Swift 的 async/await 与 throws 通常这样使用:

func loadRemoteData() async throws -> Data

这意味着你在调用它时必须用 try await,并包裹在 do-catch 中:

do {
    let data = try await loadRemoteData()
} catch {
    // 处理错误
}

你可以把这段异步 throws 的代码包装为 Result<Success, Failure> 类型:

let result = await Result {
    try await loadRemoteData()
}

这是一种将“异步抛错逻辑”变成一个明确的结果值的方式,使得你可以把逻辑结构化、组合、传递。

实用场景:

在 ViewModel 中捕获异步错误

目的
• 避免在 SwiftUI 的 @MainActor、@Published 环境中嵌套 do-catch
• 让错误与成功的结果都作为值进行统一处理

示例:

@MainActor
func fetchData() async {
    let result = await Result {
        try await apiService.loadUser()
    }

    switch result {
    case .success(let user):
        self.user = user
    case .failure(let error):
        self.errorMessage = error.localizedDescription
    }
}

这样可以:
• 更易测试:你可以直接构造 .success / .failure 进行单元测试
• 更可组合:可以继续 .map() 或 .mapError() 处理后续逻辑

搭配 .mapError 统一错误类型

很多时候我们不希望把错误直接暴露给 UI,而是将其转为通用的 AppError:

let result = await Result {
    try await apiService.loadUser()
}
.mapError { error in
    return AppError.network(error.localizedDescription)
}

这让你的 ViewModel 不用关心底层的 error 是 URLError 还是 DecodingError,而是统一成业务可识别的类型。

避免多个 await 重复 do-catch 块

当你在一个异步流程中连续调用多个 async throws 函数时,传统写法会很冗长:

do {
    let user = try await fetchUser()
    let profile = try await fetchProfile(for: user.id)
    let avatar = try await fetchAvatar(for: profile.avatarID)
} catch {
    // 各种错误可能混在一起
}

如果改用 Result 方式,每步都可以安全处理错误并组合:

let result = await Result {
    try await fetchUser()
}
.flatMap { user in
    await Result { try await fetchProfile(for: user.id) }
}
.flatMap { profile in
    await Result { try await fetchAvatar(for: profile.avatarID) }
}

再用 .get() 解包或 .switch 判断结果。

好处:
• 每一步都能捕获错误
• 易于插入中间处理(日志、缓存、fallback)
• 错误处理更细粒度、可组合


async throws 函数封装
func taskResult<T>(_ work: @escaping () async throws -> T) async -> Result<T, Error> {
    await Result {
        try await work()
    }
}

然后统一调用:

let result = await taskResult {
    try await fetchData()
}

实用扩展函数

onSuccess / onFailure

extension Result {
    func onSuccess(_ handler: (Success) -> Void) -> Self {
        if case let .success(value) = self {
            handler(value)
        }
        return self
    }

    func onFailure(_ handler: (Failure) -> Void) -> Self {
        if case let .failure(error) = self {
            handler(error)
        }
        return self
    }
}

用法:

result
    .onSuccess { print("成功结果:\($0)") }
    .onFailure { print("错误:\($0)") }

recover:提供默认值或兜底方案

extension Result {
    func recover(_ value: @autoclosure () -> Success) -> Success {
        switch self {
        case .success(let success): return success
        case .failure: return value()
        }
    }
}

实战案例:网络请求封装

func loadUser() async -> Result<User, NetworkError> {
    await Result {
        let (data, _) = try await URLSession.shared.data(from: URL(string: "https://api.example.com/user")!)
        return try JSONDecoder().decode(User.self, from: data)
    }.mapError { error in
        .requestFailed
    }
}

总结对比表

特性 throws/do-catch Result
返回结构 无返回值 显式 success/failure
链式调用 不方便 支持 map / flatMap
多阶段处理 需嵌套 可组合
可读性
可测试性 异常难模拟 易构造成功/失败场景

参考资源


结语

Swift 的 Result 是将函数式编程与错误处理融合的优秀设计。
它不仅简化了异常流程,还增强了组合能力。我们应在业务逻辑、网络请求、异步流程、状态传递中更多地使用 Result,写出更简洁、更安全的代码。

让我们从现在开始,用 Result 统一错误处理思路,让代码更具表达力!

最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。