Swift并发编程:async/await实战入门指南

徐伟
2026-02-12 07:08
阅读 800

大家好,我是你们的学长,一名211高校计算机专业的研究生。平时我特别喜欢写技术博客,尤其是帮助刚入行的新手少走弯路。今天这篇教程,就是想和大家聊聊 Swift 并发编程中的 async/await —— 这是 Apple 从 iOS 15 开始大力推广的现代异步编程方式。

我当初学的时候,也踩过不少坑。比如在主线程里直接调用网络请求,结果 App 卡死;或者用回调嵌套写出“回调地狱”代码,自己都看不懂。后来接触到 async/await,才真正体会到“异步也能写得像同步一样清晰”。

这篇文章,我会用最通俗的语言、最实用的例子,带你从零上手 async/await,最后还会用它做一个小项目。无论你是刚学 Swift 的新手,还是只会用 DispatchQueue 的老手,都能从中受益。


为什么需要 async/await?

在移动开发中,很多操作(比如网络请求、文件读写、数据库查询)都是 耗时的。如果在主线程直接执行这些操作,App 会卡住,甚至被系统强制退出。

传统的解决方案是使用 闭包回调(callback)GCD(Grand Central Dispatch)。但它们容易导致:

  • 代码嵌套深(“回调地狱”)
  • 错误处理复杂
  • 难以组合多个异步任务

async/await 是 Swift 5.5 引入的 结构化并发模型 的核心,它让异步代码看起来像同步代码一样线性、清晰,还能自动处理线程切换和错误传播。

✅ 核心优势:写法简单 + 自动调度 + 错误处理统一


环境准备:你的开发环境支持吗?

async/await 需要满足以下条件:

要求项 最低版本
Xcode 13.0+
iOS 15.0+
macOS 12.0+
Swift 5.5+

📌 注意:如果你的项目还要支持 iOS 14 及以下,不能直接使用 async/await。可以考虑用 @available 做版本判断,或用第三方库(如 Combine)作为过渡。

检查你的环境

  1. 打开 Xcode → About Xcode,确认版本 ≥ 13.0
  2. Project SettingsDeployment Info 中,将 iOS Deployment Target 设为 15.0 或更高

如果你还在用旧版 Xcode,建议升级。Xcode 15 已经非常稳定,而且对并发调试支持更好。


核心概念:用最简单的语言讲清楚

1. async:标记一个函数是“异步的”

当你看到一个函数声明为:

func fetchData() async -> String { ... }

这意味着:调用这个函数时,程序不会立刻返回结果,而是可能“暂停”一会儿,等数据准备好再继续

⚠️ 重要:async 函数只能在 async 上下文(比如另一个 async 函数,或 Task 中)被调用。

2. await:等待异步结果

当你调用 async 函数时,必须加上 await

let result = await fetchData()

这行代码会 暂停当前任务,直到 fetchData() 返回结果,然后继续执行下一行。

💡 类比:就像你去餐厅点餐(await),服务员去厨房做菜(async),你坐着等(暂停),菜好了再吃(继续执行)。

3. Task:启动异步任务的入口

在非异步上下文中(比如 viewDidLoad),你需要用 Task 来启动异步操作:

override func viewDidLoad() {
    super.viewDidLoad()
    Task {
        let data = await fetchData()
        print(data)
    }
}

Task 会自动在后台线程执行异步代码,并在需要更新 UI 时切回主线程(稍后会讲)。


实战项目:用 async/await 获取天气数据

我们来做一个简单的 App:输入城市名,显示该城市的当前温度。

第一步:创建异步网络请求函数

// 模拟网络请求(实际项目中替换为真实 API)
func fetchWeather(for city: String) async throws -> String {
    // 模拟网络延迟
    try await Task.sleep(nanoseconds: 1_000_000_000) // 1秒
    
    // 模拟成功响应
    return "The temperature in \(city) is 25°C"
}

🔍 说明:

  • throws 表示这个函数可能抛出错误
  • Task.sleep 是异步的延时函数(替代 sleep,后者会阻塞线程)

第二步:在 ViewController 中调用

import UIKit

class WeatherViewController: UIViewController {
    @IBOutlet weak var cityTextField: UITextField!
    @IBOutlet weak var resultLabel: UILabel!
    
    @IBAction func fetchButtonTapped(_ sender: UIButton) {
        guard let city = cityTextField.text, !city.isEmpty else {
            resultLabel.text = "Please enter a city"
            return
        }
        
        // 启动异步任务
        Task {
            do {
                let weather = try await fetchWeather(for: city)
                // ⚠️ 注意:此时不在主线程!
                await MainActor.run {
                    self.resultLabel.text = weather
                }
            } catch {
                await MainActor.run {
                    self.resultLabel.text = "Failed to fetch weather"
                }
            }
        }
    }
}

