Swift并发编程:async/await实战指南
大家好,我是一名从培训班出来的iOS开发者,现在在一家创业公司做前端。当初刚学Swift的时候,看到async/await这几个字就头大,感觉像是天书一样。面试官一问“说说Swift的并发模型”,我就只能支支吾吾,结果错过了好几个机会。
今天我来手把手带你搞定Swift并发编程!这篇文章就是我当年最想要的那种——没有高深理论,全是能跑起来的代码,还有我踩过的坑和避坑建议。无论你是完全零基础的小白,还是正在准备面试题,都能从中学到实用的东西。
为什么你需要学async/await?
想象一下这样的场景:你的产品要从后端API获取用户数据,如果用传统的回调方式,代码会一层套一层,调试起来痛苦不堪。而async/await让异步代码写起来像同步代码一样简单直观。
我当初在培训班学到这个概念时,老师直接扔给我们一堆GCD(Grand Central Dispatch)的代码,结果我们花了好几天才搞明白。其实Swift 5.5推出的async/await已经大大简化了并发编程!
环境准备:让你的电脑ready
要使用async/await,你需要:
- Xcode 13.0 或更高版本
- iOS 15.0+ 的部署目标(或者 macOS 12.0+, watchOS 8.0+, tvOS 15.0+)
检查步骤:
- 打开Xcode → Preferences → Locations → Command Line Tools 选择最新版本
- 创建新项目时,确保Deployment Info中的iOS版本设置为15.0或更高
- 在代码中尝试写
async关键字,如果没有报错说明环境OK
💡 新手提示:如果你的项目需要支持iOS 14及以下版本,那就不能用
async/await了。这时候你可能需要继续用GCD或者第三方库。
核心概念:用大白话解释
什么是async/await?
简单来说:
async:标记一个函数是"异步"的,意味着它可能会花时间执行(比如网络请求)await:在调用async函数时使用,告诉程序"在这里等一下,等结果回来再继续"
同步 vs 异步:一个生活化的例子
想象你要煮泡面:
- 同步方式:烧水 → 等水开 → 放面 → 等3分钟 → 吃面(整个过程你都在厨房等着)
- 异步方式:烧水(同时去刷牙)→ 水开了(暂停刷牙)→ 放面 → 继续刷牙 → 面好了(暂停刷牙)→ 吃面
async/await就是帮你优雅地处理这种"暂停-继续"的过程。
关键规则记住这三条
- 只有标记为
async的函数才能调用其他async函数 - 调用
async函数必须用await await只能在async函数内部使用
违反任何一条都会编译报错!
实战项目:做一个天气查询App
让我们动手做一个简单的天气查询功能。这个例子虽然简单,但涵盖了实际开发中的典型场景:调用后端API、处理异步响应、更新UI。
第一步:创建异步网络请求函数
import Foundation
// 定义天气数据结构
struct WeatherData: Codable {
let temperature: Double
let description: String
}
// 异步网络请求函数
func fetchWeather(for city: String) async throws -> WeatherData {
// 构建URL
guard let url = URL(string: "https://api.weather.com/v1/weather?city=\(city)") else {
throw URLError(.badURL)
}
// 发起网络请求
let (data, _) = try await URLSession.shared.data(from: url)
// 解析JSON
let weather = try JSONDecoder().decode(WeatherData.self, from: data)
return weather
}
⚠️ 注意:上面的URL是示例,实际使用需要真实的天气API(比如OpenWeatherMap)
第二步:在ViewController中调用
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var temperatureLabel: UILabel!
@IBOutlet weak var descriptionLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// 注意:不能直接在viewDidLoad里调用async函数!
Task {
await loadWeatherData()
}
}
// 标记为async的函数
func loadWeatherData() async {
do {
// 使用await调用异步函数
let weather = try await fetchWeather(for: "Beijing")
// 更新UI必须在主线程
await MainActor.run {
self.temperatureLabel.text = "\(weather.temperature)°C"
self.descriptionLabel.text = weather.description
}
} catch {
print("加载天气失败: \(error)")
// 这里可以显示错误提示给用户
}
}
}
第三步:处理多个并发请求
假设你的产品需要同时获取多个城市的天气:
func loadMultipleCitiesWeather() async {
let cities = ["Beijing", "Shanghai", "Guangzhou"]
// 方式1:顺序执行(慢)
for city in cities {
let weather = try? await fetchWeather(for: city)
print("\(city): \(weather?.temperature ?? 0)°C")
}
// 方式2:并发执行(快)✅ 推荐
async let beijing = fetchWeather(for: "Beijing")
async let shanghai = fetchWeather(for: "Shanghai")
async let guangzhou = fetchWeather(for: "Guangzhou")
// 等待所有结果
let weathers = try? await [beijing, shanghai, guangzhou]
print("并发获取完成: \(weathers?.count ?? 0)个城市")
}
常见问题与解决方案
❓ 问题1:为什么不能在viewDidLoad直接调用async函数?
原因:viewDidLoad不是async函数,而调用async函数必须用await,await又只能在async函数内使用。
解决方案:使用Task来包装异步代码,就像我们在实战项目中做的那样。
❓ 问题2:如何在非async函数中调用async函数?
有时候你可能需要在按钮点击事件中调用异步函数:
@IBAction func refreshButtonTapped(_ sender: UIButton) {
Task {
await loadWeatherData()
}
}
❓ 问题3:如何取消正在进行的异步任务?
当用户离开页面时,应该取消网络请求以节省资源:
class ViewController: UIViewController {
private var weatherTask: Task<Void, Never>?
override func viewDidLoad() {
super.viewDidLoad()
weatherTask = Task {
await loadWeatherData()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
weatherTask?.cancel() // 取消任务
}
func loadWeatherData() async {
// 检查任务是否被取消
try Task.checkCancellation()
do {
let weather = try await fetchWeather(for: "Beijing")
// ... 处理结果
} catch {
if error is CancellationError {
print("任务被取消")
}
}
}
}
❓ 问题4:如何处理超时?
网络请求应该有超时机制:
func fetchWeatherWithTimeout(for city: String) async throws -> WeatherData {
// 创建一个延迟任务
let timeoutTask = Task.sleep(NSEC_PER_SEC * 10) // 10秒超时
// 并发执行网络请求和超时任务
let result = try await withThrowingTaskGroup(of: WeatherData.self) { group in
group.addTask {
return try await fetchWeather(for: city)
}
group.addTask {
try await timeoutTask.value
throw NSError(domain: "Timeout", code: 1, userInfo: nil)
}
// 返回第一个完成的任务结果
return try await group.next()!
}
return result
}
async/await vs 传统GCD对比
| 特性 | async/await | GCD (DispatchQueue) |
|---|---|---|
| 代码可读性 | ✅ 像同步代码一样清晰 | ❌ 回调嵌套,容易形成"金字塔" |
| 错误处理 | ✅ 使用try/catch | ❌ 需要手动传递错误 |
| 取消任务 | ✅ 内置取消机制 | ❌ 需要自己实现 |
| 调试难度 | ✅ 断点调试友好 | ❌ 跨线程调试复杂 |
| 兼容性 | ❌ 仅iOS 15+ | ✅ 所有iOS版本 |
面试题高频考点
如果你正在准备iOS面试,这些async/await相关的问题很可能会被问到:
async/await的底层原理是什么?
- 答:基于Swift的结构化并发模型,使用协程(coroutine)实现,比GCD更安全高效。
如何在主线程更新UI?
- 答:使用
MainActor.run { }或者将整个函数标记为@MainActor。
- 答:使用
async let和Task有什么区别?
- 答:
async let用于并发执行多个任务并等待结果,Task用于启动独立的异步任务。
- 答:
如何处理异步任务的依赖关系?
- 答:可以使用
TaskGroup或者按顺序await多个任务。
- 答:可以使用
学习建议与避坑指南
🚀 下一步学习路径
- 深入理解Actor模型:
MainActor只是冰山一角,了解自定义Actor如何保证数据安全 - 学习TaskGroup:处理动态数量的并发任务
- 掌握Continuation:将传统的回调API转换为async/await风格
- 实践Combine + async/await:现代iOS开发的最佳组合
⚠️ 我踩过的坑(帮你避雷)
- 不要在循环里频繁创建Task:这会导致资源浪费,应该考虑使用
TaskGroup - 及时取消不需要的任务:特别是在列表滚动或页面切换时
- 网络请求记得加错误处理:不要只写happy path
- 测试不同网络环境:弱网、无网情况下你的app表现如何?
📚 推荐学习资源
- 官方文档:Apple的Concurrency文档
- 免费教程:Hacking with Swift的Modern Swift Concurrency系列
- 实战练习:GitHub上搜索"swift concurrency example"
- 视频课程:Ray Wenderlich的Swift并发专题(付费但值得)
总结
async/await是Swift并发编程的重大进步,它让异步代码变得简单、安全、易维护。作为从培训班出来的开发者,我深知新手面对这些概念时的困惑。希望这篇实战导向的教程能帮你快速上手。
记住,编程最好的学习方式就是动手写代码。不要只是看,要把文中的例子都敲一遍,改一改参数,看看会发生什么。遇到问题不要慌,先检查那三条核心规则,90%的问题都能解决。
最后提醒一句:技术是为了服务产品和用户。无论你用多么炫酷的并发技术,最终都要回归到用户体验——快速响应、流畅操作、友好提示。这才是优秀开发者应该追求的目标!
加油,未来的iOS大神!🚀

评论 0