適用於 Swift 開發者的 Flutter 並行處理
Dart 和 Swift 都支援並行程式設計。本指南旨在協助您了解 Dart 中的並行處理如何運作,以及它與 Swift 的比較。透過了解這些,您可以建立高效能的 iOS 應用程式。
在 Apple 生態系統中開發時,有些任務可能需要很長時間才能完成。這些任務包括擷取或處理大量資料。iOS 開發人員通常使用 Grand Central Dispatch (GCD) 來排程使用共享執行緒池的任務。透過 GCD,開發人員將任務新增至分派佇列,而 GCD 則決定在哪個執行緒上執行這些任務。
但是,GCD 會啟動執行緒來處理剩餘的工作項目。這表示您可能會產生大量的執行緒,而系統可能會過度負荷。使用 Swift,結構化並行模型減少了執行緒的數量和上下文切換。現在,每個核心都只有一個執行緒。
Dart 具有單執行緒執行模型,支援 Isolates
、事件迴圈和非同步程式碼。Isolate
是 Dart 對輕量級執行緒的實作。除非您產生 Isolate
,否則您的 Dart 程式碼會在事件迴圈驅動的主 UI 執行緒中執行。Flutter 的事件迴圈相當於 iOS 的主迴圈,換句話說,就是附加到主執行緒的 Looper。
Dart 的單執行緒模型並不表示您必須將所有內容都當作導致 UI 凍結的阻塞操作執行。相反地,請使用 Dart 語言提供的非同步功能,例如 async
/await
。
非同步程式設計
#非同步操作允許其他操作在其完成之前執行。Dart 和 Swift 都使用 async
和 await
關鍵字支援非同步函式。在這兩種情況下,async
都標記函式執行非同步工作,而 await
則告知系統等待函式的結果。這表示如果必要,Dart VM *可以*暫停該函式。如需非同步程式設計的詳細資訊,請查看Dart 中的並行處理。
利用主執行緒/隔離區
#對於 Apple 作業系統,主要 (也稱為主) 執行緒是應用程式開始執行的位置。使用者介面的渲染總是發生在主執行緒上。Swift 和 Dart 之間的一個差異是
Swift 可能會使用不同的執行緒來執行不同的任務,而且 Swift 不保證使用哪個執行緒。因此,當在 Swift 中分派 UI 更新時,您可能需要確保工作發生在主執行緒上。
假設您想要編寫一個非同步擷取天氣並顯示結果的函式。
在 GCD 中,若要手動將程序分派至主執行緒,您可以執行類似下列操作。
首先,定義 Weather
enum
enum Weather: String {
case rainy, sunny
}
接下來,定義檢視模型並將其標記為發布類型為 Weather?
的 result
的 @Observable
。使用 GCD 建立背景 DispatchQueue
以將工作傳送到執行緒池,然後分派回主執行緒以更新 result
。
@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
}
}
}
}
最後,顯示結果
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 {
// Mimic 1 second network delay.
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 中,所有工作預設都會在主隔離區上執行。若要在 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(
// 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 檔案。
利用背景執行緒/隔離區
#Flutter 應用程式可以在各種多核心硬體上執行,包括執行 macOS 和 iOS 的裝置。為了改善這些應用程式的效能,您有時必須同時在不同的核心上執行任務。這對於避免長時間執行的操作封鎖 UI 渲染尤其重要。
在 Swift 中,您可以利用 GCD 在具有不同服務品質類別 (qos) 屬性的全域佇列上執行任務。這表示任務的優先順序。
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 中,您可以將計算卸載到工作隔離區,通常稱為背景工作人員。常見的情況是產生一個簡單的工作隔離區,並在工作人員退出時在訊息中傳回結果。從 Dart 2.19 開始,您可以使用 Isolate.run()
來產生隔離區並執行計算
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
函式來啟動隔離區以執行回呼函式
final jsonData = await compute(getNumberOfKeys, jsonString);
在此情況下,回呼函式是頂層函式,如下所示
Map<String, dynamic> getNumberOfKeys(String jsonString) {
return jsonDecode(jsonString);
}
您可以在以 Swift 開發人員身分學習 Dart中找到關於 Dart 的更多資訊,以及在 適用於 SwiftUI 開發人員的 Flutter 或 適用於 UIKit 開發人員的 Flutter 中找到關於 Flutter 的更多資訊。
除非另有說明,否則本網站上的文件反映了 Flutter 的最新穩定版本。頁面最後更新於 2024 年 11 月 20 日。 檢視來源 或 回報問題。