Swift并发编程:从零开始掌握async/await实战技巧

#曹庆林
2026-04-24 22:37
阅读 530

大家好,我是小林,一名211高校的计算机专业研究生。平时除了写论文、调模型,我还特别喜欢在技术博客上分享自己的学习心得。最近有不少刚入门iOS开发的朋友私信我,说对Swift的并发编程一头雾水——尤其是async/await这种“看起来很高级”的语法。其实我当初学的时候也踩过不少坑:明明代码逻辑没问题,App却卡得动不了;网络请求一多就崩溃;甚至因为不理解异步回调,写了整整三天才调通一个简单的登录功能……

正因如此,我才决定写下这篇面向完全零基础读者的教程。无论你是刚接触Swift的新手,还是从Objective-C转过来的老兵,只要跟着本文一步步来,你都能轻松掌握现代Swift并发编程的核心思想和最佳实践。


为什么需要并发?先搞懂这个问题

在讲async/await之前,我们得先明白:什么是并发?为什么要用它?

想象一下你在煮面:

  • 如果你先把水烧开(等5分钟),再下面条(煮3分钟),最后洗菜切菜(花4分钟)……整个过程要12分钟。
  • 但如果你一边烧水,一边洗菜切菜,等水开了立刻下面,总时间可能只要8分钟。

这就是“并发”的核心思想:让多个任务同时进行,提高效率

在App开发中,很多操作都很“慢”:

  • 网络请求(可能几百毫秒到几秒)
  • 文件读写(尤其是大文件)
  • 图片处理、视频解码
  • 数据库查询

如果这些操作都在主线程(也就是UI线程)里同步执行,你的App就会卡住不动——用户点按钮没反应,滑动列表卡成PPT。苹果官方明确要求:耗时操作必须放到后台线程执行

过去,我们用GCD(Grand Central Dispatch)或OperationQueue来处理并发,但代码容易变得复杂、难以维护,还容易出现回调地狱(callback hell)。而Swift 5.5引入的async/await,正是为了解决这些问题!

💡 小知识:虽然DeepSeek、Llama这类大模型本身不直接用于iOS开发,但它们背后依赖的高性能并发处理思想,与我们今天要学的内容高度相关。理解并发,是迈向高阶开发的第一步。


环境准备:确保你能跑起来代码

要使用async/await,你需要满足以下条件:

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

验证方法:打开Xcode → 菜单栏 Xcode > About Xcode,查看版本号。

创建测试项目

  1. 打开Xcode,点击 Create a new Xcode project
  2. 选择 App → 点击 Next
  3. 填写:
    • Product Name: AsyncAwaitDemo
    • Interface: SwiftUI
    • Language: Swift
  4. 点击 Next,选择保存位置,创建项目

⚠️ 注意:虽然async/await在UIKit中也能用,但SwiftUI对并发支持更友好,所以我们用SwiftUI作为演示框架。


核心概念:用最简单的话讲清楚async/await

什么是 async 函数?

async 是一个关键字,标记一个函数是“异步”的。这意味着:

  • 这个函数内部可能会执行耗时操作(如网络请求)
  • 调用它时,不会立即返回结果,而是“承诺稍后给你”
  • 必须配合 await 使用
// 定义一个异步函数
func fetchUserName() async -> String {
    // 模拟网络延迟
    try? await Task.sleep(for: .seconds(2))
    return "小林同学"
}

什么是 await?

await 用于“等待”一个异步操作完成。它会暂停当前函数的执行,但不会阻塞整个线程!这是关键区别。

// 调用异步函数
let name = await fetchUserName()
print("欢迎你, \(name)!") // 2秒后打印

🌟 重点理解await 不是“卡住”,而是把控制权交还给系统,等数据准备好后再继续。这就像你去餐厅点菜,服务员记下订单后你就继续聊天(不傻等),等菜好了他再叫你。

主线程安全:@MainActor 是什么?

在SwiftUI中,所有UI更新必须在主线程进行。如果你在后台线程直接修改@State变量,App会崩溃!

解决方案:使用 @MainActor 标记需要在主线程执行的函数。

@MainActor
func updateUI(with name: String) {
    self.userName = name // 安全地更新UI
}

或者,在异步函数内部显式切换到主线程:

Task { @MainActor in
    self.userName = await fetchUserName()
}

实战项目:做一个带加载动画的用户信息卡片

现在,我们来动手做一个小功能:点击按钮后,从“服务器”获取用户信息,并显示在卡片上。过程中会有加载状态提示。

第一步:定义数据模型

// User.swift
struct User: Codable {
    let id: Int
    let name: String
    let email: String
}

第二步:模拟网络请求(异步函数)

// NetworkService.swift
import Foundation

actor NetworkService {
    func fetchUser(id: Int) async throws -> User {
        // 模拟网络延迟
        try await Task.sleep(for: .seconds(1.5))
        
        // 模拟返回数据
        return User(id: id, name: "张三", email: "zhangsan@example.com")
    }
}

🔍 为什么用 actor?
actor 是Swift 5.5引入的并发安全类型,自动保证其内部状态不会被多个线程同时访问,避免数据竞争。对于网络服务这类共享资源,使用actor是最安全的做法。

第三步:构建UI界面

// ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var user: User?
    @State private var isLoading = false
    @State private var errorMessage: String?
    
    private let networkService = NetworkService()
    
    var body: some View {
        VStack(spacing: 20) {
            if isLoading {
                ProgressView("加载中...")
                    .font(.headline)
            } else if let user = user {
                VStack {
                    Text("ID: \(user.id)")
                        .font(.title2)
                    Text(user.name)
                        .font(.title)
                    Text(user.email)
                        .font(.subheadline)
                        .foregroundColor(.secondary)
                }
                .padding()
                .background(Color.gray.opacity(0.1))
                .cornerRadius(10)
            } else if let errorMessage = errorMessage {
                Text("错误: \(errorMessage)")
                    .foregroundColor(.red)
            } else {
                Text("点击下方按钮获取用户信息")
                    .font(.body)
                    .foregroundColor(.gray)
            }
            
            Button("获取用户 #1") {
                loadUser(id: 1)
            }
            .buttonStyle(.borderedProminent)
            .disabled(isLoading)
        }
        .padding()
    }
    
    private func loadUser(id: Int) {
        isLoading = true
        errorMessage = nil
        
        Task {
            do {
                let fetchedUser = try await networkService.fetchUser(id: id)
                await MainActor.run {
                    self.user = fetchedUser
                    self.isLoading = false
                }
            } catch {
                await MainActor.run {
                    self.errorMessage = error.localizedDescription
                    self.isLoading = false
                }
            }
        }
    }
}

关键代码解析

  1. Task { }:启动一个独立的异步任务,避免阻塞UI。
  2. try await:调用可能抛出错误的异步函数。
  3. await MainActor.run { }:确保UI更新在主线程安全执行。
  4. 状态管理:通过@State变量控制加载、成功、错误三种状态。

运行效果:点击按钮后,显示“加载中...”,1.5秒后显示用户信息。期间按钮不可点击,防止重复请求。


新手常见问题 & 避坑指南

❓ 问题1:为什么不能直接在Button action里写 await?

// 错误写法!
Button("获取") {
    let user = await fetchUser() // ❌ 编译错误
}

原因:Button的action是一个同步闭包,不能直接调用await

正确做法:用Task { }包裹异步代码,如上文所示。


❓ 问题2:如何取消正在进行的请求?

比如用户点了“获取”,又马上点了“取消”。

解决方案:使用Task的取消机制。

@State private var loadingTask: Task<Void, Never>?

private func loadUser(id: Int) {
    // 取消之前的任务
    loadingTask?.cancel()
    
    isLoading = true
    errorMessage = nil
    
    loadingTask = Task {
        do {
            let fetchedUser = try await networkService.fetchUser(id: id)
            if !Task.isCancelled { // 检查是否被取消
                await MainActor.run {
                    self.user = fetchedUser
                    self.isLoading = false
                }
            }
        } catch {
            if !Task.isCancelled {
                await MainActor.run {
                    self.errorMessage = error.localizedDescription
                    self.isLoading = false
                }
            }
        }
    }
}

❓ 问题3:async/await 和 Combine 或 RxSwift 冲突吗?

完全不冲突!实际上:

  • async/await 更适合一次性异步操作(如API调用)
  • Combine/RxSwift 更适合持续的数据流(如文本框输入、实时股票价格)

你可以混合使用。例如,把Combine的Publisher转换为async序列:

for await value in publisher.values {
    print(value)
}

❓ 问题4:能在iOS 13/14上用吗?

不行。async/await 需要 iOS 15+。如果你的App要支持旧系统,可以:

  • 使用#available做版本判断
  • 或者继续用completion handler + GCD

但强烈建议新项目直接面向iOS 15+,享受现代并发带来的简洁与安全。


学习建议:下一步该学什么?

掌握了async/await,你已经迈入了现代Swift并发的大门。接下来,我建议你深入以下方向:

主题 推荐理由 学习资源
TaskGroup / async let 并行执行多个异步任务 Apple官方文档
Continuation (CheckedContinuation) 将传统回调API转为async/await WWDC21 Session 10215
Structured Concurrency 理解任务父子关系与生命周期 《Modern Swift Concurrency》by John Sundell
Actors 深度使用 构建线程安全的状态管理器 Swift论坛并发板块

📚 避坑提醒:不要一上来就学unsafe相关的并发API(如UnownedTask),先打好结构化并发的基础更重要。

另外,虽然DeepSeek、Llama这类大语言模型不能直接帮你写iOS代码,但你可以用它们:

  • 解释报错信息
  • 生成测试数据
  • 对比不同并发方案的优劣

但记住:最终代码一定要自己理解并测试,别盲目信任AI输出!


结语

回想起我第一次用async/await重构旧项目的网络层,代码量减少了40%,逻辑清晰到连实习生都能看懂。这就是现代并发的魅力——用同步的思维写异步的代码

希望这篇教程能帮你少走弯路。如果你在实践中遇到问题,欢迎在我的博客评论区留言,我会尽力解答。也别忘了点赞收藏,让更多初学者看到这份“避坑指南”!

下期预告:《SwiftUI状态管理终极指南:@State, @Binding, @ObservedObject 全解析》

加油,未来的iOS大神!🚀

评论 0

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