跳转至正文

面向 Swift 开发者的 Flutter 并发

在学习 Flutter 和 Dart 时发挥你的 Swift 并发知识。

Dart 和 Swift 都支持并发编程。本指南帮助你理解 Dart 中的并发机制及其与 Swift 的对比。掌握这些后,你可以构建高性能 iOS 应用。

在 Apple 生态中开发时,某些任务可能耗时较长,例如获取或处理大量数据。iOS 开发者通常使用 Grand Central Dispatch(GCD)通过共享线程池调度任务:将任务加入 dispatch 队列,由 GCD 决定在哪条线程执行。

但 GCD 会创建线程处理剩余工作项,可能导致线程过多、系统过载。Swift 的结构化并发模型减少了线程数和上下文切换,现在每个核心只有一条线程。

Dart 采用单线程执行模型,支持 Isolate、事件循环和异步代码。Isolate 是 Dart 对轻量线程的实现。除非你 spawn 一个 Isolate,否则 Dart 代码在由事件循环驱动的主 UI 线程中运行。Flutter 的事件循环相当于 iOS 主循环,即附加在主线程上的 Looper。

Dart 的单线程模型并不意味着你必须把所有操作都作为阻塞操作导致 UI 冻结,而应使用 Dart 提供的异步特性,例如 async/await

异步编程

#

异步操作允许其他操作在其完成前执行。Dart 和 Swift 都使用 asyncawait 关键字支持异步函数:async 标记函数执行异步工作,await 告诉系统等待函数返回结果,这意味着 Dart VM 可能 在必要时挂起该函数。有关异步编程的更多细节,请参阅 Concurrency in Dart(Dart 中的并发)。

利用主线程 / 主 isolate

#

在 Apple 操作系统上,主线程是应用开始运行的地方,用户界面渲染始终在主线程进行。Swift 与 Dart 的一个区别是 Swift 可能对不同任务使用不同线程,且不保证使用哪条线程,因此在 Swift 中调度 UI 更新时可能需要确保工作发生在主线程。

假设你要编写一个异步获取天气并显示结果的函数。

在 GCD 中,若要手动将进程派发到主线程,可以这样做。

首先定义 Weather enum

swift
enum Weather: String {
    case rainy, sunny
}

接下来定义 view model,标记为 @Observable,发布类型为 Weather?result。使用 GCD 创建后台 DispatchQueue 将工作发送到线程池,再派回主线程更新 result

swift
@Observable class ContentViewModel {
    private(set) var result: Weather?

    private let queue = DispatchQueue(label: "weather_io_queue")
    func load() {
        // Mimic 1 second network delay.
        queue.asyncAfter(deadline: .now() + 1) { [weak self] in
            DispatchQueue.main.async {
                self?.result = .sunny
            }
        }
    }
}

最后显示结果:

swift
struct ContentView: View {
    @State var viewModel = ContentViewModel()
    var body: some View {
        Text(viewModel.result?.rawValue ?? "Loading...")
            .onAppear {
                viewModel.load()
        }
    }
}

近年来 Swift 引入 actors 以支持共享可变状态的同步。要确保工作在主线程执行,可定义标记为 @MainActor 的 view model 类,其 load() 内部使用 Task 调用异步函数。

swift
@MainActor @Observable class ContentViewModel {
  private(set) var result: Weather?

  func load() async {
    // Mimic 1 second network delay.
    try? await Task.sleep(nanoseconds: 1_000_000_000)
    self.result = .sunny
  }
}

接下来使用 @State 定义 view model,由视图调用 load()

swift
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

dart
enum Weather { rainy, windy, sunny }

然后定义简单的 view model(类似 SwiftUI 中的做法)以获取天气。在 Dart 中,Future 对象表示将来提供的值,与 Swift 的 @Observable 类似。本例中 view model 内的函数返回 Future<Weather>

dart
@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 中,FutureBuilderStreamBuilder widget 用于在 UI 中显示 Future 的结果。以下示例使用 FutureBuilder

dart
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  final HomePageViewModel viewModel = const HomePageViewModel();

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      // Feed a FutureBuilder to your widget tree.
      child: FutureBuilder<Weather>(
        // Specify the Future that you want to track.
        future: viewModel.load(),
        builder: (context, snapshot) {
          // A snapshot is of type `AsyncSnapshot` and contains the
          // state of the Future. By looking if the snapshot contains
          // an error or if the data is null, you can decide what to
          // show to the user.
          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)的全局队列上运行任务,以表示任务优先级。

swift
func parse(string: String, completion: @escaping ([String:Any]) -> Void) {
  // Mimic 1 sec delay.
  DispatchQueue(label: "data_processing_queue", qos: .userInitiated)
    .asyncAfter(deadline: .now() + 1) {
      let result: [String:Any] = ["foo": 123]
      completion(result)
    }
  }
}

在 Dart 中,可将计算卸载到 worker isolate(常称为后台 worker)。常见场景是 spawn 一个简单的 worker isolate,在 worker 退出时通过消息返回结果。可使用 Isolate.run() spawn isolate 并运行计算:

dart
void main() async {
  // Read some data.
  final jsonData = await Isolate.run(() => jsonDecode(jsonString) as Map<String, dynamic>);`

  // Use that data.
  print('Number of JSON keys: ${jsonData.length}');
}

在 Flutter 中,也可使用 compute 函数启动 isolate 运行回调:

dart
final jsonData = await compute(getNumberOfKeys, jsonString);

此时回调为如下所示的顶层函数:

dart
Map<String, dynamic> getNumberOfKeys(String jsonString) {
 return jsonDecode(jsonString);
}

有关 Dart 的更多信息请参阅 Learning Dart as a Swift developer,有关 Flutter 请参阅 Flutter for SwiftUI developersFlutter for UIKit developers