Swift并发编程:async/await实战入门指南
大家好,我是你们的学长,一名211高校计算机专业的研究生。平时我特别喜欢写技术博客,尤其是帮助刚入行的新手少走弯路。今天这篇教程,就是想和大家聊聊 Swift 并发编程中的 async/await —— 这是 Apple 从 iOS 15 开始大力推广的现代异步编程方式。
我当初学的时候,也踩过不少坑。比如在主线程里直接调用网络请求,结果 App 卡死;或者用回调嵌套写出“回调地狱”代码,自己都看不懂。后来接触到 async/await,才真正体会到“异步也能写得像同步一样清晰”。
这篇文章,我会用最通俗的语言、最实用的例子,带你从零上手 async/await,最后还会用它做一个小项目。无论你是刚学 Swift 的新手,还是只会用 DispatchQueue 的老手,都能从中受益。
为什么需要 async/await?
在移动开发中,很多操作(比如网络请求、文件读写、数据库查询)都是 耗时的。如果在主线程直接执行这些操作,App 会卡住,甚至被系统强制退出。
传统的解决方案是使用 闭包回调(callback) 或 GCD(Grand Central Dispatch)。但它们容易导致:
- 代码嵌套深(“回调地狱”)
- 错误处理复杂
- 难以组合多个异步任务
而 async/await 是 Swift 5.5 引入的 结构化并发模型 的核心,它让异步代码看起来像同步代码一样线性、清晰,还能自动处理线程切换和错误传播。
✅ 核心优势:写法简单 + 自动调度 + 错误处理统一
环境准备:你的开发环境支持吗?
async/await 需要满足以下条件:
| 要求项 | 最低版本 |
|---|---|
| Xcode | 13.0+ |
| iOS | 15.0+ |
| macOS | 12.0+ |
| Swift | 5.5+ |
📌 注意:如果你的项目还要支持 iOS 14 及以下,不能直接使用
async/await。可以考虑用@available做版本判断,或用第三方库(如Combine)作为过渡。
检查你的环境
- 打开 Xcode →
About Xcode,确认版本 ≥ 13.0 - 在
Project Settings→Deployment Info中,将iOS Deployment Target设为 15.0 或更高
如果你还在用旧版 Xcode,建议升级。Xcode 15 已经非常稳定,而且对并发调试支持更好。
核心概念:用最简单的语言讲清楚
1. async:标记一个函数是“异步的”
当你看到一个函数声明为:
func fetchData() async -> String { ... }
这意味着:调用这个函数时,程序不会立刻返回结果,而是可能“暂停”一会儿,等数据准备好再继续。
⚠️ 重要:
async函数只能在async上下文(比如另一个async函数,或Task中)被调用。
2. await:等待异步结果
当你调用 async 函数时,必须加上 await:
let result = await fetchData()
这行代码会 暂停当前任务,直到 fetchData() 返回结果,然后继续执行下一行。
💡 类比:就像你去餐厅点餐(
await),服务员去厨房做菜(async),你坐着等(暂停),菜好了再吃(继续执行)。
3. Task:启动异步任务的入口
在非异步上下文中(比如 viewDidLoad),你需要用 Task 来启动异步操作:
override func viewDidLoad() {
super.viewDidLoad()
Task {
let data = await fetchData()
print(data)
}
}
Task 会自动在后台线程执行异步代码,并在需要更新 UI 时切回主线程(稍后会讲)。
实战项目:用 async/await 获取天气数据
我们来做一个简单的 App:输入城市名,显示该城市的当前温度。
第一步:创建异步网络请求函数
// 模拟网络请求(实际项目中替换为真实 API)
func fetchWeather(for city: String) async throws -> String {
// 模拟网络延迟
try await Task.sleep(nanoseconds: 1_000_000_000) // 1秒
// 模拟成功响应
return "The temperature in \(city) is 25°C"
}
🔍 说明:
throws表示这个函数可能抛出错误Task.sleep是异步的延时函数(替代sleep,后者会阻塞线程)
第二步:在 ViewController 中调用
import UIKit
class WeatherViewController: UIViewController {
@IBOutlet weak var cityTextField: UITextField!
@IBOutlet weak var resultLabel: UILabel!
@IBAction func fetchButtonTapped(_ sender: UIButton) {
guard let city = cityTextField.text, !city.isEmpty else {
resultLabel.text = "Please enter a city"
return
}
// 启动异步任务
Task {
do {
let weather = try await fetchWeather(for: city)
// ⚠️ 注意:此时不在主线程!
await MainActor.run {
self.resultLabel.text = weather
}
} catch {
await MainActor.run {
self.resultLabel.text = "Failed to fetch weather"
}
}
}
}
}
关键点解析
Task包裹异步代码:因为fetchButtonTapped不是async函数try await:调用可能抛错的异步函数MainActor.run:确保 UI 更新在主线程执行
🛑 新手常犯错误:直接在
Task里更新 UI,导致崩溃或界面不刷新!
如何同时发起多个异步请求?
假设你想同时获取北京、上海、广州的天气,而不是一个一个等。
错误做法(串行):
let beijing = await fetchWeather(for: "Beijing")
let shanghai = await fetchWeather(for: "Shanghai") // 等北京完才开始
let guangzhou = await fetchWeather(for: "Guangzhou")
总耗时 ≈ 3 秒
正确做法(并发):
let beijingTask = Task { try await fetchWeather(for: "Beijing") }
let shanghaiTask = Task { try await fetchWeather(for: "Shanghai") }
let guangzhouTask = Task { try await fetchWeather(for: "Guangzhou") }
let beijing = try await beijingTask.value
let shanghai = try await shanghaiTask.value
let guangzhou = try await guangzhouTask.value
总耗时 ≈ 1 秒(三个请求同时发出)
✅ 技巧:用
Task提前启动任务,再用.value等待结果,实现并发。
关于 Cursor 和 v0 的说明
最近很多同学在用 AI 编程工具 Cursor(特别是 v0 版本)来辅助写代码。我自己也在用!
Cursor v0 对 Swift 并发的支持如何?
| 功能 | 支持情况 | 建议 |
|---|---|---|
生成 async/await 代码 |
✅ 很好 | 可以快速生成基础结构 |
处理 MainActor |
⚠️ 有时遗漏 | 需要手动检查 UI 更新是否在主线程 |
错误处理 (try/catch) |
✅ 一般 | 建议自己补充具体错误类型 |
并发任务 (Task 并行) |
⚠️ 容易写成串行 | 需要明确提示“并发执行” |
📝 我的经验:用 Cursor 生成初稿,自己再优化并发逻辑和线程安全。不要完全依赖 AI,尤其是涉及 UI 更新的部分。
新手常见问题 & 解决方案
❓ 问题1:为什么我的 async 函数报错 “'async' in a function that does not support concurrency”?
原因:你在非异步上下文中调用了 async 函数,且没有用 Task 包裹。
解决:
// 错误
let result = await fetchData()
// 正确
Task {
let result = await fetchData()
}
❓ 问题2:UI 没有更新,或者 App 崩溃?
原因:在后台线程直接修改了 UI 元素。
解决:所有 UI 更新必须在主线程,使用 MainActor.run:
await MainActor.run {
label.text = "Updated"
}
或者将整个函数标记为 @MainActor(如果整个函数都需要在主线程):
@MainActor
func updateUI(with text: String) {
label.text = text
}
❓ 问题3:如何取消一个正在进行的异步任务?
Swift 并发支持 任务取消。你可以监听 Task.isCancelled:
func longRunningTask() async {
for i in 1...100 {
if Task.isCancelled {
print("Task cancelled")
return
}
try? await Task.sleep(nanoseconds: 100_000_000) // 0.1秒
print("Progress: \(i)%")
}
}
// 取消任务
let task = Task {
await longRunningTask()
}
task.cancel() // 主动取消
💡 实际项目中,可以在
viewWillDisappear中取消网络请求,避免内存泄漏。
学习建议 & 下一步
避坑指南
- ✅ 永远不要在主线程做耗时操作,即使用了
async/await,底层仍可能阻塞 - ✅ UI 更新必须用
MainActor,这是铁律 - ✅ 错误处理不要忽略,用
do-catch或try?明确处理
推荐学习路径
- 掌握基础:
async/await+Task+MainActor - 进阶:
AsyncStream(处理连续数据流,如 WebSocket) - 高阶:
TaskGroup(动态并发任务)、Actor(状态隔离) - 实战:结合 URLSession、Core Data 等系统框架重写异步代码
推荐资源
- Apple 官方文档:Concurrency
- WWDC2021 视频:Meet async/await in Swift
- 书籍:《Modern Swift Concurrency》
结语
async/await 是 Swift 并发编程的未来。它让异步代码变得可读、可维护、可组合。虽然一开始可能会对“哪里能用 await”感到困惑,但只要记住两个原则:
- async 函数只能在 async 上下文调用
- UI 更新必须回到主线程
你就能写出安全、高效的并发代码。
我当初也是从“回调地狱”爬出来的,深知清晰的异步模型有多重要。希望这篇教程能帮你少走弯路。如果你觉得有用,欢迎关注我的博客,我会持续更新更多 iOS 实战内容!
🌟 记住:写代码不是为了跑起来,而是为了以后还能看懂。

评论 0