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 都使用 asyncawait 關鍵字支援非同步函式。在這兩種情況下,async 標記一個函式執行非同步工作,await 告訴系統等待函式的結果。這意味著 Dart VM 可以 在必要時暫停函式。有關非同步程式設計的更多詳細資訊,請檢視 Dart 中的併發性

利用主執行緒/Isolate

#

對於 Apple 作業系統,主執行緒(也稱為主要執行緒)是應用程式開始執行的地方。使用者介面的渲染始終發生在主執行緒上。Swift 和 Dart 的一個區別是
Swift 可能會為不同的任務使用不同的執行緒,並且 Swift 不保證使用哪個執行緒。因此,在 Swift 中排程 UI 更新時,你可能需要確保工作發生在主執行緒上。

假設你想編寫一個非同步獲取天氣並顯示結果的函式。

在 GCD 中,要手動將程序排程到主執行緒,你可能會執行以下操作。

首先,定義 Weather enum

swift
enum Weather: String {
    case rainy, sunny
}

接下來,定義檢視模型並將其標記為 @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 引入了 actor 以支援共享可變狀態的同步。為了確保工作在主執行緒上執行,定義一個標記為 @MainActor 的檢視模型類,其中包含一個使用 Task 內部呼叫非同步函式的 load() 函式。

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 將檢視模型定義為狀態,其中包含一個可由檢視模型呼叫的 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 }

然後,定義一個簡單的檢視模型(類似於在 SwiftUI 中建立的),以獲取天氣。在 Dart 中,Future 物件表示將來提供的值。Future 類似於 Swift 的 @Observable。在此示例中,檢視模型中的函式返回一個 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
Widgets 用於在 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。一個常見的場景是生成一個簡單的工作隔離器,並在 worker 退出時在訊息中返回結果。從 Dart 2.19 開始,你可以使用 Isolate.run() 來生成一個隔離器並執行計算

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 函式來啟動一個隔離器以執行回撥函式

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

在這種情況下,回撥函式是如下所示的頂級函式

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

你可以在 Swift 開發者學習 Dart 中找到更多關於 Dart 的資訊,在 適用於 SwiftUI 開發者的 Flutter適用於 UIKit 開發者的 Flutter 中找到更多關於 Flutter 的資訊。