关键点解析

  1. Task 包裹异步代码:因为 fetchButtonTapped 不是 async 函数
  2. try await:调用可能抛错的异步函数
  3. MainActor.run:确保 UI 更新在主线程执行

🛑 新手常犯错误:直接在 Task 里更新 UI,导致崩溃或界面不刷新!


如何同时发起多个异步请求?

假设你想同时获取北京、上海、广州的天气,而不是一个一个等。

错误做法(串行):

let beijing = await fetchWeather(for: "Beijing")
let shanghai = await fetchWeather(for: "Shanghai") // 等北京完才开始
let guangzhou = await fetchWeather(for: "Guangzhou")

总耗时 ≈ 3 秒

正确做法(并发):

let beijingTask = Task { try await fetchWeather(for: "Beijing") }
let shanghaiTask = Task { try await fetchWeather(for: "Shanghai") }
let guangzhouTask = Task { try await fetchWeather(for: "Guangzhou") }

let beijing = try await beijingTask.value
let shanghai = try await shanghaiTask.value
let guangzhou = try await guangzhouTask.value

总耗时 ≈ 1 秒(三个请求同时发出)

✅ 技巧:用 Task 提前启动任务,再用 .value 等待结果,实现并发。


关于 Cursor 和 v0 的说明

最近很多同学在用 AI 编程工具 Cursor(特别是 v0 版本)来辅助写代码。我自己也在用!

Cursor v0 对 Swift 并发的支持如何?

功能 支持情况 建议
生成 async/await 代码 ✅ 很好 可以快速生成基础结构
处理 MainActor ⚠️ 有时遗漏 需要手动检查 UI 更新是否在主线程
错误处理 (try/catch) ✅ 一般 建议自己补充具体错误类型
并发任务 (Task 并行) ⚠️ 容易写成串行 需要明确提示“并发执行”

📝 我的经验:用 Cursor 生成初稿,自己再优化并发逻辑和线程安全。不要完全依赖 AI,尤其是涉及 UI 更新的部分。


新手常见问题 & 解决方案

❓ 问题1:为什么我的 async 函数报错 “'async' in a function that does not support concurrency”?

原因:你在非异步上下文中调用了 async 函数,且没有用 Task 包裹。

解决

// 错误
let result = await fetchData()

// 正确
Task {
    let result = await fetchData()
}

❓ 问题2:UI 没有更新,或者 App 崩溃?

原因:在后台线程直接修改了 UI 元素。

解决:所有 UI 更新必须在主线程,使用 MainActor.run

await MainActor.run {
    label.text = "Updated"
}

或者将整个函数标记为 @MainActor(如果整个函数都需要在主线程):

@MainActor
func updateUI(with text: String) {
    label.text = text
}

❓ 问题3:如何取消一个正在进行的异步任务?

Swift 并发支持 任务取消。你可以监听 Task.isCancelled

func longRunningTask() async {
    for i in 1...100 {
        if Task.isCancelled {
            print("Task cancelled")
            return
        }
        try? await Task.sleep(nanoseconds: 100_000_000) // 0.1秒
        print("Progress: \(i)%")
    }
}

// 取消任务
let task = Task {
    await longRunningTask()
}
task.cancel() // 主动取消

💡 实际项目中,可以在 viewWillDisappear 中取消网络请求,避免内存泄漏。


学习建议 & 下一步

避坑指南

  • 永远不要在主线程做耗时操作,即使用了 async/await,底层仍可能阻塞
  • UI 更新必须用 MainActor,这是铁律
  • 错误处理不要忽略,用 do-catchtry? 明确处理

推荐学习路径

  1. 掌握基础async/await + Task + MainActor
  2. 进阶AsyncStream(处理连续数据流,如 WebSocket)
  3. 高阶TaskGroup(动态并发任务)、Actor(状态隔离)
  4. 实战:结合 URLSession、Core Data 等系统框架重写异步代码

推荐资源

  • Apple 官方文档:Concurrency
  • WWDC2021 视频:Meet async/await in Swift
  • 书籍:《Modern Swift Concurrency》

结语

async/await 是 Swift 并发编程的未来。它让异步代码变得可读、可维护、可组合。虽然一开始可能会对“哪里能用 await”感到困惑,但只要记住两个原则:

  1. async 函数只能在 async 上下文调用
  2. UI 更新必须回到主线程

你就能写出安全、高效的并发代码。

我当初也是从“回调地狱”爬出来的,深知清晰的异步模型有多重要。希望这篇教程能帮你少走弯路。如果你觉得有用,欢迎关注我的博客,我会持续更新更多 iOS 实战内容!

🌟 记住:写代码不是为了跑起来,而是为了以后还能看懂。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