Swift并发编程不再难:用async/await搞定异步任务

码上见山
2026-01-14 01:20
阅读 3171

大家好,我是你们的老朋友,一个在大厂搬砖三年、业余时间在B站分享技术的iOS开发者。最近在整理简历时,我发现“熟悉Swift并发模型”几乎成了中高级iOS岗位的标配要求。而很多刚入门的同学一看到 async/await 就头大——这不就是我当初学的时候的状态吗?今天,我就带大家从零开始,用一个真实的小项目,彻底搞懂Swift的并发编程。

别担心,即使你连“异步”是啥都不知道,也能跟上。我们还会巧妙地把区块链、前端这些热门词融入进来(别急,后面会解释),让你的简历更有亮点!


为什么你需要学会 async/await?

在iOS开发中,我们经常要处理耗时操作:比如从网络下载数据、读取大文件、或者调用区块链节点API。如果直接在主线程做这些事,App就会卡住——用户点按钮没反应,滑动列表卡成PPT。这就是“阻塞主线程”。

传统的解决方案是用回调(Callback)或GCD(Grand Central Dispatch),但代码容易变得又深又乱,俗称“回调地狱”。Swift 5.5 引入了 async/await,让异步代码写起来像同步代码一样清晰!

我当初学的时候,第一次看到 async/await 的示例,简直不敢相信:原来异步可以这么优雅?


环境准备:三分钟搭好开发环境

要跑本文的代码,你需要:

  1. macOS(任何较新版本即可)
  2. Xcode 13.0 或更高版本(因为 async/await 是 Swift 5.5 引入的)
  3. 一个Apple ID(用于真机调试,可选)

验证你的环境

打开 Xcode → Create a new project → 选择 iOS App → 语言选 Swift → 运行到模拟器。

在 ContentView.swift(如果你用 SwiftUI)或 ViewController.swift 中粘贴以下代码:

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, async/await!")
            .padding()
    }
}

如果能正常编译运行,恭喜你,环境OK!


核心概念:用“点外卖”理解 async/await

想象你去餐厅点了一份牛排(耗时操作)。传统方式是:

  • 你站在厨房门口等(同步)→ 其他人没法点餐,服务瘫痪。
  • 或者你留下电话,厨师做好了打电话给你(回调)→ 但你可能同时点了三份菜,电话一响你都懵了:“哪份好了?”

而 async/await 就像:

  • 你点完单就去玩手机(不阻塞),等服务员喊“您的牛排好了!”(await),你再回来吃。

关键术语解释

术语 通俗解释
async 标记一个函数是“异步”的,它可能会暂停执行去等别的事
await 在调用 async 函数时使用,表示“我愿意等它完成”
Task 一个独立的并发工作单元,类似线程但更轻量
主线程 负责UI更新的线程,绝对不能被阻塞!

避坑指南:永远不要在主线程做耗时操作!await 后的代码可能回到主线程,所以后续处理要小心。


实战项目:构建一个“区块链行情查看器”

为了让你的简历更有竞争力,我们做一个结合区块链和前端展示的小App:从公开API获取比特币价格,并用SwiftUI展示。

为什么选区块链?因为它的API调用典型、数据结构简单,且是简历上的热门关键词。前端?没错,SwiftUI就是Apple生态的“前端”!

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

新建一个文件 BlockchainService.swift:

import Foundation

class BlockchainService {
    // 1. 声明为 async 函数
    func fetchBitcoinPrice() async throws -> Double {
        let url = URL(string: "https://api.coindesk.com/v1/bpi/currentprice/USD.json")!
        
        // 2. 使用 URLSession 的 async 版本
        let (data, _) = try await URLSession.shared.data(from: url)
        
        // 3. 解析 JSON
        let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
        let bpi = json?["bpi"] as? [String: Any]
        let usd = bpi?["USD"] as? [String: Any]
        let rate = usd?["rate_float"] as? Double
        
        guard let price = rate else {
            throw NSError(domain: "ParsingError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to parse price"])
        }
        
        return price
    }
}

关键点:

  • URLSession.shared.data(from:) 现在有 async 版本,不用再写 completion handler!
  • 所有 await 的地方都可能“暂停”,但不会卡住UI。

第二步:在UI中安全地调用异步函数

回到 ContentView.swift,修改如下:

import SwiftUI

struct ContentView: View {
    @State private var bitcoinPrice: String = "Loading..."
    @State private var errorMessage: String = ""
    
