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

在现代 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 invalidURLcase 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 inreturn 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 = usercase .failure(let error):self.errorMessage = error.localizedDescription}
}

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

搭配 .mapError 统一错误类型

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

let result = await Result {try await apiService.loadUser()
}
.mapError { error inreturn 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 inawait Result { try await fetchProfile(for: user.id) }
}
.flatMap { profile inawait 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 successcase .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-catchResult
返回结构无返回值显式 success/failure
链式调用不方便支持 map / flatMap
多阶段处理需嵌套可组合
可读性
可测试性异常难模拟易构造成功/失败场景

参考资源

  • Apple 官方文档 - Result

结语

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/87031.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/87031.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Github 2025-06-28 Rust开源项目日报 Top10

根据Github Trendings的统计,今日(2025-06-28统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10Rust实现的非官方Bitwarden兼容服务器 创建周期:2317 天开发语言:Rust协议类型:GNU Affero General Public License v3.0Star数量…

python 写一个判断文本中是否有手机号的函数,并提取出文本中的手机号

我们需要判断文本中是否有手机号&#xff0c;并提取出手机号。 中国大陆的手机号规则&#xff1a; 1. 通常为11位数字。 2. 目前手机号段分配如下&#xff1a; - 移动号段&#xff1a;134(0-8)、135、136、137、138、139、147、148、150、151、152、157、158、159、172、178、1…

作物生长模型Oryza V3实战12:drate程序详解

drate(v2).exe,可以通过观察移植日、穗部分化、开花和成熟的物候日期(即日和年),DRATE(v2)用于校准四个阶段的发展速率:幼苗期(DVRJ,oCday-1)、光周期敏感期(DVRI,oCday-1)、穗部发育期(DVRP,oCday-1)和生殖期(DVRR,oCday-1)。 一 准备输入文件 1、准备.crp,.…

利用视觉-语言模型搭建机器人灵巧操作的支架

25年6月来自斯坦福和德国卡尔斯鲁厄理工的论文“Scaffolding Dexterous Manipulation with Vision-Language Models”。 灵巧机械手对于执行复杂的操作任务至关重要&#xff0c;但由于演示收集和高维控制的挑战&#xff0c;其训练仍然困难重重。虽然强化学习 (RL) 可以通过在模…

面试拷打-20250701

memcopy和memmov 详细解释 示例1&#xff1a;不重叠的内存区域 正常复制。 示例2&#xff1a;重叠的内存区域 原始数据&#xff1a;src2是一个包含字符串"HelloWorld"的字符数组。使用memcpy&#xff1a; memcpy(src2 2, src2, 5);试图将src2中的前5个字符复制…

什么是 BigKey?

Redis BigKey 深度解析&#xff1a;识别、危害与优化方案 什么是 BigKey&#xff1f; 在 Redis 中&#xff0c;BigKey 是指存储大量数据的单个键&#xff0c;这些键通常具有异常大的内存占用或包含大量元素。BigKey 不是由数据类型定义&#xff0c;而是由其资源消耗决定的。 …

量化选股策略 聚宽

# 量化选股策略完整分析与优化建议 ## 策略整体架构分析 这个量化交易策略主要由以下几个核心部分组成&#xff1a; 1. **初始化设置**&#xff1a;配置基准指数、交易参数和全局变量 2. **选股逻辑**&#xff1a;通过财务指标筛选优质股票 3. **股票过滤**&#xff1a;排除…

Python 数据分析:numpy,抽提,布尔索引2。

目录 1 示例代码2 欢迎纠错3 论文写作/Python 学习智能体------以下关于 Markdown 编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右S…

解决leetcode第3597题分割字符串

3597. 分割字符串 难度&#xff1a;中等 问题描述&#xff1a; 给你一个字符串 s&#xff0c;按照以下步骤将其分割为 互不相同的段 &#xff1a; 从下标 0 开始构建一个段。 逐字符扩展当前段&#xff0c;直到该段之前未曾出现过。 只要当前段是唯一的&#xff0c;就将其…

电源芯片之DCDC初探索ING

1. 概述 DC-DC转换器的意思是直流变直流&#xff08;不同的直流电源值得转换&#xff09;&#xff0c;是一种在直流电路中将一个电压值的电能变为另一个电压值的电能装置。 DC-DC转换器一般由控制芯片、电感线圈、二极管、三极管、电容器构成。 2. 基本拓扑结构 2.1 非隔离…

JavaEE:分布式session

一、使用Redis存储分布式session&#xff1a; 1.SpringBoot整合Redis&#xff0c;见如下地址&#xff1a; JavaEE&#xff1a;SpringBoot整合Redis_a526001650a-CSDN博客 2.代码实现分布式session存储(此处以token为例)&#xff1a; Autowired private RedisTemplate<St…

OpenCV CUDA模块设备层-----“大于阈值设为零” 的图像处理函数 thresh_to_zero_inv_func()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 OpenCV 的 CUDA 模块&#xff08;cudev&#xff09; 中的一个仿函数生成器&#xff0c;用于创建一个 “大于阈值设为零” 的图像处理函数对象。 …

FastGPT与MCP:解锁AI新时代的技术密码

一、AI 浪潮中的新星&#xff1a;FastGPT 与 MCP 登场 在当今科技飞速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;已成为推动各行业变革的核心力量。从智能语音助手到复杂的图像识别系统&#xff0c;AI 的应用无处不在&#xff0c;而其中的关键技术 —— 语言模…

browser-tools-mcp + excel-mcp-server + cursor 实现读取网页信息自动写入Excel

browser-tools-mcp excel-mcp-server cursor 实现读取网页信息自动写入Excel 文章目录 browser-tools-mcp excel-mcp-server cursor 实现读取网页信息自动写入Excel一、安装node.js和npm1、安装nvm2、安装最新版本的node.js 二、安装browser-tools-mcp1、安装 BrowserTools…

Linux安装JDK和Maven

Linux安装JDK和Maven 安装JDK1.8 oracle官网 https://www.oracle.com 下载包地址&#xff1a;https://www.oracle.com/java/technologies/downloads/archive/ 步骤1&#xff1a;官网下载压缩包 点击想要下载的版本&#xff0c;需要登录Oracle的账号&#xff0c;没有的话需要…

MySQL主从复制与数据库集群深度解析

一、主从复制核心架构与复制模式 MySQL主从复制是构建分布式数据库的基础技术&#xff0c;通过日志同步机制实现数据冗余与读写分离。其核心架构分为三层&#xff1a; 日志记录层&#xff1a;主库将数据变更写入二进制日志&#xff08;Binlog&#xff09;网络传输层&#xff…

安装emsdk 4.0.10报Connection reset by peer解决

出错如下: 使用浏览器下载所需文件 https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/node-v22.16.0-darwin-x64.tar.gz 移动到到emsdk/downloads下 修改emsdk.py download_even_if_exists=True 设置环境变量

win11,visual studio 2022,配置dcmtk,opencv

一、配置dcmtk 1 文件下载---地址&#xff0c;Software Development based on DCMTK - dicom.offis.de 源文件下载&#xff0c;选择.zip下载&#xff0c;.tar.gz为Linux和macOS下面常见的压缩包 支持库下载 解决 DCMTK 在 Windows 上编译时所需的依赖库问题 libiconv GNU有…

2025 最新 Appium Inspector 环境搭建教程

1 环境搭建背景 版本升级&#xff1a;Appium 2.0 版本替代 1.x&#xff0c;原 Appium Desktop 因安全漏洞和功能废弃不再适用。需求痛点&#xff1a;Android Studio 仅支持 debug 程序元素定位&#xff0c;需通过 Appium Inspector 实现通用 APK 元素定位。 2 环境搭建步骤 …

Vue 安装使用教程

一、Vue 简介 Vue&#xff08;读作 /vjuː/&#xff0c;类似于“view”&#xff09;是一款用于构建用户界面的渐进式 JavaScript 框架。它易于上手&#xff0c;轻量高效&#xff0c;适合快速构建前端界面&#xff0c;广泛应用于各类 Web 项目中。 二、Vue 安装方式 2.1 直接通…