目录

Swift 开发者学习 Dart

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

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

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

Mixin 是 Dart 中 Swift 开发人员可能不熟悉的另一个概念。与 Swift 一样,Dart 提供 AOT(提前编译)。但是,Dart 还支持 JIT(即时)编译模式,以帮助完成各种开发方面的工作,例如增量重新编译或调试。更多信息,请查看 Dart 概述

约定和代码风格检查

#

Swift 和 Dart 都具有代码风格检查工具来强制执行标准约定。但是,虽然 Swift 使用 SwiftLint 作为独立工具,但 Dart 具有官方的布局约定,并包含一个代码风格检查器,使遵守约定变得轻而易举。要自定义项目的代码风格检查规则,请按照 自定义静态分析 说明进行操作。(请注意,Dart 和 Flutter 的 IDE 插件也提供此功能。)

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

有关 Dart 约定和代码风格检查的更多信息,请查看 Effective Dart代码风格检查规则

变量

#

与 Swift 相比,在 Dart 中声明和初始化变量略有不同。变量声明始终以变量的类型、 var 关键字或 final 关键字开头。与 Swift 一样,Dart 支持类型推断,其中编译器根据分配给变量的值推断类型:

dart
// 字符串类型变量。
String name = 'Bob';

// 不可变字符串类型变量。
final String name = 'Bob';

// 这与 `String name = 'Bob';` 相同,
// 因为 Dart 将类型推断为 String。
var name = 'Bob';

// 这与 `final String name = 'Bob';` 相同。
final name = 'Bob';

每个 Dart 语句末尾都有一个分号来指示语句的结束。您可以用显式类型替换 Dart 中的 var 。但是,按照惯例, 当分析器可以隐式推断类型时,建议使用 var

dart
// 首先声明一个变量:
String name;
// 稍后初始化变量:
name = 'bob';
// 使用推断一次声明并初始化变量:
var name = 'bob';

上述 Dart 代码的 Swift 等效代码如下所示:

swift
// 首先声明一个变量:
var name: String
// 稍后初始化变量
name = "bob"

// 使用推断一次声明并初始化变量:
var name = "bob"

在 Dart 中,当在声明后初始化没有显式类型的变量时,其类型将被推断为通配符 dynamic 类型。同样,当无法自动推断类型时,它默认为 dynamic 类型, 这将删除所有类型安全 。因此,Dart 代码风格检查器会通过生成警告来阻止这种情况。如果您 打算 允许变量具有任何类型,则最好将其赋值为 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; // 压力单位 (dynes/cm2)
const double atm = 1.01325 * bar; // 标准大气压

在类级别定义的 const 变量需要标记为 static const

dart
class StandardAtmosphere {
  static const bar = 1000000; // 压力单位 (dynes/cm2)
  static const double atm = 1.01325 * bar; // 标准大气压
}

const 关键字不仅用于声明常量变量;它还可以用于创建常量值:

dart
var foo = const ['one', 'two', 'three'];
foo.add('four'); // 错误:foo 包含常量值。
foo = ['apple', 'pear']; // 这允许,因为 foo 本身不是常量。
foo.add('orange'); // 允许,因为 foo 不再包含常量值。

在上面的示例中,您不能更改 const 值(添加、更新或删除给定列表中的元素),但您可以为 foo 分配一个新值。在为 foo 分配新的(非常量)列表后,您可以 添加更新删除 列表的内容。

您还可以将常量值分配给 final 字段。您不能在常量上下文中使用 final 字段,但您可以使用常量。例如:

dart
final foo1 = const [1, 2, 3];
const foo2 = [1, 2, 3]; // 等同于 `const [1, 2, 3]`
const bar2 = foo2; // 正确
const bar1 = foo1; // 编译时错误, `foo1` 不是常量

您还可以定义 const 构造函数,使这些类不可变(不变)并使其能够创建这些类的实例作为编译时常量。更多信息,请查看 const 构造函数

内置类型

#

