Swift并发编程不再难:用async/await搞定异步任务
大家好,我是你们的老朋友,一个在大厂搬砖三年、业余时间在B站分享技术的iOS开发者。最近在整理简历时,我发现“熟悉Swift并发模型”几乎成了中高级iOS岗位的标配要求。而很多刚入门的同学一看到 async/await 就头大——这不就是我当初学的时候的状态吗?今天,我就带大家从零开始,用一个真实的小项目,彻底搞懂Swift的并发编程。
别担心,即使你连“异步”是啥都不知道,也能跟上。我们还会巧妙地把区块链、前端这些热门词融入进来(别急,后面会解释),让你的简历更有亮点!
为什么你需要学会 async/await?
在iOS开发中,我们经常要处理耗时操作:比如从网络下载数据、读取大文件、或者调用区块链节点API。如果直接在主线程做这些事,App就会卡住——用户点按钮没反应,滑动列表卡成PPT。这就是“阻塞主线程”。
传统的解决方案是用回调(Callback)或GCD(Grand Central Dispatch),但代码容易变得又深又乱,俗称“回调地狱”。Swift 5.5 引入了 async/await,让异步代码写起来像同步代码一样清晰!
我当初学的时候,第一次看到
async/await的示例,简直不敢相信:原来异步可以这么优雅?
环境准备:三分钟搭好开发环境
要跑本文的代码,你需要:
- macOS(任何较新版本即可)
- Xcode 13.0 或更高版本(因为
async/await是 Swift 5.5 引入的) - 一个Apple ID(用于真机调试,可选)
验证你的环境
打开 Xcode → Create a new project → 选择 iOS App → 语言选 Swift → 运行到模拟器。
在 ContentView.swift(如果你用 SwiftUI)或 ViewController.swift 中粘贴以下代码:
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, async/await!")
.padding()
}
}
如果能正常编译运行,恭喜你,环境OK!
核心概念:用“点外卖”理解 async/await
想象你去餐厅点了一份牛排(耗时操作)。传统方式是:
- 你站在厨房门口等(同步)→ 其他人没法点餐,服务瘫痪。
- 或者你留下电话,厨师做好了打电话给你(回调)→ 但你可能同时点了三份菜,电话一响你都懵了:“哪份好了?”
而 async/await 就像:
- 你点完单就去玩手机(不阻塞),等服务员喊“您的牛排好了!”(await),你再回来吃。
关键术语解释
| 术语 | 通俗解释 |
|---|---|
async |
标记一个函数是“异步”的,它可能会暂停执行去等别的事 |
await |
在调用 async 函数时使用,表示“我愿意等它完成” |
Task |
一个独立的并发工作单元,类似线程但更轻量 |
| 主线程 | 负责UI更新的线程,绝对不能被阻塞! |
避坑指南:永远不要在主线程做耗时操作!
await后的代码可能回到主线程,所以后续处理要小心。
实战项目:构建一个“区块链行情查看器”
为了让你的简历更有竞争力,我们做一个结合区块链和前端展示的小App:从公开API获取比特币价格,并用SwiftUI展示。
为什么选区块链?因为它的API调用典型、数据结构简单,且是简历上的热门关键词。前端?没错,SwiftUI就是Apple生态的“前端”!
第一步:创建异步网络请求函数
新建一个文件 BlockchainService.swift:
import Foundation
class BlockchainService {
// 1. 声明为 async 函数
func fetchBitcoinPrice() async throws -> Double {
let url = URL(string: "https://api.coindesk.com/v1/bpi/currentprice/USD.json")!
// 2. 使用 URLSession 的 async 版本
let (data, _) = try await URLSession.shared.data(from: url)
// 3. 解析 JSON
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let bpi = json?["bpi"] as? [String: Any]
let usd = bpi?["USD"] as? [String: Any]
let rate = usd?["rate_float"] as? Double
guard let price = rate else {
throw NSError(domain: "ParsingError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to parse price"])
}
return price
}
}
关键点:
URLSession.shared.data(from:)现在有async版本,不用再写 completion handler!- 所有
await的地方都可能“暂停”,但不会卡住UI。
第二步:在UI中安全地调用异步函数
回到 ContentView.swift,修改如下:
import SwiftUI
struct ContentView: View {
@State private var bitcoinPrice: String = "Loading..."
@State private var errorMessage: String = ""
private let service = BlockchainService()
var body: some View {
VStack(spacing: 20) {
Text("比特币实时价格")
.font(.title)
Text(bitcoinPrice)
.font(.system(size: 24, weight: .bold))
.foregroundColor(.green)
if !errorMessage.isEmpty {
Text("错误: \(errorMessage)")
.foregroundColor(.red)
}
Button("刷新价格") {
// 4. 在 Task 中调用 async 函数
Task {
await loadPrice()
}
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
.padding()
.onAppear {
Task {
await loadPrice()
}
}
}
// 5. 封装加载逻辑
private func loadPrice() async {
do {
let price = try await service.fetchBitcoinPrice()
// 6. 回到主线程更新UI
await MainActor.run {
bitcoinPrice = "$\(String(format: "%.2f", price))"
errorMessage = ""
}
} catch {
await MainActor.run {
errorMessage = error.localizedDescription
bitcoinPrice = "N/A"
}
}
}
}
逐行解析:
@State变量用于驱动UI更新Task { ... }创建一个并发任务,在其中可以awaitonAppear里自动加载一次数据- 重要:更新UI必须在主线程!用
MainActor.run { ... }确保安全
我当初学的时候,就忘了
MainActor.run,结果App偶尔崩溃,调试了好久才发现是线程问题!
第三步:优化体验——添加加载状态
用户点击“刷新”时,应该有个loading提示:
// 在 ContentView 中新增状态
@State private var isLoading = false
// 修改 loadPrice 函数
private func loadPrice() async {
await MainActor.run {
isLoading = true
errorMessage = ""
}
do {
let price = try await service.fetchBitcoinPrice()
await MainActor.run {
bitcoinPrice = "$\(String(format: "%.2f", price))"
isLoading = false
}
} catch {
await MainActor.run {
errorMessage = error.localizedDescription
bitcoinPrice = "N/A"
isLoading = false
}
}
}
// 在 Button 上加 disabled 状态
Button("刷新价格") {
Task {
await loadPrice()
}
}
.disabled(isLoading) // 加载时禁用按钮
现在,你的App既有区块链数据,又有流畅的前端交互,简历上可以写“熟练使用Swift并发模型实现区块链数据可视化”啦!
新手常见问题解答
Q1:为什么我的 async 函数报错 “'async' in a function that does not support concurrency”?
原因:你可能在不支持并发的上下文中调用了 await,比如普通函数里。
解决:确保调用 await 的地方在一个 async 函数内,或者包裹在 Task { } 中。
Q2:如何同时发起多个异步请求?
用 async let 或 TaskGroup:
async let price1 = service.fetchBitcoinPrice()
async let price2 = service.fetchEthereumPrice()
let btc = try await price1
let eth = try await price2
这样两个请求会并发执行,而不是串行。
Q3:async/await 和 GCD 有什么区别?
| 特性 | GCD | async/await |
|---|---|---|
| 语法 | 回调嵌套,易混乱 | 线性代码,易读 |
| 错误处理 | 需手动传递Error | 原生支持throw/try/catch |
| 取消任务 | 复杂 | 内置Task取消机制 |
| 学习曲线 | 较陡 | 平缓 |
建议:新项目优先用 async/await,老代码可逐步迁移。
Q4:能在非Apple平台用 async/await 吗?
Swift的并发模型是语言级特性,理论上跨平台可用。但目前主要在Apple生态成熟(iOS 15+、macOS 12+)。低版本系统需用 backport 库(如 swift-concurrency-backport)。
下一步学习建议
恭喜你已经掌握了Swift并发的基础!接下来可以:
- 深入学习:了解
TaskGroup、AsyncStream、Actor等高级特性 - 项目实战:在你的个人项目中替换掉旧的GCD代码
- 面试准备:在简历中加入“使用Swift并发优化App性能,减少30%主线程阻塞”等量化描述
- 扩展领域:尝试用Swift调用更多区块链API(如以太坊、Solana),打造全栈能力
最后的小建议:技术日新月异,但核心思想不变——写出清晰、安全、高效的代码。
async/await不仅是语法糖,更是思维升级。当你能把复杂异步逻辑写得像散文一样流畅,你就离大厂Offer不远了!
如果你喜欢这种“项目驱动+避坑指南”的教学风格,欢迎来B站关注我,我会持续更新更多iOS实战教程。下期见!

评论 0