跳到主要內容

Swift 開發者學習 Dart

本指南旨在利用您的 Swift 程式設計知識來學習 Dart。它展示了兩種語言的主要相似點和不同點,並介紹了 Swift 中不存在的 Dart 概念。作為 Swift 開發者,Dart 可能會讓您感到熟悉,因為這兩種語言共享許多概念。

Swift 和 Dart 都支援健全空安全。這兩種語言預設都不允許變數為空。

與 Swift 類似,Dart 對集合泛型併發(使用 async/await)和擴充套件都有類似的支援。

混入(Mixins)是 Dart 中另一個對 Swift 開發者來說可能比較新的概念。與 Swift 類似,Dart 提供 AOT(提前編譯)編譯。然而,Dart 也支援 JIT(即時編譯)模式,以輔助各種開發方面,例如增量重新編譯或除錯。欲瞭解更多資訊,請檢視 Dart 概覽

約定與 Lint

#

Swift 和 Dart 都具有 Lint 工具來強制執行標準約定。然而,Swift 有一個獨立的工具 SwiftLint,而 Dart 有官方的佈局約定幷包含一個 Linter,使合規性變得輕鬆。要為您的專案自定義 Lint 規則,請遵循自定義靜態分析的說明。(請注意,Dart 和 Flutter 的 IDE 外掛也提供此功能。)

Dart 還提供了一個程式碼格式化工具,在命令列或透過 IDE 執行 dart format 時,它可以自動格式化任何 Dart 專案。

有關 Dart 約定和 Lint 的更多資訊,請檢視高效 DartLinter 規則

變數

#

與 Swift 相比,Dart 中變數的宣告和初始化方式略有不同。變數宣告總是以變數型別、var 關鍵字或 final 關鍵字開頭。與 Swift 一樣,Dart 支援型別推斷,編譯器會根據賦給變數的值推斷型別

dart
// String-typed variable.
String name = 'Bob';

// Immutable String-typed variable.
final String name = 'Bob';

// This is the same as `String name = 'Bob';`
// since Dart infers the type to be String.
var name = 'Bob';

// And this is the same as `final String name = 'Bob';`.
final name = 'Bob';

每個 Dart 語句都以分號結尾,表示語句的結束。您可以在 Dart 中用顯式型別替換 var。然而,根據約定,當分析器可以隱式推斷型別時,建議使用 var

dart
// Declare a variable first:
String name; 
// Initialize the variable later:
name = 'bob';
// Declare and initialize a variable at once with inference:
var name = 'bob';

上述 Dart 程式碼的 Swift 等效寫法如下

swift
// Declare a variable first: 
var name: String
// Initialize the variable later
name = "bob"

// Declare and initialize a variable at once with inference:
var name = "bob"

在 Dart 中,當一個沒有顯式型別的變數在其聲明後被初始化時,其型別會被推斷為包羅永珍的 dynamic 型別。同樣,當無法自動推斷型別時,它會預設為 dynamic 型別,這會移除所有型別安全。因此,Dart Linter 會透過生成警告來阻止這種做法。如果您打算允許變數擁有任何型別,則首選將其賦值給 Object? 而不是 dynamic

有關更多資訊,請檢視 Dart 語言之旅中的變數部分

Final

#

Dart 中的 final 關鍵字表示變數只能設定一次。這與 Swift 中的 let 關鍵字類似。

在 Dart 和 Swift 中,您只能初始化 final 變數一次,無論是在宣告語句中還是在初始化列表中。任何第二次賦值的嘗試都會導致編譯時錯誤。以下兩個程式碼片段都是有效的,但隨後設定 name 會導致編譯錯誤。

dart
final String name;
if (b1) {
  name = 'John';
} else {
  name = 'Jane';
}
swift
let name: String
if (b1) {
  name = "John"
} else {
  name = "Jane"
}

Const

#

除了 final,Dart 還有 const 關鍵字。const 的一個好處是它在編譯時完全求值,並且在應用程式的生命週期內不能被修改。

dart
const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere

在類級別定義的 const 變數需要標記為 static const

dart
class StandardAtmosphere {
  static const bar = 1000000; // Unit of pressure (dynes/cm2)
  static const double atm = 1.01325 * bar; // Standard atmosphere
}

const 關鍵字不僅僅用於宣告常量變數;它還可以用於建立常量值

dart
var foo = const ['one', 'two', 'three'];
foo.add('four'); // Error: foo contains a constant value.
foo = ['apple', 'pear']; // This is allowed as foo itself isn't constant.
foo.add('orange'); // Allowed as foo no longer contains a constant value.

在上面的例子中,您不能更改 const 值(新增、更新或刪除給定列表中的元素),但您可以為 foo 分配一個新值。在 foo 被分配了一個新的(非常量)列表後,您可以新增、更新或刪除列表的內容。

您還可以將常量值賦給 final 欄位。您不能在常量上下文中使用 final 欄位,但可以使用常量。例如

dart
final foo1 = const [1, 2, 3];
const foo2 = [1, 2, 3]; // Equivalent to `const [1, 2, 3]`
const bar2 = foo2; // OK
const bar1 = foo1; // Compile-time error, `foo1` isn't constant

您還可以定義 const 建構函式,使這些類不可變(不變),並使建立這些類的例項作為編譯時常量成為可能。有關更多資訊,請檢視const 建構函式

內建型別

#

Dart 在平臺庫中包含多種型別,例如

  • 基本值型別,例如
    • 數字 (num, int, double)
    • 字串 (String)
    • 布林值 (bool)
    • 空值 (Null)
  • 集合
    • 列表/陣列 (List)
    • 集合 (Set)
    • 對映/字典 (Map)

有關更多資訊,請檢視 Dart 語言之旅中的內建型別

數字

#

Dart 定義了三種用於儲存數字的數值型別

num
一種通用的 64 位數字型別。
int
一個平臺相關的整數。在原生程式碼中,它是一個 64 位補碼整數。在 Web 端,它是一個非小數的 64 位浮點數。
double
一個 64 位浮點數。

與 Swift 不同,Dart 沒有無符號整數的特定型別。

所有這些型別也是 Dart API 中的類。intdouble 型別都共享 num 作為它們的父類

Object is the parent of num, which is the parent of int and double

由於數字值在技術上是類的例項,它們方便地公開了自己的實用函式。因此,例如,一個 int 可以如下轉換為 double

dart
int intVariable = 3;
double doubleVariable = intVariable.toDouble();

在 Swift 中,使用專門的初始化器可以實現相同的效果

swift
var intVariable: Int = 3
var doubleVariable: Double = Double(intVariable)

對於字面值,Dart 會自動將整數字面量轉換為 double 值。以下程式碼完全沒有問題

dart
double doubleValue = 3;

與 Swift 不同,在 Dart 中您可以使用相等 (==) 運算子將整數值與雙精度浮點數進行比較,如下所示

dart
int intVariable = 3;
double doubleVariable = 3.0;
print(intVariable == doubleVariable); // true

此程式碼列印 true。然而,在 Dart 中,Web 和原生平臺之間的底層數字實現是不同的。Dart 中的數字頁面詳細介紹了這些差異,並展示瞭如何編寫程式碼以使這些差異不影響結果。

字串

#

與 Swift 一樣,Dart 使用 String 型別表示一系列字元,儘管 Dart 不支援表示單個字元的 Character 型別。String 可以用單引號或雙引號定義,但是,推薦使用單引號

dart
String c = 'a'; // There isn't a specialized "Character" type
String s1 = 'This is a String';
String s2 = "This is also a String";
swift
let c: Character = "a"
let s1: String = "This is a String"
let s2: String = "This is also a String"

