面向 Swift 开发人员的 Flutter 并发
Dart 和 Swift 都支持并发编程。本指南将帮助您了解 Dart 中的并发机制及其与 Swift 的比较。通过了解这些,您可以创建高性能的 iOS 应用。
在 Apple 生态系统中开发时,某些任务可能需要很长时间才能完成。这些任务包括获取或处理大量数据。iOS 开发人员通常使用 Grand Central Dispatch (GCD) 来使用共享线程池调度任务。使用 GCD,开发人员将任务添加到调度队列,GCD 决定在哪个线程上执行它们。
但是,GCD 会启动线程来处理剩余的工作项。这意味着最终可能会产生大量线程,系统可能会过度占用。在 Swift 中,结构化并发模型减少了线程数量和上下文切换次数。现在,每个核心只有一个线程。
Dart 采用单线程执行模型,支持Isolate
、事件循环和异步代码。Isolate
是 Dart 对轻量级线程的实现。除非您启动 Isolate
,否则您的 Dart 代码将在由事件循环驱动的主 UI 线程中运行。Flutter 的事件循环等同于 iOS 主循环——换句话说,就是附加到主线程的 Looper。
Dart 的单线程模型并不意味着您必须将所有内容都作为阻塞操作运行,从而导致 UI 冻结。相反,请使用 Dart 语言提供的异步特性,例如async
/await
。
异步编程
#异步操作允许其他操作在其完成之前执行。Dart 和 Swift 都使用async
和await
关键字支持异步函数。在这两种情况下,async
都表示函数执行异步工作,await
告诉系统等待函数的结果。这意味着 Dart VM 可以 根据需要挂起函数。有关异步编程的更多详细信息,请查看Dart 中的并发。
利用主线程/Isolate
#对于 Apple 操作系统,主线程(也称为主线程)是应用程序开始运行的地方。渲染用户界面始终在主线程上进行。Swift 和 Dart 之间的一个区别是 Swift 可能会为不同的任务使用不同的线程,并且 Swift 不保证使用哪个线程。因此,在 Swift 中调度 UI 更新时,您可能需要确保工作在主线程上进行。
假设您想编写一个异步获取天气并显示结果的函数。
在 GCD 中,要手动将进程调度到主线程,您可以执行以下操作。
首先,定义 Weather
enum
:
enum Weather: String {
case rainy, sunny
}
接下来,定义视图模型并将其标记为 @Observable
,该视图模型发布类型为 Weather?
的 result
。使用 GCD 创建后台 DispatchQueue
将工作发送到线程池,然后调度回主线程以更新 result
。
@Observable class ContentViewModel {
private(set) var result: Weather?
private let queue = DispatchQueue(label: "weather_io_queue")
func load() {
// 模拟 1 秒网络延迟。
queue.asyncAfter(deadline: .now() + 1) { [weak self] in
DispatchQueue.main.async {
self?.result = .sunny
}
}
}
}
最后,显示结果:
struct ContentView: View {
@State var viewModel = ContentViewModel()
var body: some View {
Text(viewModel.result?.rawValue ?? "Loading...")
.onAppear {
viewModel.load()
}
}
}
最近,Swift 引入了 actor 来支持共享可变状态的同步。为了确保在主线程上执行工作,请定义一个标记为 @MainActor
的视图模型类,并使用 Task
在内部调用异步函数的 load()
函数。
@MainActor @Observable class ContentViewModel {
private(set) var result: Weather?
func load() async {
// 模拟 1 秒网络延迟。
try? await Task.sleep(nanoseconds: 1_000_000_000)
self.result = .sunny
}
}
接下来,使用 @State
将视图模型定义为状态,并使用视图模型可以调用的 load()
函数:
struct ContentView: View {
@State var viewModel = ContentViewModel()
var body: some View {
Text(viewModel.result?.rawValue ?? "Loading...")
.task {
await viewModel.load()
}
}
}
在 Dart 中,默认情况下,所有工作都在主 Isolate 上运行。要在 Dart 中实现相同的示例,首先创建 Weather
enum
:
enum Weather {
rainy,
windy,
sunny,
}
然后,定义一个简单的视图模型(类似于在 SwiftUI 中创建的视图模型)来获取天气。在 Dart 中,Future
对象表示将来要提供的值。Future
类似于 Swift 的 @Observable
。在这个示例中,视图模型中的一个函数返回一个 Future<Weather>
对象:
@immutable
class HomePageViewModel {
const HomePageViewModel();
Future<Weather> load() async {
await Future.delayed(const Duration(seconds: 1));
return Weather.sunny;
}
}
此示例中的 load()
函数与 Swift 代码类似。Dart 函数标记为 async
,因为它使用了 await
关键字。
此外,标记为 async
的 Dart 函数会自动返回一个 Future
。换句话说,您不必在标记为 async
的函数内部手动创建一个 Future
实例。
对于最后一步,显示天气值。在 Flutter 中,FutureBuilder
和 StreamBuilder
小部件用于在 UI 中显示 Future 的结果。以下示例使用 FutureBuilder
:
class HomePage extends StatelessWidget {
const HomePage({super.key});
final HomePageViewModel viewModel = const HomePageViewModel();
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
// 将 FutureBuilder 馈送到您的 widget 树。
child: FutureBuilder<Weather>(
// 指定要跟踪的 Future。
future: viewModel.load(),
builder: (context, snapshot) {
// 快照的类型为 `AsyncSnapshot`,并包含 Future 的状态。通过查看快照是否包含错误或数据是否为 null,您可以决定向用户显示什么内容。
if (snapshot.hasData) {
return Center(
child: Text(
snapshot.data.toString(),
),
);
} else {
return const Center(
child: CupertinoActivityIndicator(),
);
}
},
),
);
}
}
有关完整示例,请查看 GitHub 上的 async_weather 文件。
利用后台线程/Isolate
#Flutter 应用可以在各种多核硬件上运行,包括运行 macOS 和 iOS 的设备。为了提高这些应用程序的性能,有时您必须在不同的核心上并发运行任务。这对于避免使用长时间运行的操作阻塞 UI 渲染尤其重要。
在 Swift 中,您可以利用 GCD 在具有不同服务质量类 (qos) 属性的全局队列上运行任务。这表示任务的优先级。
func parse(string: String, completion: @escaping ([String:Any]) -> Void) {
// 模拟 1 秒延迟。
DispatchQueue(label: "data_processing_queue", qos: .userInitiated)
.asyncAfter(deadline: .now() + 1) {
let result: [String:Any] = ["foo": 123]
completion(result)
}
}
}
在 Dart 中,您可以将计算卸载到工作 Isolate,通常称为后台工作程序。一种常见的场景是生成一个简单的 worker Isolate,并在 worker 退出时在消息中返回结果。从 Dart 2.19 开始,您可以使用 Isolate.run()
来生成 Isolate 并运行计算:
void main() async {
// 读取一些数据。
final jsonData = await Isolate.run(() => jsonDecode(jsonString) as Map<String, dynamic>);`
// 使用该数据。
print('JSON 密钥数:${jsonData.length}');
}
在 Flutter 中,您还可以使用 compute
函数来启动 Isolate 以运行回调函数:
final jsonData = await compute(getNumberOfKeys, jsonString);
在这种情况下,回调函数是一个顶级函数,如下所示:
Map<String, dynamic> getNumberOfKeys(String jsonString) {
return jsonDecode(jsonString);
}
您可以在将 Swift 开发人员学习 Dart中找到有关 Dart 的更多信息,以及在面向 SwiftUI 开发人员的 Flutter (/get-started/flutter-for/swiftui-devs) 或 面向 UIKit 开发人员的 Flutter (/get-started/flutter-for/uikit-devs) 中找到有关 Flutter 的更多信息。
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。