Swift并发新姿势:async/await让我少熬了三个夜

周末写代码
2026-03-07 18:27
阅读 1924

上周五晚上九点,娃刚睡着,我正准备打开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)
}

注意几个关键点:

  1. URLSession.dataTaskURLSession.data(from:),这是Swift 5.5新增的异步方法
  2. 不再需要手动切回主线程!async函数天然在调用者线程执行,而我们的loadHomePage()是在主线程调用的
  3. 错误处理用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

最热最新
暂无评论
周末写代码Lv.1
0
影响力
0
文章
0
粉丝