轉義特殊字元

#

Dart 中轉義特殊字元與 Swift(以及大多數其他語言)類似。要包含特殊字元,請使用反斜槓字元對其進行轉義。

以下程式碼顯示了一些示例

dart
final singleQuotes = 'I\'m learning Dart'; // I'm learning Dart
final doubleQuotes = "Escaping the \" character"; // Escaping the " character
final unicode = '\u{1F60E}'; // 😎,  Unicode scalar U+1F60E

請注意,也可以直接使用 4 位十六進位制值(例如,\u2665),但是,花括號也適用。有關處理 Unicode 字元的更多資訊,請檢視 Dart 語言之旅中的Runes 和字形簇

字串連線和多行宣告

#

在 Dart 和 Swift 中,您都可以轉義多行字串中的換行符,這使您的原始碼更易於閱讀,但仍然將 String 輸出為單行。Dart 有幾種定義多行字串的方法

  1. 使用隱式字串連線:任何相鄰的字串字面量都會自動連線,即使它們跨越多行

    dart
    final s1 = 'String '
      'concatenation'
      " even works over line breaks.";
  2. 使用多行字串字面量:當字串兩邊使用三個引號(單引號或雙引號)時,該字面量允許跨越多行

    dart
    final s2 = '''You can create
    multiline strings like this one.''';
    
    final s3 = """This is also a
    multiline string.""";
  3. Dart 還支援使用 + 運算子連線字串。這適用於字串字面量和字串變數

    dart
    final name = 'John';
    final greeting = 'Hello ' + name + '!';

字串插值

#

使用 ${} 語法將表示式插入字串字面量中。Dart 在此基礎上進行了擴充套件,允許在表示式為單個識別符號時省略花括號

dart
var food = 'bread';
var str = 'I eat $food'; // I eat bread
var str = 'I eat ${bakery.bestSeller}'; // I eat bread

在 Swift 中,您可以透過將變數或表示式用括號括起來並加上反斜槓字首來達到同樣的效果

swift
let s = "string interpolation"
let c = "Swift has \(s), which is very handy."

原始字串

#

與 Swift 一樣,您可以在 Dart 中定義原始字串。原始字串會忽略跳脫字元,幷包含字串中存在的任何特殊字元。在 Dart 中,您可以透過在字串字面量前加上字母 r 來實現這一點,如下例所示。

dart
// Include the \n characters.
final s1 = r'Includes the \n characters.';
// Also includes the \n characters.
final s2 = r"Also includes the \n characters.";

final s3 = r'''
  The \n characters are also included
  when using raw multiline strings.
  ''';
final s4 = r"""
  The \n characters are also included
  when using raw multiline strings.
  """;
