Swift并发新姿势:async/await让我少熬了三个夜
上周五晚上九点,娃刚睡着,我正准备打开Xcode改个紧急Bug,突然收到PM的消息:“双11大促首页加载慢得像蜗牛,用户都跑了!”——那一刻,我真的想把MacBook扔进尿布桶。
但作为一个在Swift组干了快两年的全职妈妈,我早就习惯了“一边哄睡一边debug”的生活。而且,咱可是Vim党啊!就算用Xcode,也得开着Vim插件才安心。今天这篇,就聊聊我是怎么用Swift 5.5引入的async/await,把那个要命的首页加载速度从3秒优化到800毫秒的。顺便,也给还在用回调地狱的兄弟们指条明路。
老代码,老问题
事情得从我们App的首页说起。首页要同时拉取:
- 用户个性化推荐(调内部推荐服务)
- 热门商品列表(调商品API)
- 促销活动横幅(调营销系统)
- 用户积分状态(调账户服务)
以前的写法,是经典的“回调嵌套+DispatchGroup”大法:
func loadHomePage() {
let group = DispatchGroup()
var recommendations: [Product] = []
var hotItems: [Product] = []
var banners: [Banner] = []
var points: Int = 0
group.enter()
fetchRecommendations { result in
recommendations = result
group.leave()
}
group.enter()
fetchHotItems { result in
hotItems = result
group.leave()
}
// ...还有两个类似的调用
group.notify(queue: .main) {
self.updateUI(with: recommendations, hotItems, banners, points)
}
}
看着就头大吧?更惨的是,上周测试同学报了个偶发Crash:“Concurrent access to variable 'recommendations' from multiple threads”。原来是多个网络回调在非主线程里同时修改同一个变量,线程安全没做好。我盯着屏幕,想起娃今天打翻的辅食碗——乱得一模一样。
试试GPT-4o给的建议?
其实去年我就听说Swift有了async/await,但一直没敢动生产代码。直到前两天,我让GPT-4o帮我重构这段逻辑,它直接甩给我一段清爽的代码:
func loadHomePage() async {
do {
async let recs = fetchRecommendationsAsync()
async let hot = fetchHotItemsAsync()
async let banners = fetchBannersAsync()
async let points = fetchUserPointsAsync()
let (recommendations, hotItems, banners, points) = await (recs, hot, banners, points)
updateUI(with: recommendations, hotItems, banners, points)
} catch {
handleError(error)
}
}
我愣了三秒——这不就是JavaScript的Promise.all吗?但更优雅!没有嵌套,没有手动管理group,变量作用域清晰,还自动保证线程安全。最关键的是,所有请求是并发执行的,而不是串行!
不过,我可没全信GPT-4o。毕竟,前几天我还试了Llama 3本地跑代码生成,结果它把URLSession.dataTask写成了同步阻塞调用,差点让我线上事故+1。AI辅助可以,但生产代码还得自己兜底。
动手改造:从闭包到结构化并发
说干就干。第一步,把所有网络请求函数改成async版本。以fetchRecommendations为例:
// 旧版(闭包回调)
func fetchRecommendations(completion: @escaping ([Product]) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, _ in
let products = decode(data)
DispatchQueue.main.async {
completion(products)
}
}.resume()
}
// 新版(async/await)
func fetchRecommendationsAsync() async throws -> [Product] {
let (data, _) = try await URLSession.shared.data(from: url)
return try decode(data)
}
注意几个关键点:
URLSession.dataTask→URLSession.data(from:),这是Swift 5.5新增的异步方法- 不再需要手动切回主线程!
async函数天然在调用者线程执行,而我们的loadHomePage()是在主线程调用的 - 错误处理用
throws,比传Result类型更直观
然后,把首页加载逻辑重写成前面GPT-4o给的那段。部署测试环境,跑起来——丝滑!
但别急着提交。我立刻想到一个问题:如果某个接口超时或失败,会不会拖垮整个页面?
查了文档才发现,async let启动的任务是独立的,一个失败不会影响其他。但await会等待所有任务完成。为了更精细的控制,我改用TaskGroup:
func loadHomePage() async {
var results: [Any] = Array(repeating: nil, count: 4)
await withTaskGroup(of: (Int, Any).self) { group in
group.addTask { (0, try await fetchRecommendationsAsync()) }
group.addTask { (1, try await fetchHotItemsAsync()) }
group.addTask { (2, try await fetchBannersAsync()) }
group.addTask { (3, try await fetchUserPointsAsync()) }
for await (index, result) in group {
results[index] = result
}
}
// 即使某个任务失败,其他结果仍可用
updateUIWithPartialData(results)
}
这样,就算推荐服务挂了,热门商品和横幅照样能展示,用户体验不至于崩盘。
真实世界的坑:别被“并发”骗了
你以为这就完了?Too young!
上线前一天,测试同学又来找我:“首页偶尔白屏,日志显示所有请求都成功了,但UI没更新。” 我心里一紧——该不会又是什么线程问题?
调试发现,罪魁祸首是updateUI方法。虽然async函数在主线程调用,但如果你在await之后做了耗时操作(比如解析大量数据),仍然会阻塞主线程!
// 危险!
let products = await fetchProducts()
let processed = heavyProcess(products) // 别在主线程做这个!
updateUI(processed)
正确做法是用Task.detached把计算移到后台:
let products = await fetchProducts()
let processed = await Task.detached {
return heavyProcess(products)
}.value
updateUI(processed)
或者,更地道的Swift方式——用@MainActor标记UI更新函数,确保它只在主线程执行:
@MainActor
func updateUI(_ data: HomePageData) {
// 安全!
}
性能对比:数字不说谎
重构前后,我做了个简单压测(模拟弱网环境):
| 指标 | 旧版(回调) | 新版(async/await) |
|---|---|---|
| 平均加载时间 | 2.8s | 0.78s |
| 最大内存占用 | 42MB | 38MB |
| Crash率 | 0.3% | 0.02% |
| 代码行数 | 86行 | 42行 |
最爽的是,代码量几乎砍半,而且逻辑一目了然。连我们组新来的实习生都说:“这代码我能看懂!”
给同行的建议:别怕新东西
我知道,很多老iOS开发者(包括我)对新语法有抵触。毕竟,咱们经历过ARC、Swift ABI稳定、iOS 13暗黑模式……每次升级都像拆炸弹。但async/await真的不一样,它是Apple官方力推的并发模型,WWDC讲了三年,文档也成熟了。
而且,App Store审核完全支持。我们这次更新过审只用了12小时,审核备注里还夸“性能体验优秀”(虽然可能只是模板话)。
当然,也不是所有场景都适合。比如,如果你的App最低支持iOS 12,那还是老实写回调吧。但只要目标是iOS 15+,闭眼冲async/await就对了。
代码人生:带娃写码,也要追求优雅
写这篇文章时,娃又醒了,哭着要喝奶。我暂停打字,冲奶粉,换尿布,再回来继续。这就是我的日常——代码和尿布齐飞,需求与辅食共舞。
但正是这种碎片化的生活,让我更珍惜每一行干净、高效、可维护的代码。async/await不仅提升了App性能,也让我少熬了几个夜,多陪了娃几小时。技术的价值,不就在于此吗?
所以,别再说“等有空再学新东西”了。利用娃午睡的半小时,重构一个函数;趁着喂奶的间隙,读一篇文档。代码人生,本就不该是非此即彼的选择题。
最后,如果你也在用Swift做并发,欢迎留言交流。说不定,下次你遇到的坑,就是我昨天踩过的。

评论 0