Dart 在平台库中包含许多类型,例如:

  • 基本值类型,例如
    • 数字( numintdouble
    • 字符串( String
    • 布尔值( bool
    • 值 null( Null
  • 集合
    • 列表/数组( List
    • 集合( Set
    • 映射/字典( Map

更多信息,请查看 Dart 语言教程中的 内置类型

数字

#

Dart 定义了三种数值类型来保存数字:

num
一种通用的 64 位数字类型。
int
一种平台相关的整数。在原生代码中,它是一个 64 位二进制补码整数。在 Web 上,它是一个非分数的 64 位浮点数。
double
一个 64 位浮点数。

与 Swift 不同,没有针对无符号整数的特定类型。

所有这些类型也是 Dart API 中的类。 intdouble 类型都共享 num 作为它们的父类:

Object 是 num 的父类,num 是 int 和 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'; // 没有专门的“Character”类型
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 中定义原生字符串。原生字符串忽略转义字符并包含字符串中存在的任何特殊字符。您可以通过在字符串文字前添加字母 r 来在 Dart 中执行此操作,如下例所示。

dart
// 包含 \n 字符。
final s1 = r'Includes the \n characters.';
// 也包含 \n 字符。
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 强制执行健全的空安全。默认情况下,类型不允许 null 值,除非标记为可为空。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;

// 如果 a 为 null,则将 b 赋值给 a;否则,a 保持不变
a ??= b;
swift
a = a ?? b

! 运算符(也称为“强制解包”)

#

在可以安全地假设可空变量或表达式实际上是非 null 的情况下,可以告诉编译器抑制任何编译时错误。这是使用后缀 ! 运算符完成的,将其作为表达式的后缀。(不要将其与 Dart 的“非”运算符混淆,后者使用相同的符号):

dart
int? a = 5;

int b = a; // 不允许。
int b = a!; // 允许。

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

?. 运算符一样,在访问对象的属性或方法时使用 ! 运算符:

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

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

延迟字段

#

late 关键字可以分配给类字段,以指示它们在稍后某个时刻初始化,同时保持不可为空。这类似于 Swift 的“隐式解包可选值”。这对于在初始化之前从未观察到变量的情况很有用,允许稍后初始化它。不可为空的 late 字段不能在稍后某个时刻赋值为 null。此外,不可为空的 late 字段在初始化之前被观察到时会引发运行时错误,这是您希望在行为良好的应用程序中避免的情况。

dart
// 使用空安全:
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; // 抛出运行时异常,因为
       // “a” 已经被初始化。

函数

#

Swift 使用 main.swift 文件作为应用程序的入口点。Dart 使用 main 函数作为应用程序的入口点。每个程序都必须具有 main 函数才能可执行。例如:

dart
void main() {
  // main 函数是入口点
  print("hello world");
}
swift
// main.swift 文件是入口点
print("hello world")

Dart 不支持 元组 (尽管在 pub.dev 上有 几个元组包 可用)。如果函数需要返回多个值,您可以将它们包装在集合中,例如列表、集合或映射,或者您可以编写一个包装器类,其中可以返回包含这些值的实例。更多信息可以在 集合 部分找到。

异常和错误处理

#

与 Swift 一样,Dart 的函数和方法都支持处理 异常错误 。Dart 的 错误 通常表示程序员的错误或系统故障,例如堆栈溢出。Dart 错误不应该被捕获。另一方面,Dart 的 异常 表示可恢复的故障,并且应该被捕获。例如,在运行时,代码可能会尝试访问流式馈送,但改为接收异常,如果未捕获该异常,则会导致应用程序终止。您可以通过将函数调用包装在 try-catch 块中来管理 Dart 中的异常。

dart
try {
  // 创建音频播放器对象
  audioPlayer = AVAudioPlayer(soundUrl);
            
  // 播放声音
  audioPlayer.play();
}
catch {
  // 无法创建音频播放器对象,记录异常
  print("Couldn't create the audio player for file $soundFilename");
}

类似地,Swift 使用 do-try-catch 块。例如:

swift
do {
  // 创建音频播放器对象
  audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
            
  // 播放声音
  audioPlayer?.play()
}
catch {
  // 无法创建音频播放器对象,记录错误
  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;
}

// 使用必需参数和命名参数调用函数
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
// Swift 等效项
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;
}
// 调用函数时,必须提供 c
multiply(3, 5, c: 2);

第三种参数类型是 可选位置参数 。顾名思义,这些类似于默认位置参数,但在调用函数时可以省略它们。它们必须列在任何必需的位置参数之后,并且不能与命名参数一起使用。

dart
int multiply(int a, int b, [int c = 1, int d = 1]) {
  return a * b * c * d;
}
// 使用必需参数和可选位置参数调用函数。
multiply(3, 5); // 15
multiply(3, 5, 2); // 30
multiply(3, 5, 2, 3); // 90
swift
// Swift 等效项
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);
// 定义一个返回另一个函数的函数
MultiplierFunction multiplyBy(int multiplier) {
  return (int value) {
    return value * multiplier;
  };
}
// 调用返回新函数的函数
MultiplierFunction multiplyByTwo = multiplyBy(2);
// 调用新函数
print(multiplyByTwo(3)); // 6
swift
// 下面 Dart 函数的 Swift 等效项
// 定义一个返回闭包的函数
typealias MultiplierFunction = (Int) -> (Int)

func multiplyBy(_ multiplier: Int) -> MultiplierFunction {
  return { $0 * multiplier} // 返回闭包
}

// 调用返回函数的函数
let multiplyByTwo = multiplyBy(2)
// 调用新函数
print(multiplyByTwo(3)) // 6

匿名函数

#

Dart 中的 匿名函数 的工作方式与 Swift 中的闭包几乎相同,

除了语法上的差异外, 匿名函数 在 Dart 中的工作方式与 Swift 中的闭包几乎相同。与命名函数一样,您可以像任何其他值一样传递匿名函数。例如,您可以将匿名函数存储在变量中,将它们作为参数传递给另一个函数,或从另一个函数返回它们。

Dart 有两种声明匿名函数的方法。第一种方法使用花括号,其工作方式与任何其他函数一样。它允许您使用多行,并且需要一个 return 语句才能返回任何值。

dart
// 多行匿名函数
[1,2,3].map((element) {
  return element * 2;
}).toList(); // [2, 4, 6]
swift
  // Swift 等效匿名函数
  [1, 2, 3].map { $0 * 2 }

另一种方法使用箭头函数,其名称来源于其语法中使用的箭头状符号。当您的函数体只包含一个表达式并且返回值时,您可以使用此简写语法。这省略了对任何括号或 return 语句的需求,因为这些都是隐含的。

dart
// 单行匿名函数
[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++;
}

// 返回一个 `Iterable<int>` ,它迭代
// 0、1、2、3 和 4。
print(listNumbers(5));

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

print(doubleNumbersTo(3)); // 返回一个包含 [0, 0]、[1, 1] 和 [2, 2] 的可迭代对象。

这是一个 同步 生成器函数的示例。您还可以定义 异步 生成器函数,它们返回流而不是可迭代对象。在 并发 部分了解更多信息。

语句

#

本节介绍 Dart 和 Swift 之间语句的异同。

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

#

Dart 中的所有控制流语句的工作方式与其 Swift 对应部分类似,只是语法略有不同。

if

#

与 Swift 不同,Dart 中的 if 语句需要在条件周围使用括号。虽然 Dart 风格指南建议在控制流语句周围使用花括号(如下所示),但是当您有一个没有 else 子句的 if 语句并且整个 if 语句适合在一行上时,您可以根据需要省略花括号。

dart
var a = 1;
// Dart 中需要条件的括号。
if (a == 1) {
  print('a == 1');
} else if (a == 2) {
  print('a == 2');
} else {
  print('a != 1 && a != 2');
}

// 单行 `if` 语句的花括号是可选的。
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); // 结果是 double
assert(5 ~/ 2 == 2); // 结果是 int
assert(5 % 2 == 1); // 余数

a = 0;
b = ++a; // 在 b 获取其值之前递增 a。
assert(a == b); // 1 == 1

a = 0;
b = a++; // 在 b 获取其值之后递增 a。
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? 始终为 true。

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

dart
(person as Employee).employeeNumber = 4204583;

Dart 只有单类型转换运算符,它的作用类似于 Swift 的 as! 运算符。没有 Swift 的 as? 运算符的等效项。

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

如果您不确定对象是否为 T 类型,则使用 is T 进行检查后再使用该对象。

在 Dart 中,类型提升会更新 if 语句范围内局部变量的类型。这也适用于 null 检查。提升仅适用于 局部 变量,不适用于实例变量。

dart
if (person is Employee) {
  person.employeeNumber = 4204583;
}
swift
// Swift 需要将变量进行转换。
if let person = person as? Employee {
  print(person.employeeNumber)
}

逻辑运算符

#

逻辑运算符(例如 AND( && )、OR( || )和 NOT( ! ))在两种语言中都是相同的。例如:

dart
if (!done && (col == 0 || col == 3)) {
  // ...执行某些操作...
}

位运算和移位运算符

#

两种语言中的位运算符大多相同。

例如:

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); // 左移
assert((value >> 4) == 0x02); // 右移
assert((-value >> 4) == -0x03); // 右移 // 结果在 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']; // 初始化列表并指定完整类型
final list2 = <String>['one', 'two', 'three']; // 使用简写类型初始化列表
final list3 = ['one', 'two', 'three']; // Dart 也可以推断类型
swift
var list1: Array<String> = ["one", "two", "three"] // 初始化数组并指定完整类型
var list2: [String] = ["one", "two", "three"] // 使用简写类型初始化数组
var list3 = ["one", "two", "three"] // Swift 也可以推断类型

以下代码示例概述了

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 = {}; // 这也可以。
// final names = {}; // 创建一个空映射,而不是一个集合。

不可修改的集合

#

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 中,您需要在将对象存储在 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'; // 被添加
gifts['second'] = 'turtle doves'; // 被更新

要从 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) {
    // Dart 有更好的方法来做这件事,请继续关注。
    this.x = x;
    this.y = y;
  }
}

// 创建 Point 类的新的实例
Point p = Point(3, 5);

构造函数参数

#

由于在构造函数中编写代码来分配所有类字段通常非常冗余,因此 Dart 有一些语法糖来简化此操作:

dart
class Point {
  double x;
  double y;

  // 设置 x 和 y 的语法糖
  // 在构造函数体运行之前。
  Point(this.x, this.y);
}

// 创建 Point 类的新的实例
Point p = Point(3, 5);

与函数类似,构造函数也可以采用可选的位置参数或命名参数:

dart
class Point {
  ...
  // 带有一个可选的位置参数
  Point(this.x, [this.y = 0]);
  // 带有命名参数
  Point({required this.y, this.x = 0});
  // 带有位置参数和命名参数
  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);

  // 命名构造函数
  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;

  // 此类的主要构造函数。
  Point(this.x, this.y);

  // 委托给主要构造函数。
  Point.alongXAxis(double x) : this(x, 0);
}