swift
let s1 = #"Includes the \n characters."#
let s2 = #"""
  The \n characters are also included
  when using raw multiline strings.
  """#

相等性

#

與 Swift 一樣,Dart 的相等性運算子 (==) 比較兩個字串是否相等。如果兩個字串包含相同的程式碼單元序列,則它們相等。

dart
final s1 = 'String '
  'concatenation'
  " works even over line breaks.";
assert(s1 ==
  'String concatenation works even over '
  'line breaks.');

常用 API

#

Dart 為字串提供了幾種常用 API。例如,Dart 和 Swift 都允許您使用 isEmpty 檢查字串是否為空。還有其他便捷方法,例如 toUpperCasetoLowerCase。有關更多資訊,請檢視 Dart 語言之旅中的字串

布林值

#

布林值在 Dart (bool) 和 Swift (Bool) 中都表示一個二進位制值。

空安全

#

Dart 強制執行健全空安全。預設情況下,型別不允許空值,除非標記為可空。Dart 透過在型別末尾加上問號 (?) 來表示這一點。這類似於 Swift 的可選型別

空感知運算子

#

Dart 支援多種處理可空性的運算子。空合併運算子 (??) 和可選鏈運算子 (?.) 在 Dart 中可用,並且與 Swift 中的操作方式相同

dart
a = a ?? b;
swift
let str: String? = nil
let count = str?.count ?? 0

此外,Dart 還提供了空安全的級聯運算子 (?..)。當目標表達式解析為 null 時,此運算子會忽略任何操作。Dart 還提供了空賦值運算子 (??=),這是 Swift 沒有的。如果具有可空型別的變數的當前值為 null,此運算子會為該變數賦值。表示為 a ??= b;,它是以下內容的簡寫

dart
a = a ?? b;

// Assign b to a if a is null; otherwise, a stays the same
a ??= b;
swift
a = a ?? b

! 運算子(也稱為“強制解包”)

#

在安全地假設可空變數或表示式實際上是非空的情況下,可以告訴編譯器抑制任何編譯時錯誤。這是透過使用字尾 ! 運算子實現的,將其作為表示式的字尾放置。(不要將其與 Dart 的“非”運算子混淆,後者使用相同的符號)

dart
int? a = 5;

int b = a; // Not allowed.
int b = a!; // Allowed.

在執行時,如果 a 結果為空,則會發生執行時錯誤。

?. 運算子類似,當訪問物件的屬性或方法時,使用 ! 運算子

dart
myObject!.someProperty;
myObject!.someMethod();

如果在執行時 myObjectnull,則會發生執行時錯誤。

Late 欄位

#

late 關鍵字可以分配給類欄位,以指示它們在稍後初始化,同時保持不可空。這類似於 Swift 的“隱式解包可選型別”。這對於在變數初始化之前從未被觀察到的情況很有用,允許其稍後初始化。一個不可空的 late 欄位不能在稍後被賦值為 null。此外,當一個不可空的 late 欄位在初始化之前被觀察到時,會丟擲執行時錯誤,這是您在良好行為的應用程式中希望避免的情況。

dart
// Using null safety:
class Coffee {
  late String _temperature;

  void heat() { _temperature = 'hot'; }
  void chill() { _temperature = 'iced'; }

  String serve() => _temperature + ' coffee';
}

在這種情況下,_temperature 僅在呼叫 heat()chill() 之後才初始化。如果 serve() 在其他方法之前被呼叫,則會發生執行時異常。請注意,_temperature 永遠不能為 null

您還可以將 late 關鍵字與初始化器結合使用,以實現延遲初始化

dart
class Weather {
  late int _temperature = _readThermometer();
}

在這種情況下,_readThermometer() 僅在首次訪問該欄位時執行,而不是在初始化時執行。

Dart 的另一個優點是使用 late 關鍵字來延遲 final 變數的初始化。雖然在將 final 變數標記為 late 時不必立即初始化它,但它仍然只能初始化一次。第二次賦值會導致執行時錯誤。

dart
late final int a;
a = 1;
a = 2; // Throws a runtime exception because
       // "a" is already initialized.

函式

#

Swift 使用 main.swift 檔案作為應用程式的入口點。Dart 使用 main 函式作為應用程式的入口點。每個程式都必須有一個 main 函式才能執行。例如

dart
void main() {
  // main function is the entry point
  print("hello world");
}
swift
// main.swift file is the entry point
print("hello world")

Dart 不支援 Tuples(儘管 pub.dev 上有幾個 tuple 包可用)。如果一個函式需要返回多個值,您可以將它們包裝在一個集合中,例如列表、集合或對映,或者您可以編寫一個包裝類,返回一個包含這些值的例項。有關此內容的更多資訊,請參閱集合部分。

異常與錯誤處理

#

與 Swift 一樣,Dart 的函式和方法支援處理異常錯誤。Dart 錯誤通常表示程式設計師的錯誤或系統故障,如棧溢位。Dart 錯誤不應該被捕獲。另一方面,Dart 異常表示可恢復的故障,旨在被捕獲。例如,在執行時程式碼可能嘗試訪問流式資料來源,但反而收到一個異常,如果未捕獲,將導致應用程式終止。您可以透過將函式呼叫包裝在 try-catch 塊中來管理 Dart 中的異常。

dart
try {
  // Create audio player object
  audioPlayer = AVAudioPlayer(soundUrl);
            
  // Play the sound
  audioPlayer.play();
}
catch {
  // Couldn't create audio player object, log the exception
  print("Couldn't create the audio player for file $soundFilename");
}

類似地,Swift 使用 do-try-catch 塊。例如

swift
do {
  // Create audio player object
  audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
            
  // Play the sound
  audioPlayer?.play()
}
catch {
  // Couldn't create audio player object, log the error
  print("Couldn't create the audio player for file \(soundFilename)")
}

您可以在同步和非同步 Dart 程式碼中使用 try-catch 塊。有關更多資訊,請參閱 ErrorException 類的文件。

引數

#

與 Swift 類似,Dart 支援在其函式中使用命名引數。然而,與 Swift 不同的是,這些在 Dart 中並非預設。Dart 中預設的引數型別是位置引數。

dart
int multiply(int a, int b) {
  return a * b;
}

Swift 中,等效的做法是在引數前加上下劃線,以消除引數標籤的需要。

swift
func multiply(_ a: Int, _ b: Int) -> Int {
  return a * b
}

在 Dart 中建立命名引數時,將它們定義在單獨的花括號塊中,位於位置引數之後

dart
int multiply(int a, int b, {int c = 1, int d = 1}) {
  return a * b * c * d;
}

// Calling a function with both required and named parameters
multiply(3, 5); // 15
multiply(3, 5, c: 2); // 30
multiply(3, 5, d: 3); // 45
multiply(3, 5, c: 2, d: 3); // 90
swift
// The Swift equivalent
func multiply(_ a: Int, _ b: Int, c: Int = 1, d: Int = 1) -> Int {
  return a * b * c * d
}

命名引數必須包含以下之一

  • 預設值
  • 在型別末尾新增 ? 將型別設定為可空
  • 變數型別前的關鍵字 required

要了解更多關於可空型別的資訊,請檢視空安全

在 Dart 中將命名引數標記為必需,必須在其前面加上 required 關鍵字

dart
int multiply(int a, int b, { required int c }) {
  return a * b * c;
}
// When calling the function, c has to be provided
multiply(3, 5, c: 2);

第三種引數型別是可選位置引數。顧名思義,它們與預設的位置引數類似,但可以在呼叫函式時省略。它們必須列在任何必需的位置引數之後,並且不能與命名引數結合使用。

dart
int multiply(int a, int b, [int c = 1, int d = 1]) {
  return a * b * c * d;
}
// Calling a function with both required and optional positioned parameters.
multiply(3, 5); // 15
multiply(3, 5, 2); // 30
multiply(3, 5, 2, 3); // 90
swift
// The Swift equivalent
func multiply(_ a: Int, _ b: Int, _ c: Int = 1, _ d: Int = 1) -> Int {
  return a * b * c * d
}

與命名引數一樣,可選位置引數必須具有預設值或可空型別。

一等函式

#

與 Swift 一樣,Dart 函式也是一等公民,這意味著它們被視為任何其他物件。例如,以下程式碼展示瞭如何從一個函式返回一個函式

dart
typedef int MultiplierFunction(int value);
// Define a function that returns another function
MultiplierFunction multiplyBy(int multiplier) {
  return (int value) {
    return value * multiplier;
  };
}
// Call function that returns new function
MultiplierFunction multiplyByTwo = multiplyBy(2);
// Call the new function
print(multiplyByTwo(3)); // 6
swift
// The Swift equivalent of the Dart function below
// Define a function that returns a closure
typealias MultiplierFunction = (Int) -> (Int)

func multiplyBy(_ multiplier: Int) -> MultiplierFunction {
  return { $0 * multiplier} // Returns a closure
}

// Call function that returns a function
let multiplyByTwo = multiplyBy(2)
// Call the new function
print(multiplyByTwo(3)) // 6

匿名函式

#

Dart 中的匿名函式除了語法上的差異外,與 Swift 中的閉包幾乎完全相同。與命名函式一樣,您可以像傳遞任何其他值一樣傳遞匿名函式。例如,您可以將匿名函式儲存在變數中,將其作為引數傳遞給另一個函式,或者從另一個函式返回它們。

Dart 有兩種宣告匿名函式的方式。第一種,使用花括號,像任何其他函式一樣工作。它允許您使用多行,並且需要一個 return 語句來返回任何值。

dart
// Multi line anonymous function
[1,2,3].map((element) { 
  return element * 2; 
}).toList(); // [2, 4, 6]
swift
  // Swift equivalent anonymous function
  [1, 2, 3].map { $0 * 2 }

另一種方法是使用箭頭函式,其名稱來源於其語法中使用的箭頭狀符號。當您的函式體只包含單個表示式並返回該值時,可以使用這種簡寫語法。這省略了任何大括號或 return 語句的需要,因為這些都是隱含的。

dart
// Single-line anonymous function
[1,2,3].map((element) => element * 2).toList(); // [2, 4, 6]

箭頭語法或花括號的選擇適用於任何函式,而不僅僅是匿名函式。

dart
multiply(int a, int b) => a * b;

multiply(int a, int b) {
  return a * b;
}

生成器函式

#

Dart 支援生成器函式,這些函式返回一個惰性構建的可迭代項集合。使用 yield 關鍵字將項新增到最終的可迭代物件中,或者使用 yield* 新增整個項集合。

以下示例展示瞭如何編寫一個基本的生成器函式

dart
Iterable<int> listNumbers(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

// Returns an `Iterable<int>` that iterates
// through 0, 1, 2, 3, and 4.
print(listNumbers(5));

Iterable<int> doubleNumbersTo(int n) sync* {
  int k = 0;
  while (k < n) { 
    yield* [k, k]; 
    k++;
  }
}

print(doubleNumbersTo(3)); // Returns an iterable with [0, 0], [1, 1], and [2, 2].

這是一個同步生成器函式的示例。您還可以定義非同步生成器函式,它們返回流而不是可迭代物件。在併發部分了解更多資訊。

語句

#

本節涵蓋 Dart 和 Swift 之間語句的異同。

控制流 (if/else, for, while, switch)

#

Dart 中所有控制流語句的工作方式與 Swift 中的對應語句類似,只是語法上有一些差異。

if

#

與 Swift 不同,Dart 中的 if 語句要求條件周圍有括號。儘管 Dart 風格指南建議在流程控制語句周圍使用花括號(如下所示),但是當 if 語句沒有 else 子句且整個 if 語句適合在一行內時,如果您願意,可以省略花括號。

dart
var a = 1;
// Parentheses for conditions are required in Dart.
if (a == 1) {
  print('a == 1');
} else if (a == 2) {
  print('a == 2');
} else {
  print('a != 1 && a != 2');
}

// Curly braces are optional for single line `if` statements.
if (a == 1) print('a == 1');
swift
let a = 1;
if a == 1 {
  print("a == 1")
} else if a == 2 {
  print("a == 2")
} else {
  print("a != 1 && a != 2")
}

for(-in)

#

在 Swift 中,for 迴圈僅用於遍歷集合。要多次迴圈程式碼塊,Swift 允許您遍歷一個範圍。Dart 不支援定義範圍的語法,但除了遍歷集合的 for-in 之外,還包含一個標準 for 迴圈。

Dart 的 for-in 迴圈與 Swift 中的對應迴圈類似,它可以遍歷任何 Iterable 值,如下面的 List 示例所示

dart
var list = [0, 1, 2, 3, 4];
for (var i in list) {
  print(i);
}
swift
let array = [0, 1, 2, 3, 4]
for i in array {
  print(i)
}

Dart 的 for-in 迴圈沒有像 Swift 字典那樣允許您遍歷對映的特殊語法。要達到類似的效果,您可以將對映的條目提取為 Iterable 型別。或者,您可以使用 Map.forEach

dart
Map<String, int> dict = {
  'Foo': 1,
  'Bar': 2
};
for (var e in dict.entries) {
  print('${e.key}, ${e.value}');
}
dict.forEach((key, value) {
  print('$key, $value');
});
swift
var dict:[String:Int] = [
  "Foo":1,
  "Bar":2
]
for (key, value) in dict {
   print("\(key),\(value)")
}

運算子

#

與 Swift 不同,Dart 不允許新增新運算子,但它允許您使用 operator 關鍵字過載現有運算子。例如

dart
class Vector {
  final double x;
  final double y;
  final double z;

  Vector operator +(Vector v) {
    return Vector(x: x + v.x, y: y + v.y, z: z+v.z);
  }
}
swift
struct Vector {
  let x: Double
  let y: Double
  let z: Double
}

func +(lhs: Vector, rhs: Vector) -> Vector {
  return Vector(x: lhs.x + rhs.x, y: lhs.y + rhs.y, z: lhs.z + rhs.z)
}

...

算術運算子

#

在大多數情況下,算術運算子在 Swift 和 Dart 中的行為相同,除了除法運算子 (/) 有一個顯著例外。在 Swift(和許多其他程式語言)中,let x = 5/2 的結果是 2(一個整數)。在 Dart 中,int x = 5/2 的結果是 2.5(一個浮點值)。要獲得整數結果,請使用 Dart 的截斷除法運算子 (~/)。

雖然 ++-- 運算子存在於 Swift 的早期版本中,但它們已在 Swift 3.0 中被移除。Dart 中的對應運算子以相同的方式執行。例如

dart
assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // Result is a double
assert(5 ~/ 2 == 2); // Result is an int
assert(5 % 2 == 1); // Remainder

a = 0;
b = ++a; // Increment a before b gets its value.
assert(a == b); // 1 == 1

a = 0;
b = a++; // Increment a AFTER b gets its value.
assert(a != b); // 1 != 0

型別測試運算子

#

這兩種語言中測試運算子的實現方式略有不同。

含義Dart 運算子Swift 等效
型別轉換(見下文描述)expr as Texpr as! T
expr as? T
如果物件具有指定型別,則為 trueexpr is Texpr is T
如果物件不具有指定型別,則為 trueexpr is! T!(expr is T)

如果 objT 指定型別的子型別,則 obj is T 的結果為 true。例如,obj is Object? 總是為 true。

僅當您確定物件是該型別時,才使用型別轉換運算子將物件強制轉換為特定型別。例如

dart
(person as Employee).employeeNumber = 4204583;

Dart 只有一個單型別轉換運算子,其行為類似於 Swift 的 as! 運算子。沒有 Swift 的 as? 運算子的等效項。

swift
(person as! Employee).employeeNumber = 4204583;

如果您不確定物件是否為 T 型別,請在使用物件之前使用 is T 進行檢查。

在 Dart 中,型別提升會在 if 語句的作用域內更新區域性變數的型別。這也適用於空檢查。型別提升僅適用於區域性變數,而不適用於例項變數。

dart
if (person is Employee) {
  person.employeeNumber = 4204583;
}
swift
// Swift requires the variable to be cast.
if let person = person as? Employee {
  print(person.employeeNumber) 
}

邏輯運算子

#

邏輯運算子(例如 AND (&&)、OR (||) 和 NOT (!))在兩種語言中都是相同的。例如

dart
if (!done && (col == 0 || col == 3)) {
  // ...Do something...
}

位運算子和移位運算子

#

位運算子在兩種語言中基本相同。

例如

dart
final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // Shift left
assert((value >> 4) == 0x02); // Shift right
assert((-value >> 4) == -0x03); // Shift right // Result may differ on the web

條件運算子

#

Dart 和 Swift 都包含一個條件運算子 (?:),用於評估可能需要 if-else 語句的表示式

dart
final displayLabel = canAfford ? 'Please pay below' : 'Insufficient funds';
swift
let displayLabel = canAfford ?  "Please pay below" : "Insufficient funds"

級聯 (.. 運算子)

#

與 Swift 不同,Dart 支援使用級聯運算子進行級聯操作。這允許您在單個物件上鍊式呼叫多個方法或屬性賦值。

以下示例展示瞭如何在一個級聯操作鏈中,對一個新構造的物件設定多個屬性,然後呼叫多個方法

dart
Animal animal = Animal()
  ..name = 'Bob'
  ..age = 5
  ..feed()
  ..walk();

print(animal.name); // "Bob"
print(animal.age); // 5
swift
var animal = Animal()
animal.name = "Bob"
animal.age = 5
animal.feed()
animal.walk()

print(animal.name)
print(animal.age)

集合

#

本節涵蓋 Swift 中的一些集合型別及其與 Dart 中對應型別的比較。

列表

#

Dart 中的 List 字面量與 Swift 中的陣列定義方式相同,使用方括號並用逗號分隔。兩種語言之間的語法非常相似,但在以下示例中顯示了一些細微差異

dart
final List<String> list1 = <String>['one', 'two', 'three']; // Initialize list and specify full type
final list2 = <String>['one', 'two', 'three']; // Initialize list using shorthand type
final list3 = ['one', 'two', 'three']; // Dart can also infer the type
swift
var list1: Array<String> = ["one", "two", "three"] // Initialize array and specify the full type
var list2: [String] = ["one", "two", "three"] // Initialize array using shorthand type
var list3 = ["one", "two", "three"] // Swift can also infer the type

以下程式碼示例概述了您可以在 Dart List 上執行的基本操作。第一個示例展示瞭如何使用 index 運算子從列表中檢索值

dart
final fruits = ['apple', 'orange', 'pear'];
final fruit = fruits[1];

要將值新增到列表末尾,請使用 add 方法。要新增另一個 List,請使用 addAll 方法

dart
final fruits = ['apple', 'orange', 'pear'];
fruits.add('peach');
fruits.addAll(['kiwi', 'mango']);

有關完整的 List API,請參閱 List文件。

不可修改

#

將陣列賦值給常量(Swift 中的 let)會使陣列不可變,這意味著其大小和內容無法更改。您也不能將新陣列賦值給常量。

在 Dart 中,這略有不同,根據您的需求,您有幾種選擇

  • 如果列表是編譯時常量且不應被修改,請使用 const 關鍵字
    const fruits = ['apple', 'orange', 'pear'];
  • 將列表分配給 final 欄位。這意味著列表本身不必是編譯時常量,並確保該欄位不能被另一個列表覆蓋。然而,它仍然允許修改列表的大小或內容
    final fruits = ['apple', 'orange', 'pear'];
  • 使用不可修改建構函式(如下例所示)建立 final List。這會建立一個大小和內容都不能更改的 List,使其行為就像 Swift 中的常量 Array
dart
final fruits = List<String>.unmodifiable(['apple', 'orange', 'pear']);
swift
let fruits = ["apple", "orange", "pear"]

展開運算子

#

Dart 的另一個有用特性是展開運算子 (...) 和空感知展開運算子 (...?),它們提供了一種將多個值插入集合的簡潔方式。

例如,您可以使用展開運算子 (...) 將列表的所有值插入另一個列表,如下所示

dart
final list = [1, 2, 3];
final list2 = [0, ...list]; // [ 0, 1, 2, 3 ]
assert(list2.length == 4);

儘管 Swift 沒有展開運算子,但上面第二行的等效程式碼如下所示

swift
let list2 = [0] + list

如果展開運算子右側的表示式可能為 null,您可以使用空感知展開運算子 (...?) 來避免異常

dart
List<int>? list;
final list2 = [0, ...?list]; //[ 0 ]
assert(list2.length == 1);
swift
let list2 = [0] + list ?? []

集合

#

Dart 和 Swift 都支援使用字面量定義 Set。Set 的定義方式與列表相同,但使用花括號而不是方括號。Set 是無序集合,只包含唯一項。這些項的唯一性是透過雜湊碼實現的,這意味著物件需要雜湊值才能儲存在 Set 中。每個 Dart 物件都包含一個雜湊碼,而在 Swift 中,您需要顯式應用 Hashable 協議才能將物件儲存在 Set 中。

以下程式碼片段展示了 Dart 和 Swift 中初始化 Set 的區別

dart
final abc = {'a', 'b', 'c'};
swift
var abc: Set<String> = ["a", "b", "c"]

在 Dart 中,您不能透過指定空花括號 ({}) 來建立空集合;這會導致建立一個空 Map。要建立空 Set,請在 {} 宣告前加上型別引數或將 {} 賦值給 Set 型別的變數

dart
final names = <String>{};
Set<String> alsoNames = {}; // This works, too.
// final names = {}; // Creates an empty map, not a set.

不可修改

#

List 類似,Set 也有一個不可修改的版本。例如

dart
final abc = Set<String>.unmodifiable(['a', 'b', 'c']);
swift
let abc: Set<String> = ["a", "b", "c"]

對映

#

Dart 中的 Map 型別可以與 Swift 中的 Dictionary 型別進行比較。兩種型別都關聯鍵和值。這些鍵和值可以是任何型別的物件。每個鍵只出現一次,但您可以多次使用相同的值。

在兩種語言中,字典都基於雜湊表,這意味著鍵需要是可雜湊的。在 Dart 中,每個物件都包含一個雜湊值,而在 Swift 中,您需要顯式應用 Hashable 協議才能將物件儲存在 Dictionary 中。

這裡有一些使用字面量建立的簡單 MapDictionary 示例

dart
final gifts = {
 'first': 'partridge',
 'second': 'turtle doves',
 'fifth': 'golden rings',
};

final nobleGases = {
 2: 'helium',
 10: 'neon',
 18: 'argon',
};
swift
let gifts = [
   "first": "partridge",
   "second": "turtle doves",
   "fifth": "golden rings",
]

let nobleGases = [
   2: "helium",
   10: "neon",
   18: "argon",
]

以下程式碼示例概述了您可以在 Dart Map 上執行的基本操作。第一個示例展示瞭如何使用 key 運算子從 Map 中檢索值

dart
final gifts = {'first': 'partridge'};
final gift = gifts['first']; // 'partridge'

使用 containsKey 方法檢查 Map 中是否已存在某個鍵

dart
final gifts = {'first': 'partridge'};
assert(gifts.containsKey('fifth')); // false

使用索引賦值運算子 ([]=) 在 Map 中新增或更新條目。如果 Map 尚未包含該鍵,則新增該條目。如果該鍵存在,則更新該條目的值

dart
final gifts = {'first': 'partridge'};
gifts['second'] = 'turtle'; // Gets added
gifts['second'] = 'turtle doves'; // Gets updated

要從 Map 中移除一個條目,請使用 remove 方法;要移除所有滿足給定測試的條目,請使用 removeWhere 方法

dart
final gifts = {'first': 'partridge'};
gifts.remove('first');
gifts.removeWhere((key, value) => value == 'partridge');

#

Dart 沒有定義介面型別——任何類都可以用作介面。如果您只想引入一個介面,可以建立一個沒有具體成員的抽象類。要更詳細地瞭解這些類別,請檢視抽象類隱式介面擴充套件類部分的文件。

Dart 不支援值型別。如內建型別部分所述,Dart 中的所有型別都是引用型別(即使是原始型別),這意味著 Dart 不提供 struct 關鍵字。

列舉

#

列舉型別,通常稱為列舉或 enums,是一種特殊的類,用於表示固定數量的常量值。列舉長期以來一直是 Dart 語言的一部分,但 Dart 2.17 為成員添加了增強的列舉支援。這意味著您可以新增持有狀態的欄位、設定該狀態的建構函式、具有功能的方法,甚至覆蓋現有成員。有關更多資訊,請檢視 Dart 語言之旅中的宣告增強列舉

建構函式

#

Dart 的類建構函式與 Swift 中的類初始化器類似。然而,在 Dart 中,它們提供了更多設定類屬性的功能。

標準建構函式

#

標準類建構函式在宣告和呼叫方面都與 Swift 初始化器非常相似。Dart 使用完整的類名而不是 init 關鍵字。new 關鍵字曾經是建立新類例項所必需的,現在是可選的,並且不再推薦使用。

dart
class Point {
  double x = 0;
  double y = 0;

  Point(double x, double y) {
    // There's a better way to do this in Dart, stay tuned.
    this.x = x;
    this.y = y;
  }
}

// Create a new instance of the Point class
Point p = Point(3, 5);

建構函式引數

#

由於在建構函式中編寫程式碼來分配所有類欄位通常相當冗餘,Dart 提供了一些語法糖來簡化此操作

dart
class Point {
  double x;
  double y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

// Create a new instance of the Point class
Point p = Point(3, 5);

與函式類似,建構函式也可以接受可選的位置引數或命名引數

dart
class Point {
  ...
  // With an optional positioned parameter
  Point(this.x, [this.y = 0]);
  // With named parameters
  Point({required this.y, this.x = 0});
  // With both positional and named parameters
  Point(int x, int y, {int scale = 1}) {
    ...
  }
  ...
}

初始化列表

#

您也可以使用初始化列表,它在建構函式引數中使用 this 直接設定的任何欄位之後執行,但在建構函式主體之前執行

dart
class Point {
  ...
  Point(Map<String, double> json)
      : x = json['x']!,
        y = json['y']! {
    print('In Point.fromJson(): ($x, $y)');
  }
  ...
}

初始化列表是使用斷言的好地方。

命名建構函式

#

與 Swift 不同,Dart 允許類透過命名擁有多個建構函式。您可以選擇使用一個未命名建構函式,但任何額外的建構函式都必須命名。一個類也可以只有命名建構函式。

dart
class Point {
  double x;
  double y;

  Point(this.x, this.y);

  // Named constructor
  Point.fromJson(Map<String, double> json)
      : x = json['x']!,
        y = json['y']!;
}

Const 建構函式

#

當您的類例項始終不可變時,您可以透過新增 const 建構函式來強制執行此操作。刪除 const 建構函式對於使用您的類的人來說是一個重大更改,因此請謹慎使用此功能。將建構函式定義為 const 會使類不可修改:類中所有非靜態欄位都必須標記為 final

dart
class ImmutablePoint {
  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

這也意味著您可以將該類用作常量值,使物件成為編譯時常量

dart
const ImmutablePoint origin = ImmutablePoint(0, 0);

建構函式重定向

#

您可以從其他建構函式呼叫建構函式,例如,為了防止程式碼重複或為引數新增額外的預設值

dart
class Point {
  double x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(double x) : this(x, 0);
}

工廠建構函式

#

當您不需要建立新的類例項時,可以使用工廠建構函式。一個例子是如果可以返回快取的例項

dart
class Logger {
  static final Map<String, Logger> _cache =
    <String, Logger>{};
  
  final String name;
  
  // Factory constructor that returns a cached copy,
  // or creates a new one if it's not yet available.
  factory Logger(String name)=> _cache[name] ??= Logger._internal(name);
  // Private constructor used only in this library
  Logger._internal(this.name);
}

方法

#

在 Dart 和 Swift 中,方法是為物件提供行為的函式。

dart
void doSomething() { // This is a function
 // Implementation..
}

class Example {
 void doSomething() { // This is a method
   // Implementation..
 }
}
swift
func doSomething() { // This is a function
  // Implementation..
}

class Example {
  func doSomething() { // This is a method
    // Implementation..
  }
}

Getter 和 Setter

#

您可以透過在欄位名前加上 getset 關鍵字來定義 getter 和 setter。您可能還記得每個例項欄位都有一個隱式 getter,如果適用,還有一個 setter。在 Swift 中,語法略有不同,因為 getset 關鍵字需要定義在屬性語句內部,並且只能作為語句定義,而不能作為表示式

dart
class Rectangle {
  double left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  double get right => left + width;
  set right(double value) => width = value - left;

  double get bottom => top + height;
  set bottom(double value) => height = value - top;
}
swift
class Rectangle {
 var left, top, width, height: Double;

 init(left: Double, top: Double, width: Double, height: Double) {
   self.left = left
   self.top = top
   self.width = width
   self.height = height
 }

 // Define two calculated properties: right and bottom.
 var right: Double {
   get {
     return left + width
   }
   set { width = newValue - left }
 }

 var bottom: Double {
   get {
     return top + height
   }
   set { height = newValue - top }
 }
}

抽象類

#

Dart 具有抽象類的概念,這是 Swift 不支援的。抽象類不能直接例項化,只能被子類化。這使得抽象類在定義介面(類似於 Swift 中的協議)時很有用。

抽象類通常包含抽象方法,這些方法宣告沒有實現。非抽象子類被迫覆蓋這些方法並提供適當的實現。抽象類還可以包含具有預設實現的方法。如果子類在擴充套件抽象類時沒有覆蓋這些方法,則會繼承此實現。

要定義抽象類,請使用 abstract 修飾符。以下示例聲明瞭一個抽象類,該類有一個抽象方法和一個包含預設實現的方法

dart
// This class is declared abstract and thus can't be instantiated.
abstract class AbstractContainer {
  void updateChildren(); // Abstract method.

  // Method with default implementation.
  String toString() => "AbstractContainer";
}

隱式介面

#

在 Dart 語言中,每個類都隱式定義一個介面,包含該類及其實現的任何介面的所有例項成員。如果您想建立一個類 A 來支援類 B 的 API 而不繼承 B 的實現,則類 A 應該實現 B 介面。

與 Dart 不同,Swift 類不隱式定義介面。介面需要顯式定義為協議並由開發者實現。

一個類可以實現一個或多個介面,然後提供介面所需的 API。Dart 和 Swift 有不同的實現介面的方式。例如

dart
abstract class Animal {
  int getLegs();
  void makeNoise();
}

class Dog implements Animal {
  @override
  int getLegs() => 4;

  @override
  void makeNoise() => print('Woof woof');
}
swift
protocol Animal {
   func getLegs() -> Int;
   func makeNoise()
}

class Dog: Animal {
  func getLegs() -> Int {
    return 4;
  }

  func makeNoise() {
    print("Woof woof"); 
  }
}

擴充套件類

#

Dart 中的類繼承與 Swift 非常相似。在 Dart 中,您可以使用 extends 建立子類,使用 super 引用超類

dart
abstract class Animal {
  // Define constructors, fields, methods...
}

class Dog extends Animal {
  // Define constructors, fields, methods...
}
swift
class Animal {
  // Define constructors, fields, methods...
}

class Dog: Animal {
  // Define constructors, fields, methods...
}

混入

#

混入(Mixins)允許您的程式碼在類之間共享功能。您可以在類中使用混入的欄位和方法,就像它們是類的一部分一樣。一個類可以使用多個混入——這在多個類共享相同功能時很有用——而無需相互繼承或共享共同祖先。

雖然 Swift 不支援混入,但如果您編寫一個協議並結合一個為協議中指定的方法提供預設實現的擴充套件,則可以近似實現此功能。這種方法的主要問題是,與 Dart 不同,這些協議擴充套件不維護它們自己的狀態。

您可以像普通類一樣宣告混入,只要它不擴充套件 Object 以外的任何類且沒有建構函式。使用 with 關鍵字將一個或多個逗號分隔的混入新增到類中。

以下示例展示了 Dart 中如何實現此行為,以及 Swift 中如何複製類似行為

dart
abstract class Animal {}

// Defining the mixins
mixin Flyer {
  fly() => print('Flaps wings');
}
mixin Walker {
  walk() => print('Walks legs');
}
  
class Bat extends Animal with Flyer {}
class Goose extends Animal with Flyer, Walker {}
class Dog extends Animal with Walker {}

// Correct calls
Bat().fly();
Goose().fly();
Goose().walk(); 
Dog().walk();

// Incorrect calls
Bat().walk(); // Not using the Walker mixin
Dog().fly(); // Not using the Flyer mixin
class Animal {
}
swift
// Defining the "mixins"
protocol Flyer {
  func fly()
}

extension Flyer {
  func fly() {
    print("Flaps wings")
  }
}

protocol Walker {
  func walk()
}

extension Walker {
  func walk() {
    print("Walks legs")
  }
}

class Bat: Animal, Flyer {}
class Goose: Animal, Flyer, Walker {}
class Dog: Animal, Walker {}

// Correct calls
Bat().fly();
Goose().fly();
Goose().walk();
Dog().walk();

// Incorrect calls
Bat().walk(); // `bat` doesn't have the `walk` method
Dog().fly(); // "dog" doesn't have the `fly` method

class 關鍵字替換為 mixin 可以防止混入被用作常規類。

dart
mixin Walker {
  walk() => print('Walks legs');
}

// Impossible, as Walker is no longer a class.
class Bat extends Walker {}

由於可以使用多個混入,當它們在同一個類上使用時,它們的方法或欄位可能會相互重疊。它們甚至可能與使用它們的類或該類的超類重疊。為了解決這個問題,Dart 將它們堆疊在一起,因此它們新增到類中的順序很重要。

舉個例子

dart
class Bird extends Animal with Consumer, Flyer {

當在 Bird 的例項上呼叫方法時,Dart 從堆疊底部開始,首先是其自身的類 Bird,它優先於其他實現。如果 Bird 沒有實現,那麼 Dart 會繼續向上移動堆疊,接下來是 Flyer,然後是 Consumer,直到找到一個實現。如果找不到任何實現,則最後檢查父類 Animal

擴充套件方法

#

與 Swift 類似,Dart 提供了擴充套件方法,允許您向現有型別新增功能——特別是方法、getter、setter 和運算子。Dart 和 Swift 中建立擴充套件的語法看起來非常相似

dart
extension <name> on <type> {
  (<member definition>)*
}
swift
extension <type> {
  (<member definition>)*
}

例如,以下 Dart SDK 中 String 類的擴充套件允許解析整數

dart
extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
}

print('21'.parseInt() * 2); // 42
swift
extension String {
  func parseInt() -> Int {
    return Int(self) ?? 0
  }
}

print("21".parseInt() * 2) // 42

儘管擴充套件在 Dart 和 Swift 中相似,但仍有一些關鍵區別。以下部分涵蓋了最重要的差異,但請檢視擴充套件方法以獲取完整概述。

命名擴充套件

#

儘管不是強制性的,但您可以在 Dart 中命名擴充套件。命名擴充套件允許您控制其範圍——這意味著在它與另一個庫衝突時,可以隱藏或顯示該擴充套件。如果名稱以下劃線開頭,則該擴充套件僅在其定義的庫中可用。

dart
// Hide "MyExtension" when importing types from
// "path/to/file.dart".
import 'path/to/file.dart' hide MyExtension; 
// Only show "MyExtension" when importing types
// from "path/to/file.dart".
import 'path/to/file.dart' show MyExtension;

// The `shout()` method is only available within this library.
extension _Private on String {
  String shout() => this.toUpperCase();
}

初始化器

#

在 Swift 中,您可以使用擴充套件為型別新增新的便利初始化器。在 Dart 中,您不能使用擴充套件為類新增額外的建構函式,但可以新增一個靜態擴充套件方法來建立該型別的例項。考慮以下示例

dart
class Person {
  Person(this.fullName);

  final String fullName;
}

extension ExtendedPerson on Person {
  static Person create(String firstName, String lastName) {
    return Person("$firstName $lastName");
  }
}

// To use the factory method, use the name of
// the extension, not the type.
final person = ExtendedPerson.create('John', 'Doe');

重寫成員

#

重寫例項方法(包括運算子、getter 和 setter)在兩種語言中也非常相似。在 Dart 中,您可以使用 @override 註解來指示您有意重寫一個成員

dart
class Animal {
  void makeNoise => print('Noise');
}

class Dog implements Animal {
  @override
  void makeNoise() => print('Woof woof');
}

在 Swift 中,您將 override 關鍵字新增到方法定義中

swift
class Animal {
  func makeNoise() {
    print("Noise")
  }
}

class Dog: Animal {
  override func makeNoise() {
    print("Woof woof"); 
  }
}

泛型

#

與 Swift 一樣,Dart 支援使用泛型來提高型別安全或減少程式碼重複。

泛型方法

#

您可以將泛型應用於方法。要定義泛型型別,請將其放置在方法名後的 < > 符號之間。然後,該型別可以在方法內部(作為返回型別)或在方法的引數中使用

dart
// Defining a method that uses generics.
T transform<T>(T param) {
  // For example,  doing some transformation on `param`...
  return param;
}

// Calling the method. Variable "str" will be
// of type String.
var str = transform('string value');

在這種情況下,將 String 傳遞給 transform 方法可確保它返回 String。同樣,如果提供 int,則返回值是 int

透過逗號分隔來定義多個泛型

dart
// Defining a method with multiple generics.
T transform<T, Q>(T param1, Q param2) {
  // ...
}
// Calling the method with explicitly-defined types.
transform<int, String>(5, 'string value');
// Types are optional when they can be inferred.
transform(5, 'string value');

泛型類

#

泛型也可以應用於類。您可以在呼叫建構函式時指定型別,這允許您根據特定型別定製可重用類。

在以下示例中,Cache 類用於快取特定型別

dart
class Cache<T> {
  T getByKey(String key) {}
  void setByKey(String key, T value) {}
}
// Creating a cache for strings.
// stringCache has type Cache<String>
var stringCache = Cache<String>();
// Valid, setting a string value.
stringCache.setByKey('Foo', 'Bar')
// Invalid, int type doesn't match generic.
stringCache.setByKey('Baz', 5)

如果省略型別宣告,則執行時型別為 Cache<dynamic>,並且對 setByKey 的兩次呼叫都有效。

限制泛型

#

您可以使用泛型透過 extends 將程式碼限制為一系列型別。這確保了您的類使用擴充套件特定型別的泛型型別進行例項化(並且與 Swift 類似)

dart
class NumberManager<T extends num> {
  // ...
}
// Valid
var manager = NumberManager<int>(); 
var manager = NumberManager<double>(); 
// Invalid, neither String nor its parent classes extend num.
var manager = NumberManager<String>();

字面量中的泛型

#

Map-Set-List- 字面量可以顯式宣告泛型型別,這在型別未推斷或推斷不正確時很有用。

例如,List 類有一個泛型定義:class List<E>。泛型型別 E 指的是列表內容的型別。通常,此型別是自動推斷的,並在 List 類的一些成員型別中使用。(例如,它的第一個 getter 返回 E 型別的值)。當定義 List 字面量時,您可以如下顯式定義泛型型別

dart
var objList = [5, 2.0]; // Type: List<num> // Automatic type inference
var objList = <Object>[5, 2.0]; // Type: List<Object> // Explicit type definition
var objSet = <Object>{5, 2.0}; // Sets work identically

Map 也是如此,它也使用泛型定義其 keyvalue 型別 (class Map<K, V>)

dart
// Automatic type inference
var map = {
  'foo': 'bar'
}; // Type: Map<String, String>
// Explicit type definition:
var map = <String, Object>{
  'foo': 'bar'
}; // Type: Map<String, Object>

併發

#

Swift 支援多執行緒,Dart 支援 isolates,它們類似於輕量級執行緒,此處不作介紹。每個 isolate 都有自己的事件迴圈。有關更多資訊,請參閱isolates 的工作原理

Future

#

原生的 Swift 沒有 Dart 的 Future 的對應物。但是,如果您熟悉 Apple 的 Combine 框架或第三方庫(如 RxSwift 或 PromiseKit),您可能仍然瞭解這個物件。

簡而言之,Future 表示非同步操作的結果,該結果將在稍後可用。如果您有一個函式返回 StringFuture (Future<String>) 而不僅僅是 String,那麼您基本上是在接收一個可能在稍後——未來——存在的值。

當 Future 的非同步操作完成時,值就變得可用。但是,您應該記住,Future 也可以在完成時返回一個錯誤而不是一個值。

一個例子是,如果您發出一個 HTTP 請求,並立即收到一個 Future 作為響應。一旦結果到來,Future 就以該值完成。但是,如果 HTTP 請求失敗,例如因為網際網路連線中斷,則 Future 會以錯誤而不是值完成。

Future 也可以手動建立。建立 Future 最簡單的方法是定義和呼叫 async 函式,這將在下一節中討論。當您有一個需要成為 Future 的值時,您可以使用 Future 類輕鬆地將其轉換為 Future

dart
String str = 'String Value';
Future<String> strFuture = Future<String>.value(str);

Async/await

#

雖然 Future 不屬於原生的 Swift,但 Dart 中的 async/await 語法在 Swift 中有對應的概念,並且以類似的方式工作,儘管沒有 Future 物件。

與 Swift 一樣,函式可以標記為 async。Dart 的不同之處在於,任何 async 函式總是隱式返回一個 Future。例如,如果您的函式返回一個 String,則此函式的非同步對應函式返回一個 Future<String>

Swift 中放在 async 關鍵字之後的 throws 關鍵字(但僅當函式可丟擲時)在 Dart 的語法中不存在,因為 Dart 異常和錯誤不會被編譯器檢查。相反,如果非同步函式中發生異常,返回的 Future 會因該異常而失敗,然後可以適當地處理該異常。

dart
// Returns a future of a string, as the method is async
Future<String> fetchString() async {
  // Typically some other async operations would be done here.
  
  Response response = await makeNetworkRequest();
  if (!response.success) {
    throw BadNetwork();
  }

  return 'String Value';
}

然後可以按如下方式呼叫此非同步函式

dart
String stringFuture = await fetchString();
print(str); // "String Value"

Swift 中等效的非同步函式

swift
func fetchString() async throws -> String {
  // Typically some other async operations would be done here.
  let response = makeNetworkRequest()
  if !response.success {
    throw BadNetwork()
  }
  
  return "String Value"
}

同樣,非同步函式中發生的任何異常都可以透過使用 catchError 方法以處理失敗的 Future 的相同方式進行處理。

在 Swift 中,非同步函式不能從非非同步上下文呼叫。在 Dart 中,您被允許這樣做,但必須正確處理結果 Future。不必要地從非非同步上下文呼叫非同步函式被認為是不好的做法。

與 Swift 一樣,Dart 也有 await 關鍵字。在 Swift 中,await 僅在呼叫 async 函式時可用,但 Dart 的 await 適用於 Future 類。因此,await 也適用於 async 函式,因為 Dart 中的所有 async 函式都返回 Future。

等待一個 Future 會暫停當前函式的執行,並將控制權返回給事件迴圈,事件迴圈可以處理其他事情,直到 Future 以值或錯誤完成。在那之後的某個時候,await 表示式會評估為該值或丟擲該錯誤。

當 Future 完成時,將返回其值。您只能在 async 上下文中 await,就像在 Swift 中一樣。

dart
// We can only await futures within an async context.
asyncFunction() async {
  String returnedString = await fetchString();
  print(returnedString); // 'String Value'
}

當被等待的 Future 失敗時,會在帶有 await 關鍵字的行上丟擲一個錯誤物件。您可以使用常規的 try-catch 塊來處理它

dart
// We can only await futures within an async context.
Future<void> asyncFunction() async {
  String? returnedString;
  try {
    returnedString = await fetchString();
  } catch (error) {
    print('Future encountered an error before resolving.');
    return;
  }
  print(returnedString);
}

有關更多資訊和互動式練習,請檢視非同步程式設計教程。

Stream

#

Dart 非同步工具箱中的另一個工具是 Stream 類。雖然 Swift 有自己的 Stream 概念,但 Dart 中的 Stream 與 Swift 中的 AsyncSequence 類似。同樣,如果您瞭解 Observables(在 RxSwift 中)或 Publishers(在 Apple 的 Combine 框架中),那麼 Dart 的 Stream 應該會感到熟悉。

對於不熟悉 StreamAsyncSequencePublishersObservables 的人來說,其概念如下:Stream 本質上就像一個 Future,但它隨時間傳播多個值,就像一個事件匯流排。可以監聽 Stream 以接收值或錯誤事件,並且當不再發送事件時可以關閉它們。

監聽

#

要監聽一個流,您可以在 async 上下文中使用 for-in 迴圈結合流。for 迴圈會為每個發出的項呼叫回撥方法,並在流完成或出錯時結束

dart
Future<int> sumStream(Stream<int> stream) async {
  var sum = 0;
  try { 
    await for (final value in stream) {
      sum += value;
    }
  } catch (error) {
    print('Stream encountered an error! $err');
  }
  return sum;
}

如果在監聽流時發生錯誤,則會在包含 await 關鍵字的行上丟擲錯誤,您可以使用 try-catch 語句進行處理

dart
try {
  await for (final value in stream) { ... }
} catch (err) {
  print('Stream encountered an error! $err');
}

這不是監聽流的唯一方法:您也可以呼叫其 listen 方法並提供回撥,該回調在流發出值時被呼叫

dart
Stream<int> stream = ...
stream.listen((int value) {
  print('A value has been emitted: $value');
});

listen 方法有一些可選的回撥函式,用於錯誤處理或流完成時呼叫

dart
stream.listen(
  (int value) { ... },
  onError: (err) {
    print('Stream encountered an error! $err');
  },
  onDone: () {
    print('Stream completed!');
  },
);

listen 方法返回一個 StreamSubscription 例項,您可以使用它來停止監聽流

dart
StreamSubscription subscription = stream.listen(...);
subscription.cancel();

建立流

#

與 Future 一樣,您有幾種不同的方式來建立流。兩種最常見的方式是使用非同步生成器或 SteamController

非同步生成器
#

非同步生成器函式的語法與同步生成器函式相同,但使用 async* 關鍵字而不是 sync*,並且返回一個 Stream 而不是 Iterable。這種方法類似於 Swift 中的 AsyncStream 結構。

在非同步生成器函式中,yield 關鍵字將給定值傳送到流。然而,yield* 關鍵字與流而不是其他可迭代物件一起工作。這允許將來自其他流的事件傳送到此流。在以下示例中,函式只有在新生成的流完成之後才會繼續執行

dart
Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

Stream<int> stream = asynchronousNaturalsTo(5);

您還可以使用 StreamController API 建立流。有關更多資訊,請參閱使用 StreamController

文件註釋

#

常規註釋在 Dart 中的工作方式與 Swift 中相同。使用雙反斜槓 (//) 註釋掉該行雙反斜槓之後的所有內容,而 /* ... */ 塊註釋則可以跨越多行。

除了常規註釋,Dart 還有文件註釋,它們與dart doc協同工作:這是一個第一方工具,用於生成 Dart 包的 HTML 文件。將文件註釋放置在所有公共成員宣告之上被認為是最佳實踐。您可能會注意到此過程與 Swift 中為各種文件生成工具添加註釋的方式類似。

與 Swift 一樣,您可以透過使用三個正斜槓而不是兩個 (///) 來定義文件註釋

dart
/// The number of characters in this chunk when unsplit.
int get length => ...

在文件註釋中,用方括號將型別、引數和方法名括起來。

dart
/// Returns the [int] multiplication result of [a] * [b].
multiply(int a, int b) => a * b;

雖然支援 JavaDoc 風格的文件註釋,但您應該避免使用它們並使用 /// 語法。

dart
/** 
 * The number of characters in this chunk when unsplit. 
 * (AVOID USING THIS SYNTAX, USE /// INSTEAD.)
 */
int get length => ...

庫和可見性

#

Dart 的可見性語義與 Swift 類似,Dart 庫大致相當於 Swift 模組。

Dart 提供兩種訪問控制級別:公共和私有。方法和變數預設是公共的。私有變數以 _ 字元 (_) 為字首,並由 Dart 編譯器強制執行。

dart
final foo = 'this is a public property';
final _foo = 'this is a private property';

String bar() {
  return 'this is a public method';
}
String _bar() {
  return 'this is a private method';
}

// Public class
class Foo {
}

// Private class
class _Foo {
},

私有方法和變數在 Dart 中作用域限定在其庫內,在 Swift 中作用域限定在其模組內。在 Dart 中,您可以在一個檔案中定義一個庫,而在 Swift 中,您必須為您的模組建立一個新的構建目標。這意味著在一個 Dart 專案中您可以定義 n 個庫,但在 Swift 中您必須建立 n 個模組。

屬於庫的所有檔案都可以訪問該庫中的所有私有物件。但出於安全原因,檔案仍需要允許特定檔案訪問其私有物件,否則任何檔案(即使是來自專案外部的檔案)都可以註冊到您的庫並訪問可能敏感的資料。換句話說,私有物件不會在庫之間共享。

animal.dart
dart
library animals;

part 'parrot.dart';

class _Animal {
  final String _name;

  _Animal(this._name);
}
parrot.dart
dart
part of animals;

class Parrot extends _Animal {
  Parrot(String name) : super(name);

  // Has access to _name of _Animal
  String introduction() {
    return 'Hello my name is $_name';
  }
}

有關更多資訊,請檢視建立包

下一步

#

本指南向您介紹了 Dart 和 Swift 之間的主要區別。此時,您可能會考慮轉向 DartFlutter(一個使用 Dart 從單個程式碼庫構建精美、原生編譯的多平臺應用程式的開源框架)的通用文件,在那裡您將找到有關該語言的深入資訊和實用的入門方法。