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

程序员的日常信号
2025-12-17 16:54
阅读 278

大家好,我是掘金上常写iOS入门教程的全栈工程师。最近收到不少读者私信,说想学Swift并发但被各种GCDOperationQueue绕晕了。其实我当初学的时候也一样——直到Swift 5.5引入了async/await,我才真正理解了“优雅地处理异步任务”是什么意思。

今天这篇教程,我会用最简单的语言+最实用的例子,带你从零掌握Swift并发编程。无论你是刚学完Swift基础语法,还是正在做第一个App项目,都能轻松上手。


一、什么是async/await?它能做什么?

在移动开发中,我们经常要处理“耗时操作”:比如从网络下载数据、读取本地文件、或者执行一个复杂的计算。如果把这些操作放在主线程(也就是UI线程)里直接运行,App就会卡住甚至闪退。

传统做法是用回调(callback)或者闭包来处理异步结果,但代码会一层套一层,形成“回调地狱”。

async/await就是Swift提供的现代解决方案:它让你用同步的写法写出异步的逻辑,代码清晰、易读、不易出错。

📌 一句话总结async/await让异步代码像写普通函数一样简单。


二、环境准备:你需要什么?

在开始前,请确保你的开发环境满足以下条件:

项目 要求
Xcode版本 13.0 或更高(推荐最新稳定版)
iOS部署目标 iOS 15.0 或更高
Swift版本 5.5 或更高

💡 为什么要求iOS 15+?
因为async/await的核心API(如Taskasync let等)是在Swift 5.5中引入的,而Xcode 13才完整支持这些特性。如果你的项目必须支持iOS 14或更低版本,可以使用backport库,但本教程不涉及。

验证方法:新建一个Playground,输入以下代码:

import Foundation

func fetchData() async -> String {
    return "Hello, async!"
}

Task {
    let result = await fetchData()
    print(result)
}

如果能正常编译运行并打印出Hello, async!,说明环境已就绪。


三、核心概念:用最简单的话讲清楚

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

当你在一个函数声明后面加上async,就表示这个函数内部可能需要等待某些操作完成(比如网络请求),调用它时必须用await

// 异步函数
func downloadBookTitle() async -> String {
    // 模拟网络延迟
    try? await Task.sleep(nanoseconds: 1_000_000_000) // 1秒
    return "《Swift并发编程实战》"
}

2. await:等待异步函数的结果

只有在async上下文中(比如另一个async函数里,或Task中),你才能使用await

// 在Task中调用
Task {
    let title = await downloadBookTitle()
    print("下载完成:", title)
}

3. Task:启动一个异步任务

Task就像是一个“异步容器”,你可以在里面安全地使用await。它会自动在后台线程执行,不会阻塞UI。

安全意识提醒
所有网络请求、文件读写等耗时操作,都应放在Taskasync函数中,绝不能直接在主线程执行,否则会导致App卡顿甚至被系统杀死。


四、实战项目:做一个“书籍信息爬虫”

现在,我们来做一个小项目:从一个模拟的网络接口获取书籍信息,并展示在控制台。虽然不能真的做“爬虫”(那涉及法律和道德问题),但我们可以模拟一个合法的数据源来练习并发逻辑。

⚠️ 重要声明
本文中的“爬虫”仅用于教学演示,实际开发中请遵守网站robots.txt协议,使用官方API,并尊重版权。切勿对他人网站进行未授权的数据抓取!

步骤1:定义数据模型

struct Book: Codable {
    let id: Int
    let title: String
    let author: String
}

步骤2:模拟网络请求(替代真实爬虫)

// 模拟从“网络”获取书籍列表
func fetchBooks() async throws -> [Book] {
    // 模拟1秒延迟
    try await Task.sleep(nanoseconds: 1_000_000_000)
    
    // 返回模拟数据
    return [
        Book(id: 1, title: "《Swift并发指南》", author: "张三"),
        Book(id: 2, title: "《iOS性能优化》", author: "李四"),
        Book(id: 3, title: "《现代Swift实践》", author: "王五")
    ]
}

步骤3:并发获取多本书的详情(进阶用法)

假设每本书还有一个详情接口,我们需要同时获取多本书的详情:

func fetchBookDetail(for id: Int) async throws -> String {
    try await Task.sleep(nanoseconds: 500_000_000) // 0.5秒
    return "这是第\(id)本书的详细介绍..."
}

// 并发获取所有书籍详情
func fetchAllBookDetails() async throws -> [String] {
    let books = try await fetchBooks()
    
    // 使用async let实现并发
    async let detail1 = fetchBookDetail(for: books[0].id)
    async let detail2 = fetchBookDetail(for: books[1].id)
    async let detail3 = fetchBookDetail(for: books[2].id)
    
    // 等待所有结果
    return try await [detail1, detail2, detail3]
}

🔍 为什么用async let
它能让多个异步操作并行执行,而不是一个接一个地等待。总耗时≈最长的那个任务(0.5秒),而不是三个相加(1.5秒)。

步骤4:在main中运行整个流程

@main
enum BookCrawler {
    static func main() async throws {
        print("开始获取书籍列表...")
        let books = try await fetchBooks()
        print("获取到 \(books.count) 本书")
        
        print("开始并发获取详情...")
        let details = try await fetchAllBookDetails()
        print("获取完成!共 \(details.count) 条详情")
        
        // 打印结果
        for (index, book) in books.enumerated() {
            print("📚 \(book.title) by \(book.author)")
            print("   → \(details[index])\n")
        }
    }
}

运行后,你会看到类似输出:

开始获取书籍列表...
获取到 3 本书
开始并发获取详情...
获取完成!共 3 条详情
📚 《Swift并发指南》 by 张三
   → 这是第1本书的详细介绍...

📚 《iOS性能优化》 by 李四
   → 这是第2本书的详细介绍...

📚 《现代Swift实践》 by 王五
   → 这是第3本书的详细介绍...

五、新手常见问题解答

Q1:为什么我的await报错“'async' call in a function that does not support concurrency”?

原因:你试图在非async上下文中使用await

解决:把代码包在Task { }里,或者把当前函数声明为async

// ❌ 错误
let data = await fetchData()

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

Q2:如何在UI中使用async/await?

UIViewController中,你可以这样更新界面:

class BookViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        Task {
            do {
                let books = try await fetchBooks()
                // 注意:回到主线程更新UI
                await MainActor.run {
                    self.updateUI(with: books)
                }
            } catch {
                print("加载失败:", error)
            }
        }
    }
    
    func updateUI(with books: [Book]) {
        // 更新tableView等
    }
}

🔒 安全意识重点
UI操作必须在主线程执行!使用MainActor.run确保线程安全。

Q3:async/await能替代GCD吗?

大部分场景可以。但对于底层线程控制、队列优先级等高级需求,GCD仍有优势。建议:

  • 新项目优先用async/await
  • 老项目逐步迁移
  • 复杂并发逻辑可结合TaskGroupActor等新特性

六、学习建议与下一步

推荐学习路径

  1. 巩固基础:先掌握async/awaitTask
  2. 进阶并发:学习TaskGroup(并发循环)、AsyncStream(异步序列)
  3. 线程安全:理解Actor模型,避免数据竞争
  4. 实战项目:在真实App中替换旧的回调写法

推荐资源

类型 名称 说明
书籍 《Modern Swift Concurrency》 英文原版,深度讲解
官方文档 Swift Concurrency Guide 必读
开源项目 Async Algorithms Apple官方库
教程 WWDC2021 Session 10132 “Meet async/await in Swift”

避坑指南

  • ❌ 不要在async函数中使用DispatchQueue.main.async来更新UI —— 用MainActor
  • ❌ 不要滥用Task { }嵌套 —— 尽量保持函数层级扁平
  • ✅ 所有异步函数都应考虑错误处理(throws + do-catch

结语

async/await是Swift现代化的重要一步,它让并发编程变得直观而安全。我当初花了两周才搞懂GCD的各种队列,而async/await半天就上手了——这就是技术进步的力量。

希望这篇教程能帮你迈出并发编程的第一步。记住:不要怕犯错,多写多试,你很快就能写出高效又安全的异步代码!

如果你觉得有帮助,欢迎在掘金关注我,我会持续更新更多零基础友好的iOS开发教程。下次见!

评论 0

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