工厂构造函数

#

当您不需要创建新的类实例时,可以使用工厂构造函数。一个示例是如果可以返回缓存的实例:

dart
class Logger {
  static final Map<String, Logger> _cache =
    <String, Logger>{};
  
  final String name;
  
  // 返回缓存副本的工厂构造函数,
  // 或如果尚未可用则创建一个新的副本。
  factory Logger(String name)=> _cache[name] ??= Logger._internal(name);
  // 此库中仅使用的私有构造函数
  Logger._internal(this.name);
}

方法

#

在 Dart 和 Swift 中,方法都是函数

为对象提供行为。

dart
void doSomething() { // 这是一个函数
 // 实现..
}

class Example {
 void doSomething() { // 这是一个方法
   // 实现..
 }
}
swift
func doSomething() { // 这是一个函数
  // 实现..
}

class Example {
  func doSomething() { // 这是一个方法
    // 实现..
  }
}

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

  // 定义两个计算属性:right 和 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
 }

 // 定义两个计算属性:right 和 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
// 此类声明为抽象类,因此不能实例化。
abstract class AbstractContainer {
  void updateChildren(); // 抽象方法。

  // 带有默认实现的方法。
  String toString() => "AbstractContainer";
}

隐式接口

#

在 Dart 语言中,每个类都隐式定义一个接口,该接口包含该类的所有实例成员以及它实现的任何接口的实例成员。如果您想创建一个支持类 B 的 API 而无需继承 B 的实现的类 A ,则类 A 应该实现 B 接口。

