内容

Swift 开发者学习 Dart

本指南旨在在学习 Dart 时利用您现有的 Swift 编程知识。它展示了两种语言的关键相似之处和差异,并介绍了 Swift 中不存在的 Dart 概念。作为一名 Swift 开发者,您可能会觉得 Dart 非常熟悉,因为这两种语言共享许多概念。

Swift 和 Dart 都支持健全的空安全。默认情况下,这两种语言都不允许变量为 null。

与 Swift 类似,Dart 对 集合泛型并发(使用 async/await)和 扩展 提供了类似的支持。

混合 是 Dart 中 Swift 开发者可能不熟悉的一个概念。与 Swift 一样,Dart 提供 AOT(提前编译)。但是,Dart 还支持 JIT(即时编译)模式来帮助解决各种开发方面的问题,例如增量重新编译或调试。有关更多信息,请查看 Dart 概览

约定和 Lint

#

Swift 和 Dart 都有 Lint 工具来强制执行标准约定。但是,虽然 Swift 将 SwiftLint 作为独立工具,但 Dart 具有官方的布局约定,并包含一个 Lint 工具以轻松实现合规性。要为您的项目自定义 Lint 规则,请按照 自定义静态分析 说明进行操作。(请注意,Dart 和 Flutter 的 IDE 插件也提供了此功能。)

Dart 还提供了一个代码格式化程序,它可以在从命令行或通过 IDE 运行 dart format 时自动格式化任何 Dart 项目。

有关 Dart 约定和 Lint 的更多信息,请查看 Effective DartLint 规则

变量

#

与 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 Lint 通过生成警告来阻止这种情况。如果您确实想要允许变量具有任何类型,则最好将其赋值为 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 在平台库中包含许多类型,例如

  • 基本值类型,例如
    • 数字 (numintdouble)
    • 字符串 (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 语言教程中的符文和音节簇

字符串连接和多行声明

#

在 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 恰好为 null,则会发生运行时错误。

?. 运算符类似,在访问对象上的属性或方法时使用 ! 运算符

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

如果 myObject 在运行时为 null,则会发生运行时错误。

延迟字段

#

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 不支持 元组(尽管在 pub.dev 上有几个元组包可用)。如果函数需要返回多个值,则可以将它们包装在集合(如列表、集合或映射)中,或者可以编写一个包装类,其中可以返回包含这些值的实例。有关此内容的更多信息,请参阅有关集合的部分。

异常和错误处理

#

与 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 样式指南建议在控制流语句周围使用花括号(如下所示),但是当您有一个没有 else 子句的 if 语句并且整个 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
如果对象具有指定的类型,则为真expr is Texpr is T
如果对象不具有指定的类型,则为真expr is! T!(expr is T)

如果 objT 指定的类型的子类型,则 obj is T 的结果为 true。例如,obj is Object? 始终为真。

使用类型转换运算符将对象转换为特定类型,当且仅当您确定对象属于该类型时。例如

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 字面量在 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 ?? []

集合

#

Dart 和 Swift 都支持使用字面量定义 Set。集合的定义方式与列表相同,但使用花括号而不是方括号。集合是无序集合,只包含唯一项。这些项的唯一性是使用哈希码实现的,这意味着对象需要哈希值才能存储在 Set 中。每个 Dart 对象都包含一个哈希码,而在 Swift 中,您需要在对象可以存储在 Set 中之前显式应用 Hashable 协议。

以下代码片段显示了在 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.

不可修改

#

类似于 ListSet 也具有不可修改版本。例如

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

映射

#

Dart 中的 Map 类型可以与 Swift 中的 Dictionary 类型进行比较。这两种类型都关联键和值。这些键和值可以是任何类型的对象。每个键只出现一次,但您可以多次使用相同的值。

在两种语言中,字典都基于哈希表,这意味着键需要是可哈希的。在 Dart 中,每个对象都包含一个哈希值,而在 Swift 中,需要在对象存储到 Dictionary 之前显式应用 Hashable 协议。

以下是一些使用字面量创建的简单 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 关键字。

枚举

#

枚举类型,通常称为枚举或枚举,是一种特殊的类,用于表示固定数量的常量值。枚举长期以来一直是 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 会使类不可修改:类中所有非静态字段都必须标记为 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 语言中,每个类都隐式定义一个接口,其中包含该类的所有实例成员以及它实现的任何接口的实例成员。如果要创建一个支持类 B 的 API 但不继承 B 的实现的类 A,则类 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...
}

混合

#

Mixin 允许您的代码在类之间共享功能。您可以使用 mixin 的字段和方法在一个类中,就像它们是类的一部分一样使用它们的功能。一个类可以使用多个 mixin——当多个类共享相同的功能时很有用——而无需相互继承或共享一个共同的祖先。

虽然 Swift 不支持 mixin,但如果编写一个协议以及一个为协议中指定的方法提供默认实现的扩展,它可以近似实现此功能。这种方法的主要问题是,与 Dart 不同,这些协议扩展不会维护自己的状态。

您可以像普通类一样声明 mixin,只要它不扩展 Object 以外的任何类并且没有构造函数即可。使用 with 关键字将一个或多个用逗号分隔的 mixin 添加到类中。

以下示例演示了如何在 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 可以防止 mixin 用作普通类。

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

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

由于您可以使用多个 mixin,因此当在同一类中使用时,它们的方法或字段可能会相互重叠。它们甚至可能与使用它们的类或该类的超类重叠。为了解决此问题,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,它也使用泛型(class Map<K, V>)定义其 keyvalue 类型。

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),它们类似于轻量级线程,此处不再赘述。每个隔离区都有自己的事件循环。更多信息,请参阅 隔离区的工作原理

Futures

#

原生 Swift 没有与 Dart 的 Future 等效的概念。但是,如果您熟悉 Apple 的 Combine 框架或 RxSwift 或 PromiseKit 等第三方库,您可能仍然了解此对象。

简而言之,future 代表异步操作的结果,该结果将在稍后时间可用。如果您有一个返回 StringFutureFuture<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 异常和错误不会被编译器检查。相反,如果异步函数中发生异常,则返回的 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"
}

类似地,在 async 函数中发生的任何异常都可以像处理失败的 Future 一样进行处理,使用 catchError 方法。

在 Swift 中,不能从非异步上下文中调用异步函数。在 Dart 中,您可以这样做,但必须正确处理生成的 Future。在非异步上下文中不必要地调用异步函数被认为是不好的实践。

与 Swift 一样,Dart 也具有 await 关键字。在 Swift 中,await 仅在调用 async 函数时可用,但 Dart 的 awaitFuture 类一起使用。因此,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);
}

有关更多信息和交互式练习,请查看 异步编程 教程。

#

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();

创建流

#

与 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 构建精美、原生编译、跨平台应用程序的开源框架,可从单个代码库构建)的一般文档,您将在其中找到有关该语言的深入信息以及入门的实用方法。