    private let service = BlockchainService()
    
    var body: some View {
        VStack(spacing: 20) {
            Text("比特币实时价格")
                .font(.title)
            
            Text(bitcoinPrice)
                .font(.system(size: 24, weight: .bold))
                .foregroundColor(.green)
            
            if !errorMessage.isEmpty {
                Text("错误: \(errorMessage)")
                    .foregroundColor(.red)
            }
            
            Button("刷新价格") {
                // 4. 在 Task 中调用 async 函数
                Task {
                    await loadPrice()
                }
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)
        }
        .padding()
        .onAppear {
            Task {
                await loadPrice()
            }
        }
    }
    
    // 5. 封装加载逻辑
    private func loadPrice() async {
        do {
            let price = try await service.fetchBitcoinPrice()
            // 6. 回到主线程更新UI
            await MainActor.run {
                bitcoinPrice = "$\(String(format: "%.2f", price))"
                errorMessage = ""
            }
        } catch {
            await MainActor.run {
                errorMessage = error.localizedDescription
                bitcoinPrice = "N/A"
            }
        }
    }
}

逐行解析:

  1. @State 变量用于驱动UI更新
  2. Task { ... } 创建一个并发任务,在其中可以 await
  3. onAppear 里自动加载一次数据
  4. 重要:更新UI必须在主线程!用 MainActor.run { ... } 确保安全

我当初学的时候,就忘了 MainActor.run,结果App偶尔崩溃,调试了好久才发现是线程问题!

第三步:优化体验——添加加载状态

用户点击“刷新”时,应该有个loading提示:

// 在 ContentView 中新增状态
@State private var isLoading = false

// 修改 loadPrice 函数
private func loadPrice() async {
    await MainActor.run {
        isLoading = true
        errorMessage = ""
    }
    
    do {
        let price = try await service.fetchBitcoinPrice()
        await MainActor.run {
            bitcoinPrice = "$\(String(format: "%.2f", price))"
            isLoading = false
        }
    } catch {
        await MainActor.run {
            errorMessage = error.localizedDescription
            bitcoinPrice = "N/A"
            isLoading = false
        }
    }
}

// 在 Button 上加 disabled 状态
Button("刷新价格") {
    Task {
        await loadPrice()
    }
}
.disabled(isLoading) // 加载时禁用按钮

现在,你的App既有区块链数据,又有流畅的前端交互,简历上可以写“熟练使用Swift并发模型实现区块链数据可视化”啦!


新手常见问题解答

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

原因:你可能在不支持并发的上下文中调用了 await,比如普通函数里。

解决:确保调用 await 的地方在一个 async 函数内,或者包裹在 Task { } 中。

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

用 async let 或 TaskGroup:

async let price1 = service.fetchBitcoinPrice()
async let price2 = service.fetchEthereumPrice()

let btc = try await price1
let eth = try await price2

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

Q3:async/await 和 GCD 有什么区别?

特性 GCD async/await
语法 回调嵌套,易混乱 线性代码,易读
错误处理 需手动传递Error 原生支持throw/try/catch
取消任务 复杂 内置Task取消机制
学习曲线 较陡 平缓

建议:新项目优先用 async/await,老代码可逐步迁移。

Q4:能在非Apple平台用 async/await 吗?

Swift的并发模型是语言级特性,理论上跨平台可用。但目前主要在Apple生态成熟(iOS 15+、macOS 12+)。低版本系统需用 backport 库(如 swift-concurrency-backport)。


下一步学习建议

恭喜你已经掌握了Swift并发的基础!接下来可以:

  1. 深入学习:了解 TaskGroup、AsyncStream、Actor 等高级特性
  2. 项目实战:在你的个人项目中替换掉旧的GCD代码
  3. 面试准备:在简历中加入“使用Swift并发优化App性能,减少30%主线程阻塞”等量化描述
  4. 扩展领域:尝试用Swift调用更多区块链API(如以太坊、Solana),打造全栈能力

最后的小建议:技术日新月异,但核心思想不变——写出清晰、安全、高效的代码。async/await 不仅是语法糖,更是思维升级。当你能把复杂异步逻辑写得像散文一样流畅,你就离大厂Offer不远了!

如果你喜欢这种“项目驱动+避坑指南”的教学风格,欢迎来B站关注我,我会持续更新更多iOS实战教程。下期见!

评论 0

最热最新
暂无评论
码上见山Lv.1
0
影响力
0
文章
0
粉丝