与 Dart 不同,Swift 类不会隐式定义接口。接口需要明确定义为协议,并由开发人员实现。

一个类可以实现一个或多个接口,然后提供接口所需的方法。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 {
  // 定义构造函数、字段、方法...
}

class Dog extends Animal {
  // 定义构造函数、字段、方法...
}
swift
class Animal {
  // 定义构造函数、字段、方法...
}

class Dog: Animal {
  // 定义构造函数、字段、方法...
}

Mixin

#

Mixin 允许您的代码在类之间共享功能。您可以使用 mixin 的字段和方法在一个类中,将其功能用作类的一部分。一个类可以使用多个 mixin——这在多个类共享相同功能时非常有用——而无需相互继承或共享共同的祖先。

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

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

以下示例显示了如何在 Dart 中实现此行为,以及如何在 Swift 中复制类似的行为:

dart
abstract class Animal {}

// 定义 mixin
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 {}

// 正确的调用
Bat().fly();
Goose().fly();
Goose().walk(); 
Dog().walk();

// 错误的调用
Bat().walk(); // 不使用 Walker mixin
Dog().fly(); // 不使用 Flyer mixin
class Animal {
}
swift
// 定义“mixin”
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 {}

// 正确的调用
Bat().fly();
Goose().fly();
Goose().walk();
Dog().walk();

