目錄

以 Swift 開發者的角度學習 Dart

本指南旨在利用您在學習 Dart 時的 Swift 程式設計知識。它展示了這兩種語言的主要異同之處,並介紹了 Swift 中不存在的 Dart 概念。身為 Swift 開發者,Dart 可能會讓您感到熟悉,因為這兩種語言都具有許多概念。

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

和 Swift 一樣,Dart 對於集合泛型並行(使用 async/await)和擴展具有類似的支援。

混入是 Dart 中另一個 Swift 開發者可能不熟悉的概念。與 Swift 類似,Dart 也提供 AOT(提前)編譯。但是,Dart 也支援 JIT(即時)編譯模式,以協助各種開發方面,例如增量重新編譯或除錯。如需更多資訊,請查看 Dart 總覽

慣例與 Linting

#

Swift 和 Dart 都有 linting 工具來強制執行標準慣例。然而,雖然 Swift 有 SwiftLint 作為獨立工具,但 Dart 具有官方的佈局慣例,並包含一個 linter 以使符合性毫不費力。若要自訂專案的 lint 規則,請按照自訂靜態分析指示進行。(請注意,Dart 和 Flutter 的 IDE 外掛程式也提供此功能。)

Dart 也提供程式碼格式化工具,當從命令列或透過 IDE 執行 dart format 時,可以自動格式化任何 Dart 專案。

如需有關 Dart 慣例和 linting 的詳細資訊,請查看Effective 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 建構子,使這些類別成為不可變的(不變更的),並能夠建立這些類別的執行個體作為編譯時常數。如需更多資訊,請查看常數建構子

內建類型

#

Dart 在平台程式庫中包含許多型別,例如:

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

如需更多資訊,請查看 Dart 語言導覽中的內建型別

數字

#

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

num
一個通用的 64 位元數字型別。
int
一個與平台相關的整數。在原生程式碼中,它是一個 64 位元二補數整數。在網路上,它是一個非分數的 64 位元浮點數。
double
一個 64 位元浮點數。

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

所有這些型別也是 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 中,您可以使用等號 (==) 運算子將整數值與 double 值進行比較,如下所示:

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

此程式碼會印出 true。但是,在 Dart 中,網路和原生平台之間的底層數字實作不同。 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 語言導覽中的符文和字形叢集

字串串連和多行宣告

#

在 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 + '!';

字串內插

#

使用 ${<expression>} 語法將運算式插入字串常值中。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 關鍵字可以指派給類別欄位,以指示它們稍後初始化,同時保持不可為空值。這與 Swift 的「隱含解包可選值」類似。這對於在初始化之前從未觀察到變數的情況很有用,允許稍後初始化它。不可為空值的 late 欄位無法在稍後指派空值。此外,當在初始化之前觀察到不可為空值的 late 欄位時,會擲回執行階段錯誤,這是在行為良好的應用程式中想要避免的情況。

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

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

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

在這種情況下,只有在呼叫 heat()chill() 之後才會初始化 _temperature。如果先呼叫 serve(),則會發生執行階段例外狀況。請注意,_temperature 永遠不能為 null

您也可以使用 late 關鍵字,在將其與初始化器結合使用時,使初始化延遲。

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

在這種情況下,_readThermometer() 只會在第一次存取欄位時執行,而不是在初始化時執行。

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

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 中對應項目的比較。

List

#

List 常值在 Dart 中的定義方式與 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 沒有展開運算子,但上述第 2 行的對等項目如下

swift
let list2 = [0] + list

如果展開運算子右側的運算式可能為 null,您可以使用可感知空值的展開運算子 (...?) 來避免例外狀況

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

Set

#

Dart 和 Swift 都支援使用常值定義 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"]

Map

#

在 Dart 中,Map 類型可以與 Swift 中的 Dictionary 類型相比較。這兩種類型都將鍵(key)與值(value)關聯起來。這些鍵和值可以是任何類型的物件。每個鍵只會出現一次,但是您可以多次使用相同的值。

在兩種語言中,字典都是基於雜湊表(hash table)實作的,這意味著鍵需要是可雜湊的(hashable)。在 Dart 中,每個物件都包含一個雜湊值,而在 Swift 中,您需要在物件可以存儲在 Dictionary 中之前,明確地應用 Hashable 協定。

這裡有一些簡單的 MapDictionary 範例,使用字面值(literal)創建。

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 中的類別初始化器(initializer)的運作方式相似。然而,在 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)');
  }
  ...
}

初始化器列表是使用 assert 的好地方。

命名建構子

#

與 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 會使類別成為不可修改的:類別中的所有非靜態欄位都必須標記為 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),它們類似於輕量級執行緒,此處不做深入探討。每個隔離區都有自己的事件迴圈。如需更多資訊,請參閱隔離區如何運作

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,則此函式的 async 對應項返回一個 Future<String>

在 Swift 中,async 關鍵字之後放置的 throws 關鍵字(但僅在函式可拋出的情況下)在 Dart 的語法中不存在,因為 Dart 的例外和錯誤不會由編譯器檢查。相反,如果 async 函式中發生例外,則返回的 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';
}

然後可以如下呼叫此 async 函式

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

Swift 中等效的 async 函式

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"
}

同樣地,在 async 函式中發生的任何例外都可以使用 catchError 方法以與處理失敗的 Future 相同的方式進行處理。

在 Swift 中,async 函式無法從非 async 環境中調用。在 Dart 中,您可以這樣做,但您必須正確處理產生的 Future。從非 async 環境中不必要地呼叫 async 函式被認為是不好的做法。

與 Swift 類似,Dart 也具有 await 關鍵字。在 Swift 中,await 僅在呼叫 async 函式時可用,但 Dart 的 await 可與 Future 類別一起使用。因此,await 也適用於 async 函式,因為所有 async 函式在 Dart 中都會返回 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 有自己的串流概念,但 Dart 中的串流與 Swift 中的 AsyncSequence 相似。同樣地,如果您了解 Observables(在 RxSwift 中)或 Publishers(在 Apple 的 Combine 框架中),您應該會覺得 Dart 的串流很熟悉。

對於那些不熟悉 StreamsAsyncSequencePublishersObservables 的人來說,概念如下:Stream 本質上就像 Future,但具有多個值隨著時間分佈,就像事件匯流排一樣。可以監聽串流以接收值或錯誤事件,並且可以在不再發送任何事件時關閉串流。

監聽

#

要監聽串流,您可以將串流與 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();

建立 Stream

#

與 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 的一般文件(Flutter 是一個開放原始碼框架,使用 Dart 從單個程式碼庫構建美觀、本機編譯、多平台應用程式),您可以在其中找到有關該語言的深入資訊和入門的實用方法。