Swift并发编程:async/await实战——一个成都前测试转开发的血泪心得
大家好,我是小杨,坐标成都,一个从测试工程师“叛逃”到iOS开发三年的打工人。现在在一家本地SaaS公司写Swift,月薪22k(税前),房租3500,老婆刚怀孕三个月——对,就是那种既不敢裸辞又想偷偷摸鱼学新技术的状态。
今天不聊职业规划,也不吐槽成都码农工资低得像被泡在火锅底料里腌入味了,咱们聊聊Swift并发编程里的async/await。这事得从去年十月说起。
一、起因:那个让我半夜惊醒的爬虫任务
去年十月初,公司接了个新需求:要做一个内部数据监控平台,每天凌晨自动抓取竞品App的版本更新日志、评分变化、关键词排名……说白了,就是个轻量级爬虫系统,跑在macOS后台。
我主动揽下了这活——毕竟之前做测试时就写过Python爬虫,以为不过是换件衣服的事。结果刚开工三天,就被卡住了。
我们的目标App信息分散在多个页面,有些接口还带token验证,请求之间还有依赖关系。比如:先登录 → 获取用户ID → 拿ID查详情 → 再拿详情里的字段去另一个接口拉数据……
用传统的Completion Handler?代码直接嵌套五层,像极了我在春熙路排队买奶茶时的心情——又急又乱。更糟的是,任务一多,主线程直接卡死,Mac风扇狂转,吓得我赶紧Command + .终止进程。
那天晚上11点,我瘫在沙发上刷技术群,看到有人发了一张书页截图:
“使用 async/await 可以让异步代码看起来像同步一样线性执行。”
配图是《Modern Concurrency in Swift》——一本讲Swift并发的英文书。我当时心想:这不就是救星吗?
但问题是,我英语阅读速度堪比蜗牛爬青城山,而且这本书在国内连纸质版都买不到,电子版要80刀(约570块人民币)。我盯着屏幕,内心疯狂挣扎:“值不值?会不会又是看了三页就吃灰?”
最后咬咬牙,用老婆的支付宝花呗买了——理由是“投资自己”。她翻了个白眼:“上次你说学Rust也是这么说的,结果呢?”
二、入门:从“看不懂”到“好像能跑”
拿到书之后,我花了整整一周,每天下班后啃两小时。说实话,前两章直接劝退:Task、Actor、structured concurrency……术语多得像火锅里的毛肚,数都数不清。
但我不甘心。想起以前做自动化测试时,也是一步步从Appium小白熬过来的。于是决定边学边写——把那个爬虫项目当成练兵场。
第一个突破点是:把一个简单的网络请求改成async函数。
以前的写法:
func fetchVersion(completion: @escaping (String?) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, _ in
let version = String(data: data!, encoding: .utf8)
completion(version)
}.resume()
}
改成async/await后:
func fetchVersion() async throws -> String {
let (data, _) = try await URLSession.shared.data(from: url)
return String(decoding: data, as: UTF8.self)
}
天啊!没有回调地狱了!调用的时候直接:
let version = try await fetchSJVersion()
print("当前版本:\(version)")
那一刻,我差点在出租屋里喊出声:“这TM才是人写的代码!”
但很快,现实给了我一巴掌。
三、实战踩坑:并发不是万能胶水
既然单个请求能搞定,那我把整个爬虫流程全async化吧!信心满满地写了这么一段:
let loginResult = try await login()
let userInfo = try await getUserInfo(userId: loginResult.id)
let appDetails = try await getAppDetails(appId: userInfo.appId)
let keywords = try await getKeywords(rankId: appDetails.rankId)
逻辑清晰,顺序执行,完美!
可一跑起来,发现整个流程耗时40秒。而竞品数据每5分钟更新一次,这意味着我的爬虫根本追不上节奏。
问题出在哪?我把本该并发的操作写成了串行!
其实getAppDetails和getKeywords完全可以并行请求——它们不依赖彼此。但我傻乎乎地用await一个接一个等,等于主动放弃了并发优势。
这时候,书里提到的async let和TaskGroup派上用场了。
我改成这样:
async let detailsTask = getAppDetails(appId: userInfo.appId)
async let keywordsTask = getKeywords(rankId: appDetails.rankId)
let (details, keywords) = try await (detailsTask, keywordsTask)
或者用withTaskGroup批量处理多个独立请求:
var results: [String] = []
await withTaskGroup(of: String.self) { group in
urls.forEach { url in
group.addTask {
return try await fetchData(from: url)
}
}
for try await result in group {
results.append(result)
}
}
改完一测,总耗时从40秒降到12秒!那一刻,我感觉自己的CPU都超频了。
四、资源整理:别再只看官方文档了
很多人学Swift并发,第一反应是去翻Apple官方文档。但说实话,官方文档写得……嗯,很“苹果”——概念精准但缺乏场景。
除了那本《Modern Concurrency in Swift》(强烈推荐,虽然贵但值得),我还挖到几个宝藏中文资源:
- 《Swift并发编程入门》(GitHub开源项目):作者是位上海的iOS老哥,用大量生活化比喻讲解Task和Actor,比如把Actor比作“独占厕所”,只有一个人能进。
- B站UP主“喵神” 的async/await系列视频:虽然更新慢,但每集都直击痛点,尤其是讲“结构化并发”那期,让我豁然开朗。
- Ray Wenderlich官网的Swift并发教程:免费章节就够用,例子全是真实App场景,比如下载图片、处理用户输入等。
另外,我建了个Notion页面,专门收集Swift并发相关的问题和解决方案。比如:
- 如何在非async函数里调用async函数?(答案:用
Task { }) - 如何取消一个正在进行的Task?(用
Task.isCancelled检查) - Actor怎么避免死锁?(别在actor方法里await另一个actor的方法)
这些碎片知识,光看书是记不住的,必须在真实项目里摔几次跤才能长记性。
五、反思:为什么我当初那么抗拒学并发?
说实话,刚转开发那会儿,我对“并发”“多线程”这类词有PTSD。因为在测试岗时,每次遇到App卡顿、闪退,开发都会甩一句:“是不是你并发测试搞的?”——搞得我像罪魁祸首。
后来才明白,并发不是洪水猛兽,而是工具。就像菜刀,用得好能切出宫保鸡丁,用不好可能剁到手指。关键在于理解它的边界和规则。
而Swift的async/await设计哲学,恰恰是降低并发的使用门槛。它用编译器帮你检查数据竞争,用结构化并发防止任务泄漏——这对我这种半路出家、基础不牢的人来说,简直是救命稻草。
六、给同行的建议:别怕“晚”,就怕“停”
我知道很多转行的朋友(尤其是从测试、产品转开发的)会有种“我基础差,学不动新东西”的焦虑。我自己也经历过:去年面试时,被问到“Actor和Class的区别”,当场懵圈,回来查资料才发现自己连基本概念都没搞清。
但我想说:技术栈可以慢慢补,思维惯性才是最大的敌人。
async/await不是银弹,但它代表了一种趋势:异步编程正在变得越来越“同步化”。未来不管是Swift、Kotlin还是JavaScript,都会朝着这个方向演进。早点掌握,就少走弯路。
而且,实践是最好的老师。别等到“准备好了”再动手——我就是在那个破爬虫项目里,硬着头皮改了十几版代码,才真正理解什么是“结构化并发”。
七、结尾:在成都,做个快乐的“慢”程序员
上周五晚上,我终于把爬虫系统上线了。老板拍我肩膀说:“效率提升明显,下季度给你加薪。” 我心里盘算:要是能涨到25k,就能换个离地铁近点的房子,老婆产检也方便些。
回家路上,路过小区门口的烧烤摊,点了份烤苕皮,配冰啤酒。看着手机里Xcode控制台输出的“所有任务完成,耗时9.3秒”,突然觉得——在成都这座“慢城市”里,做一个不断学习的程序员,其实挺幸福的。
工资不高,但生活成本低;技术更新快,但社区资源多。只要不停下脚步,哪怕是从测试转来的“野路子”,也能写出优雅的async/await代码。
共勉。
P.S. 如果你也正在学Swift并发,欢迎私信交流(虽然我可能回得慢,毕竟要陪老婆产检 😅)。顺便,那本《Modern Concurrency in Swift》,我可以分享PDF笔记——但请支持正版,毕竟作者也得吃饭,对吧?

评论 0