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

架构图画师
2025-12-15 12:28
阅读 298

大家好,我是工作5年的后端开发工程师,也带过不少刚入行的新人。我当初学Swift并发的时候,被GCD(Grand Central Dispatch)绕得晕头转向,直到async/await出现,才真正体会到“把复杂变简单”的编程之美。今天我就用最直白的语言,带你从零开始掌握Swift的现代并发模型。

为什么我要写这篇教程?
因为我发现很多初学者一听到“并发”就害怕,但其实async/await就是为了让异步代码像同步代码一样好读、好写。本文还会结合一个简单的网络爬虫场景,让你理解它在实际项目中的价值。


一、什么是Swift的async/await?

简单说:async/await是Swift 5.5引入的一种写异步代码的新方式。它的目标只有一个:让异步代码看起来像同步代码一样线性、直观

它能用来做什么?

  • 网络请求(比如调用API)
  • 文件读写
  • 数据库操作
  • 甚至写一个简单的爬虫!

虽然你说到了“SpringBoot”,但请注意:SpringBoot是Java生态的后端框架,和Swift无关。不过我们可以用Swift写一个爬虫去抓取某个用SpringBoot搭建的网站数据——这正是我们实战项目的思路!


二、环境准备

要使用async/await,你需要:

项目 要求
Xcode 13.0 或更高版本
iOS 模拟器/iOS真机 iOS 15.0+(部分功能在iOS 13+也可用,但建议15+)
Swift 版本 5.5+

✅ 检查方法:打开Xcode → Xcode > About Xcode 查看版本。

如果你还没安装Xcode,去Mac App Store搜索“Xcode”免费下载即可。


三、核心概念:用大白话讲清楚

1. async 是什么?

标记一个函数为“异步函数”。意思是:这个函数可能会“暂停”执行,等事情做完再继续

func fetchData() async -> String {
    // 这里可能需要等待网络返回
    return "Hello, World!"
}

⚠️ 注意:只有async函数内部才能使用await

2. await 是什么?

表示“在这里等一下,等这个异步操作完成”。但它不会阻塞整个程序,只是暂停当前任务。

let result = await fetchData()
print(result) // 输出: Hello, World!

3. 对比传统写法(GCD)

写法 代码示例 问题
GCD(旧) DispatchQueue.global().async { ... } 回调地狱、难以处理错误、逻辑分散
async/await(新) let data = await fetch() 代码线性、错误处理清晰、易读易维护

我当初第一次看到await时,简直不敢相信:原来异步可以这么写!


四、实战项目:用Swift写一个简易爬虫

我们要做的很简单:从一个公开的API(假设它由SpringBoot提供)获取JSON数据,并解析出标题列表

🌰 场景假设:某公司用SpringBoot搭建了一个新闻API,地址是 https://news-api.example.com/articles,返回如下格式:

{ "articles": [ { "title": "Swift并发真香" }, { "title": "SpringBoot部署指南" } ] }

步骤1:创建异步网络请求函数

import Foundation

// 定义数据模型
struct Article: Codable {
    let title: String
}

struct NewsResponse: Codable {
    let articles: [Article]
}

// 异步函数:获取新闻
func fetchNews() async throws -> [Article] {
    let urlString = "https://jsonplaceholder.typicode.com/posts" // 使用公开测试API
    guard let url = URL(string: urlString) else {
        throw URLError(.badURL)
    }
    
    let (data, _) = try await URLSession.shared.data(from: url)
    let response = try JSONDecoder().decode([Post].self, from: data) // 注意:这里用Post代替Article
    
    // 为了简化,我们直接返回标题作为“文章”
    return response.map { Article(title: $0.title) }
}

// 适配测试API的结构
struct Post: Codable {
    let title: String
    let body: String
}

💡 提示:我们用 jsonplaceholder.typicode.com 代替假设的SpringBoot API,因为它免费、稳定、返回JSON。

步骤2:在App中调用异步函数

ContentView.swift(如果你用SwiftUI)或 ViewController.swift 中:

import SwiftUI

struct ContentView: View {
    @State private var titles: [String] = []
    @State private var isLoading = false

    var body: some View {
        NavigationView {
            List(titles, id: \.self) { title in
                Text(title)
            }
            .navigationTitle("新闻爬虫")
            .onAppear {
                Task {
                    await loadNews()
                }
            }
            .overlay(
                Group {
                    if isLoading {
                        ProgressView("正在爬取...")
                    }
                }
            )
        }
    }

    func loadNews() async {
        isLoading = true
        do {
            let articles = try await fetchNews()
            await MainActor.run {
                self.titles = articles.map { $0.title }
                self.isLoading = false
            }
        } catch {
            print("加载失败: \(error)")
            await MainActor.run {
                self.isLoading = false
            }
        }
    }
}

关键点解析:

  1. Task { }:在非异步上下文中启动异步任务。
  2. MainActor.run { }:UI更新必须在主线程,这是安全做法。
  3. try await:因为fetchNews可能抛出错误,必须用try

🔍 我当初踩过的坑:忘记用MainActor.run更新UI,结果App崩溃!记住:所有UI操作必须在主线程


五、常见问题解答(新手必看)

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

原因:你在一个普通函数(非async)里用了await

解决:要么把外层函数改成async,要么用Task { }包裹。

// 错误 ❌
func buttonTapped() {
    let data = await fetchData() // 编译错误!
}

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

Q2:如何同时发起多个请求(比如并发爬多个页面)?

async letwithTaskGroup

async let news1 = fetchNews()
async let news2 = fetchNews()

let allArticles = try await [news1, news2].flatMap { $0 }

这样两个请求会并行执行,而不是串行等待。

Q3:async/await 和 GCD 能混用吗?

可以,但不推荐。新代码尽量用async/await,旧代码可逐步迁移。


六、学习建议与避坑指南

下一步学什么?

主题 推荐理由
TaskTaskGroup 掌握更复杂的并发控制
Actor 解决数据竞争问题
Continuation 将回调式API转为async/await
Combine + async/await 混合使用 在大型项目中很常见

避坑指南(血泪经验):

  1. 不要在async函数里做耗时计算:会阻塞任务。用Task.detachedconcurrent队列。
  2. 错误处理别偷懒try?会吞掉错误,调试时很难定位问题。
  3. 模拟器网络权限:记得在Info.plist添加网络权限(iOS 9+默认允许HTTP,但生产环境用HTTPS)。

最后一句忠告:

并发不是魔法,理解“任务何时暂停、何时恢复”才是关键。多写、多调试,你会爱上async/await的简洁。


结语

今天我们从零开始,用async/await实现了一个简易爬虫。虽然没真的抓SpringBoot网站(因为需要后端配合),但你已经掌握了如何用Swift优雅地处理异步网络请求

记住:技术不是为了炫技,而是为了解决问题async/await就是Swift送给开发者的一份礼物——让异步编程不再痛苦。

动手试试吧!遇到问题欢迎留言讨论。下次我们聊聊如何用Actor保护共享状态,再见!

评论 0

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