Swift异步编程简介
\n\n现代移动应用几乎离不开网络请求、数据库操作或大文件处理。所有这些操作都需要时间,如果在主线程中执行它们,应用界面会"卡住",给用户带来糟糕的体验。这时,异步编程就派上了用场。
\n\n在Swift 5.5出现之前,开发者使用闭包(completion handlers)或第三方库(Combine、RxSwift),这常常导致"回调地狱"和难以阅读的代码。随着新的Swift Concurrency模型(async/await)的推出,编写异步代码变得和同步代码一样简单和线性。
\n\n在本指南中,我们将通过实际示例剖析Swift异步编程的关键概念。您将学习如何使用async/await、Task、Actor和其他工具来创建快速且响应迅速的iOS和macOS应用。
Swift Concurrency的核心概念
\n\n1. Async和Await:异步的基础
\n\n关键字async和await是新模型的核心。async将函数声明为异步函数,而await会暂停执行直到获得结果,同时不会阻塞线程。
来看一个简单的例子:从网络加载数据。
\n\nimport Foundation\n\n// 声明一个异步函数\nfunc fetchUserData() async throws -> String {\n let url = URL(string: "https://api.example.com/user/1")!\n let (data, _) = try await URLSession.shared.data(from: url)\n return String(data: data, encoding: .utf8) ?? "错误"\n}\n\n// 调用异步函数\nTask {\n do {\n let userData = try await fetchUserData()\n print("用户数据: \\(userData)")\n } catch {\n print("加载错误: \\(error.localizedDescription)")\n }\n}\n\n请注意:try await表示我们正在等待一个可能抛出错误的结果。代码看起来像普通的同步代码,但不会阻塞主线程。
2. Task:进入异步世界的入口点
\n\n异步函数只能在另一个异步上下文中调用。要从同步代码(例如从viewDidLoad())启动异步操作,需要使用Task。
class ViewController: UIViewController {\n \n override func viewDidLoad() {\n super.viewDidLoad()\n \n // 启动一个异步任务\n Task {\n let data = await loadData()\n updateUI(with: data)\n }\n }\n \n func loadData() async -> String {\n // 模拟长时间加载\n try? await Task.sleep(nanoseconds: 2_000_000_000)\n return "已加载的数据"\n }\n \n func updateUI(with data: String) {\n print("更新UI: \\(data)")\n }\n}\n\n重要提示:Task会创建一个在后台线程中执行的新异步任务。要更新UI,必须返回主线程——默认情况下await会自动完成此操作。
3. Actor:防止数据竞争
\n\n在多线程编程中,经常会出现数据竞争(data race)问题,即多个线程同时修改同一个变量。Swift提供了一个优雅的解决方案——Actor。
Actor是一种引用类型,它保证在任何时刻只有一个线程可以访问其可变状态。
actor Counter {\n private var value = 0\n \n func increment() {\n value += 1\n }\n \n func getValue() -> Int {\n return value\n }\n}\n\n// 使用\nlet counter = Counter