// 错误的调用
Bat().walk(); // `bat` 没有 `walk` 方法
Dog().fly(); // "dog" 没有 `fly` 方法

class 关键字替换为 mixin 可以防止将 mixin 用作常规类。

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

// 不可能,因为 Walker 不再是类。
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
// 从“path/to/file.dart”导入类型时隐藏“MyExtension”。
import 'path/to/file.dart' hide MyExtension;
// 从“path/to/file.dart”导入类型时仅显示“MyExtension”。
import 'path/to/file.dart' show MyExtension;

// `shout()` 方法仅在此库中可用。
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");
  }
}

// 要使用工厂方法,请使用扩展的名称,而不是类型。
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
// 定义一个使用泛型的方法。
T transform<T>(T param) {
  // 例如,对 `param` 执行一些转换...
  return param;
}

// 调用方法。“str”变量将是
// 字符串类型。
var str = transform('string value');

在这种情况下,将 String 传递给 transform 方法可确保它返回一个 String 。同样,如果提供了 int ,则返回值为 int

通过逗号分隔来定义多个泛型:

dart
// 定义一个具有多个泛型的方法。
T transform<T, Q>(T param1, Q param2) {
  // ...
}
// 使用显式定义的类型调用方法。
transform<int, String>(5, 'string value');
// 类型在可以推断时是可选的。
transform(5, 'string value');

泛型类

#

泛型也可以应用于类。

您可以指定调用构造函数时的类型,这允许您根据特定类型定制可重用类。

在以下示例中, Cache 类用于缓存特定类型:

dart
class Cache<T> {
  T getByKey(String key) {}
  void setByKey(String key, T value) {}
}
// 创建字符串缓存。
// stringCache 的类型为 Cache<String>
var stringCache = Cache<String>();
// 有效,设置字符串值。
stringCache.setByKey('Foo', 'Bar')
// 无效,int 类型与泛型不匹配。
stringCache.setByKey('Baz', 5)

如果省略类型声明,则运行时类型为 Cache<dynamic> ,并且对 setByKey 的两次调用都是有效的。

限制泛型

#

您可以使用泛型来限制代码对使用 extends 的类型族。这确保您的类使用扩展特定类型的泛型类型实例化(与 Swift 类似):

dart
class NumberManager<T extends num> {
  // ...
}
// 有效
var manager = NumberManager<int>();
var manager = NumberManager<double>();
// 无效,String 及其父类都不扩展 num。
var manager = NumberManager<String>();

字面量中的泛型

#

MapSetList 文字可以显式声明泛型类型,这在未推断类型或类型推断错误时非常有用。

例如, List 类具有泛型定义: class List<E> 。泛型类型 E 指的是列表内容的类型。通常,此类型会自动推断,这在 List 类的某些成员类型中使用。(例如,它的第一个 getter 返回类型为 E 的值)。在定义 List 文字时,您可以显式定义泛型类型,如下所示:

dart
var objList = [5, 2.0]; // 类型:List<num> // 自动类型推断
var objList = <Object>[5, 2.0]; // 类型:List<Object> // 显式类型定义
var objSet = <Object>{5, 2.0}; // 集合的工作方式相同

对于 Map 也是如此,它也使用泛型定义其 keyvalue 类型( class Map<K, V> ):

dart
// 自动类型推断
var map = {
  'foo': 'bar'
}; // 类型:Map<String, String>
// 显式类型定义:
var map = <String, Object>{
  'foo': 'bar'
}; // 类型:Map<String, Object>

并发

#

Swift 支持多线程,Dart 支持隔离,隔离类似于轻量级线程,这里将不介绍。每个隔离都具有其自己的事件循环。更多信息,请参见 隔离的工作方式

Future

#

普通 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 ,则此函数的异步对应函数返回一个 Future<String>

Swift 中 async 关键字后放置的 throws 关键字(但仅当函数是可抛出的时),在 Dart 的语法中不存在,因为 Dart 异常和错误不会被编译器检查。相反,如果 async 函数中发生异常,则返回的 Future 将使用该异常失败,然后可以适当地处理该异常。

dart
// 返回字符串的 future,因为该方法是异步的
Future<String> fetchString() async {
  // 通常这里会执行其他一些异步操作。

  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 {
  // 通常这里会执行其他一些异步操作。
  let response = makeNetworkRequest()
  if !response.success {
    throw BadNetwork()
  }

  return "String Value"
}

同样, async 函数中发生的任何异常都可以像处理失败的 Future 一样使用 catchError 方法来处理。

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

与 Swift 一样,Dart 也具有 await 关键字。在 Swift 中, await 仅在调用 async 函数时可用,但 Dart 的 await 可与 Future 类一起使用。因此, await 也适用于 async 函数,因为所有 async 函数都在 Dart 中返回 future。

等待 future 会暂停当前函数的执行,并将控制权返回给事件循环,事件循环可以在 future 完成(返回值或错误)之前处理其他事情。在那之后, await 表达式会评估为该值或抛出该错误。

完成后,将返回 future 的值。您只能在 async 上下文中 await ,就像在 Swift 中一样。

dart
// 我们只能在 async 上下文中 await future。
asyncFunction() async {
  String returnedString = await fetchString();
  print(returnedString); // 'String Value'
}

当等待的 future 失败时,会在包含 await 关键字的行上抛出错误对象。您可以使用常规的 try-catch 块来处理此问题:

dart
// 我们只能在 async 上下文中 await future。
Future<void> asyncFunction() async {
  String? returnedString;
  try {
    returnedString = await fetchString();
  } catch (error) {
    print('Future 在解析之前遇到错误。');
    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('流遇到错误!$err');
  }
  return sum;
}

如果在监听流时发生错误,则会在包含 await 关键字的行上抛出该错误,您可以使用 try-catch 语句来处理它:

dart
try {
  await for (final value in stream) { ... }
} catch (err) {
  print('流遇到错误!$err');
}

这不是监听流的唯一方法:您还可以调用其 listen 方法并提供回调,每当流发出值时都会调用该回调:

dart
Stream<int> stream = ...
stream.listen((int value) {
  print('已发出一个值:$value');
});

listen 方法有一些可选的回调,用于错误处理或流完成时:

dart
stream.listen(
  (int value) { ... },
  onError: (err) {
    print('流遇到错误!$err');
  },
  onDone: () {
    print('流已完成!');
  },
);

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
/// 此块在未拆分时的字符数。
int get length => ...

在文档注释中用方括号括起类型、参数和方法名称。

dart
/// 返回 [a] * [b][int] 乘法结果。
multiply(int a, int b) => a * b;

虽然支持 JavaDoc 风格的文档注释,但您应该避免使用它们并使用 /// 语法。

dart
/**
 * 此块在未拆分时的字符数。
 * (避免使用此语法,请改用 ///。)
 */
int get length => ...

库和可见性

#

Dart 的可见性语义与 Swift 的类似,Dart 库大致相当于 Swift 模块。

Dart 提供两种访问控制级别:公共和私有。方法和变量默认为公共的。私有变量以下划线字符( _ )为前缀,并由 Dart 编译器强制执行。

dart
final foo = '这是一个公共属性';
final _foo = '这是一个私有属性';

String bar() {
  return '这是一个公共方法';
}
String _bar() {
  return '这是一个私有方法';
}

// 公共类
class Foo {
}

// 私有类
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);

  // 可以访问 _Animal 的 _name
  String introduction() {
    return 'Hello my name is $_name';
  }
}

更多信息,请查看 创建包

下一步

#

本指南介绍了 Dart 和 Swift 之间的主要区别。此时,您可能需要参考 DartFlutter (一个使用 Dart 构建精美、原生编译、多平台应用程序的开源框架,可从单个代码库构建)的一般文档,您可以在其中找到有关该语言和实际入门方法的深入信息。