学习 Dart 作为 Swift 开发者
本指南旨在利用您在学习 Dart 时的 Swift 编程知识。它展示了两种语言的关键相似点和差异,并介绍了 Swift 中没有的 Dart 概念。作为一名 Swift 开发者,您可能会觉得 Dart 很熟悉,因为两种语言共享许多概念。
Swift 和 Dart 都支持健全的空安全。两种语言默认都不允许变量为空。
与 Swift 类似,Dart 对 集合、泛型、并发(使用 async/await)和 扩展 具有类似的支持。
Mixin 是 Dart 中 Swift 开发者可能不熟悉的另一个概念。与 Swift 一样,Dart 提供 AOT(提前编译)。但是,Dart 还支持 JIT(即时)编译模式,以帮助进行各种开发方面,例如增量重新编译或调试。有关更多信息,请查看 Dart 概述。
约定和 linting
#Swift 和 Dart 都具有用于强制执行标准约定的 linting 工具。但是,虽然 Swift 将 SwiftLint
作为独立工具,但 Dart 具有官方布局约定,并包含一个 linter 以轻松实现合规性。要自定义项目的 lint 规则,请遵循 自定义静态分析 指示。(请注意,Dart 和 Flutter 的 IDE 插件也提供此功能。)
Dart 还提供了一个代码格式化程序,它可以在从命令行运行 dart format
或通过 IDE 时自动格式化任何 Dart 项目。
有关 Dart 约定和 linting 的更多信息,请查看 Effective Dart 和 Linter 规则。
变量
#在 Dart 中声明和初始化变量与 Swift 相比略有不同。变量声明始终以变量的类型、var
关键字或 final
关键字开头。与 Swift 一样,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
。
// 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 等效代码如下所示
// 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
会导致编译错误。
final String name;
if (b1) {
name = 'John';
} else {
name = 'Jane';
}
let name: String
if (b1) {
name = "John"
} else {
name = "Jane"
}
Const
#除了 final
之外,Dart 还具有 const
关键字。const
的一个好处是它在编译时完全评估,并且在应用程序的生命周期内无法修改。
const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere
在类级别定义的const
变量需要标记为static const
。
class StandardAtmosphere {
static const bar = 1000000; // Unit of pressure (dynes/cm2)
static const double atm = 1.01325 * bar; // Standard atmosphere
}
const
关键字不仅仅用于声明常量变量;它也可以用于创建常量值。
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
字段,但您可以使用该常量。例如
final foo1 = const [1, 2, 3];
const foo2 = [1, 2, 3]; // Equivalent to `const [1, 2, 3]`
const bar2 = foo2; // OK
const bar1 = foo1; // Compile-time error, `foo1` isn't constant
您还可以定义const
构造函数,使这些类不可变(不变)并使您能够在编译时创建这些类的实例。有关更多信息,请查看const 构造函数。
内置类型
#Dart 在平台库中包含了许多类型,例如
- 基本值类型,例如
- 数字 (
num
,int
,double
) - 字符串 (
String
) - 布尔值 (
bool
) - 空值 (
Null
)
- 数字 (
- 集合
- 列表/数组 (
List
) - 集合 (
Set
) - 映射/字典 (
Map
)
- 列表/数组 (
有关更多信息,请查看 Dart 语言之旅中的内置类型。
数字
#Dart 定义了三种用于保存数字的数字类型
num
- 一种通用的 64 位数字类型。
int
- 平台相关的整数。在原生代码中,它是一个 64 位二进制补码整数。在 Web 上,它是一个非分数的 64 位浮点数。
double
- 一个 64 位浮点数。
与 Swift 不同,没有针对无符号整数的特定类型。
所有这些类型也是 Dart API 中的类。int
和 double
类型都以 num
作为其父类
由于数字值在技术上是类实例,因此它们具有公开自身实用程序函数的便利性。因此,例如,int
可以转换为 double
,如下所示
int intVariable = 3;
double doubleVariable = intVariable.toDouble();
在 Swift 中使用专门的初始化程序完成相同的事情
var intVariable: Int = 3
var doubleVariable: Double = Double(intVariable)
对于字面量值,Dart 会自动将整数字面量转换为 double
值。以下代码完全没问题
double doubleValue = 3;
与 Swift 不同,在 Dart 中,可以使用等号 (==
) 运算符比较整数和 double 值,如下所示
int intVariable = 3;
double doubleVariable = 3.0;
print(intVariable == doubleVariable); // true
此代码输出 true
。但是,在 Dart 中,Web 平台和原生平台的底层实现数字不同。Dart 中的数字 页面详细介绍了这些差异,并展示了如何编写代码以消除这些差异的影响。
字符串
#与 Swift 一样,Dart 使用 String
类型表示一系列字符,但 Dart 不支持表示单个字符的 Character
类型。可以使用单引号或双引号定义 String
,但是,建议使用单引号。
String c = 'a'; // There isn't a specialized "Character" type
String s1 = 'This is a String';
String s2 = "This is also a String";
let c: Character = "a"
let s1: String = "This is a String"
let s2: String = "This is also a String"
转义特殊字符
#在 Dart 中转义特殊字符与 Swift(以及大多数其他语言)类似。要包含特殊字符,请使用反斜杠字符对其进行转义。
以下代码展示了一些示例
final singleQuotes = 'I\'m learning Dart'; // I'm learning Dart
final doubleQuotes = "Escaping the \" character"; // Escaping the " character
final unicode = '\u{1F60E}'; // 😎, Unicode scalar U+1F60E
请注意,也可以直接使用 4 位十六进制值(例如,\u2665
),但是,花括号也可以使用。有关使用 Unicode 字符的更多信息,请查看 Dart 语言之旅中的Runes 和 grapheme 簇。
字符串连接和多行声明
#在 Dart 和 Swift 中,您都可以转义多行字符串中的换行符,这使您可以使源代码更易于阅读,但仍将 String
输出为单行。Dart 有几种方法可以定义多行字符串
使用隐式字符串连接:任何相邻的字符串字面量都会自动连接,即使它们跨越多行
dartfinal s1 = 'String ' 'concatenation' " even works over line breaks.";
使用多行字符串字面量:当在字符串两侧使用三个引号(单引号或双引号)时,字面量可以跨越多行
dartfinal s2 = '''You can create multiline strings like this one.'''; final s3 = """This is also a multiline string.""";
Dart 还支持使用
+
运算符连接字符串。这适用于字符串字面量和字符串变量dartfinal name = 'John'; final greeting = 'Hello ' + name + '!';
字符串插值
#使用 ${<expression>}
语法将表达式插入字符串字面量中。Dart 通过允许在表达式为单个标识符时省略花括号来扩展此功能
var food = 'bread';
var str = 'I eat $food'; // I eat bread
var str = 'I eat ${bakery.bestSeller}'; // I eat bread
在 Swift 中,您可以通过将变量或表达式用括号括起来并在前面加上反斜杠来实现相同的结果。
let s = "string interpolation"
let c = "Swift has \(s), which is very handy."
原始字符串
#与 Swift 一样,您可以在 Dart 中定义原始字符串。原始字符串忽略转义字符,并包含字符串中存在的任何特殊字符。您可以在 Dart 中通过在字符串文字前加上字母 r
来实现,如下例所示。
// 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.
""";
let s1 = #"Includes the \n characters."#
let s2 = #"""
The \n characters are also included
when using raw multiline strings.
"""#
相等性
#与 Swift 一样,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
检查字符串是否为空。还有一些其他便捷方法,例如 toUpperCase
和 toLowerCase
。有关更多信息,请查看 Dart 语言之旅中的 字符串。
布尔值
#布尔值在 Dart (bool
) 和 Swift (Bool
) 中都表示二进制值。
空安全
#Dart 强制执行健全的空安全。默认情况下,类型不允许空值,除非标记为可空。Dart 在类型末尾使用问号 (?
) 来表示这一点。这与 Swift 的可选类型类似。
空感知运算符
#Dart 支持多个运算符来处理可空性。空合并运算符 (??
) 和可选链运算符 (?.
) 在 Dart 中可用,并且与 Swift 中的操作相同。
a = a ?? b;
let str: String? = nil
let count = str?.count ?? 0
此外,Dart 提供了级联运算符的空安全版本 (?..
)。当目标表达式解析为 null
时,此运算符会忽略任何操作。Dart 还提供了空赋值运算符 (??=
),而 Swift 没有。如果具有可空类型的变量的当前值为 null
,则此运算符会将值分配给该变量。表示为 a ??= b;
,它相当于以下代码的简写
a = a ?? b;
// Assign b to a if a is null; otherwise, a stays the same
a ??= b;
a = a ?? b
! 运算符(也称为“强制解包”)
#在可以安全地假设可空变量或表达式实际上是非空的情况下,可以告诉编译器抑制任何编译时错误。这可以通过使用后缀!
运算符来完成,将其作为表达式的后缀放置。(不要将其与 Dart 的“非”运算符混淆,后者使用相同的符号)
int? a = 5;
int b = a; // Not allowed.
int b = a!; // Allowed.
在运行时,如果a
变成空,则会发生运行时错误。
与?.
运算符类似,在访问对象上的属性或方法时使用!
运算符
myObject!.someProperty;
myObject!.someMethod();
如果myObject
在运行时为null
,则会发生运行时错误。
延迟字段
#late
关键字可以分配给类字段,以指示它们在稍后初始化,同时保持非空。这类似于 Swift 的“隐式解包可选”。这对于在变量在初始化之前从未被观察到的情况下很有用,允许它在稍后初始化。非空late
字段不能在稍后分配空值。此外,非空late
字段在初始化之前被观察到时会抛出运行时错误,这是您希望在行为良好的应用程序中避免的场景。
// 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
关键字将初始化与初始化器结合使用时延迟初始化
class Weather {
late int _temperature = _readThermometer();
}
在这种情况下,_readThermometer()
仅在第一次访问该字段时运行,而不是在初始化时运行。
Dart 中的另一个优势是使用late
关键字延迟final
变量的初始化。虽然在将final
变量标记为late
时不必立即初始化它,但它仍然只能初始化一次。第二次赋值会导致运行时错误。
late final int a;
a = 1;
a = 2; // Throws a runtime exception because
// "a" is already initialized.
函数
#Swift 使用main.swift
文件作为应用程序的入口点。Dart 使用main
函数作为应用程序的入口点。每个程序都必须有一个main
函数才能执行。例如
void main() {
// main function is the entry point
print("hello world");
}
// main.swift file is the entry point
print("hello world")
Dart 不支持元组
(尽管在 pub.dev 上有 几个元组包 可用)。如果函数需要返回多个值,您可以将它们包装在集合中,例如列表、集合或映射,或者您可以编写一个包装类,其中可以返回包含这些值的实例。有关此的更多信息可以在关于 集合 和 类 的部分中找到。
异常和错误处理
#与 Swift 一样,Dart 的函数和方法支持处理 异常 和 错误。Dart 错误 通常代表程序员错误或系统故障,例如堆栈溢出。Dart 错误不应该被捕获。另一方面,Dart 异常 代表可恢复的故障,并且旨在被捕获。例如,在运行时,代码可能会尝试访问流式馈送,但反而收到异常,如果未捕获,会导致应用程序终止。您可以通过将函数调用包装在try-catch
块中来管理 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
块。例如
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
块。有关更多信息,请参阅 Error
和 Exception
类的文档。
参数
#与 Swift 类似,Dart 在其函数中支持命名参数。但是,与 Swift 不同,这些参数在 Dart 中不是默认的。Dart 中的默认参数类型是位置参数。
int multiply(int a, int b) {
return a * b;
}
Swift 中的等效项在参数前加上下划线以消除对参数标签的需求。
func multiply(_ a: Int, _ b: Int) -> Int {
return a * b
}
在 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
// The Swift equivalent
func multiply(_ a: Int, _ b: Int, c: Int = 1, d: Int = 1) -> Int {
return a * b * c * d
}
命名参数必须包含以下之一
- 默认值
- 类型末尾的
?
将类型设置为可空 - 变量类型之前的关键字
required
要了解有关可空类型的更多信息,请查看 空安全。
要在 Dart 中将命名参数标记为必需,您必须在它前面加上 required
关键字
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);
第三种参数类型是可选位置参数。顾名思义,这些参数类似于默认位置参数,但在调用函数时可以省略。它们必须列在任何必需的位置参数之后,并且不能与命名参数一起使用。
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
// The Swift equivalent
func multiply(_ a: Int, _ b: Int, _ c: Int = 1, _ d: Int = 1) -> Int {
return a * b * c * d
}
与命名参数一样,可选位置参数必须具有默认值或可空类型。
一等函数
#与 Swift 一样,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
// 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 语句才能返回任何值。
// Multi line anonymous function
[1,2,3].map((element) {
return element * 2;
}).toList(); // [2, 4, 6]
// Swift equivalent anonymous function
[1, 2, 3].map { $0 * 2 }
另一种方法使用箭头函数,以其语法中使用的箭头状符号命名。当您的函数体只包含一个表达式并且返回值时,可以使用这种简写语法。这省略了对任何括号或 return 语句的需求,因为这些都是隐含的。
// Single-line anonymous function
[1,2,3].map((element) => element * 2).toList(); // [2, 4, 6]
箭头语法或花括号之间的选择适用于任何函数,而不仅仅是匿名函数。
multiply(int a, int b) => a * b;
multiply(int a, int b) {
return a * b;
}
生成器函数
#Dart 支持 生成器函数,这些函数返回一个可迭代的项目集合,这些项目是延迟构建的。使用 yield
关键字将项目添加到最终的可迭代对象中,或使用 yield*
添加整个项目集合。
以下示例展示了如何编写基本的生成器函数
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 语句都放在一行上,您可以选择省略花括号。
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');
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
示例
var list = [0, 1, 2, 3, 4];
for (var i in list) {
print(i);
}
let array = [0, 1, 2, 3, 4]
for i in array {
print(i)
}
Dart 没有for-in
循环的任何特殊语法,允许您遍历映射,就像 Swift 对字典一样。要实现类似的效果,您可以将映射的条目提取为Iterable
类型。或者,您可以使用Map.forEach
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');
});
var dict:[String:Int] = [
"Foo":1,
"Bar":2
]
for (key, value) in dict {
print("\(key),\(value)")
}
运算符
#与 Swift 不同,Dart 不允许添加新的运算符,但它允许您使用 operator 关键字重载现有的运算符。例如
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);
}
}
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 等效项以相同的方式运行。例如
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 T | expr as! T expr as? T |
如果对象具有指定类型,则为真 | expr is T | expr is T |
如果对象没有指定类型,则为真 | expr is! T | !(expr is T) |
如果 obj
是 T
指定类型的子类型,则 obj is T
的结果为 true
。例如,obj is Object?
始终为真。
使用类型转换运算符将对象转换为特定类型,当且仅当您确定对象是该类型时。例如
(person as Employee).employeeNumber = 4204583;
Dart 只有一个单类型转换运算符,它类似于 Swift 的 as!
运算符。没有 Swift 的 as?
运算符的等效项。
(person as! Employee).employeeNumber = 4204583;
如果您不确定对象是否为 T
类型,则使用 is T
检查,然后再使用该对象。
在 Dart 中,类型提升会更新 if
语句作用域内的局部变量的类型。这对于空检查也适用。提升仅适用于局部变量,不适用于实例变量。
if (person is Employee) {
person.employeeNumber = 4204583;
}
// Swift requires the variable to be cast.
if let person = person as? Employee {
print(person.employeeNumber)
}
逻辑运算符
#逻辑运算符(如 AND (&&
)、OR (||
) 和 NOT (!
))在两种语言中都是相同的。例如
if (!done && (col == 0 || col == 3)) {
// ...Do something...
}
按位和移位运算符
#按位运算符在两种语言中基本相同。
例如
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
语句的表达式
final displayLabel = canAfford ? 'Please pay below' : 'Insufficient funds';
let displayLabel = canAfford ? "Please pay below" : "Insufficient funds"
级联 (.. 运算符)
#与 Swift 不同,Dart 支持使用级联运算符进行级联。这允许您在一个对象上链接多个方法调用或属性赋值。
以下示例展示了设置多个属性的值,然后在一个新构造的对象上调用多个方法,所有这些都在使用级联运算符的单个链中完成
Animal animal = Animal()
..name = 'Bob'
..age = 5
..feed()
..walk();
print(animal.name); // "Bob"
print(animal.age); // 5
var animal = Animal()
animal.name = "Bob"
animal.age = 5
animal.feed()
animal.walk()
print(animal.name)
print(animal.age)
集合
#本节介绍 Swift 中的一些集合类型,以及它们与 Dart 中的等效类型之间的比较。
列表
#List
字面量在 Dart 中的定义方式与 Swift 中的数组相同,使用方括号并用逗号分隔。两种语言之间的语法非常相似,但是有一些细微的差别,如以下示例所示
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
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
运算符从列表中检索值。
final fruits = ['apple', 'orange', 'pear'];
final fruit = fruits[1];
要将值添加到列表的末尾,请使用 add
方法。要添加另一个 List
,请使用 addAll
方法。
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
相同。
final fruits = List<String>.unmodifiable(['apple', 'orange', 'pear']);
let fruits = ["apple", "orange", "pear"]
扩展运算符
#Dart 中的另一个有用功能是 **扩展运算符** (...
) 和 **空感知扩展运算符** (...?
),它们提供了一种简洁的方式将多个值插入集合中。
例如,您可以使用扩展运算符 (...
) 将列表中的所有值插入另一个列表,如下所示。
final list = [1, 2, 3];
final list2 = [0, ...list]; // [ 0, 1, 2, 3 ]
assert(list2.length == 4);
虽然 Swift 没有扩展运算符,但与上面第 2 行等效的是以下内容。
let list2 = [0] + list
如果扩展运算符右侧的表达式可能是 null
,您可以使用空感知扩展运算符 (...?
) 来避免异常。
List<int>? list;
final list2 = [0, ...?list]; //[ 0 ]
assert(list2.length == 1);
let list2 = [0] + list ?? []
集合
#Dart 和 Swift 都支持使用字面量定义 Set
。集合的定义方式与列表相同,但使用花括号而不是方括号。集合是无序的集合,只包含唯一项。这些项目的唯一性是使用哈希码实现的,这意味着对象需要哈希值才能存储在 Set
中。每个 Dart 对象都包含一个哈希码,而在 Swift 中,您需要在对象可以存储在 Set
中之前显式地应用 Hashable
协议。
以下代码片段展示了在 Dart 和 Swift 中初始化 Set
的区别。
final abc = {'a', 'b', 'c'};
var abc: Set<String> = ["a", "b", "c"]
在 Dart 中,您不能通过指定空花括号 ({}
) 来创建空集合;这会导致创建一个空的 Map
。要创建空 Set
,请在 {}
声明之前添加类型参数,或将 {}
赋值给类型为 Set
的变量。
final names = <String>{};
Set<String> alsoNames = {}; // This works, too.
// final names = {}; // Creates an empty map, not a set.
不可修改
#与 List
类似,Set
也有一个不可修改的版本。例如
final abc = Set<String>.unmodifiable(['a', 'b', 'c']);
let abc: Set<String> = ["a", "b", "c"]
映射
#Dart 中的 Map
类型可以与 Swift 中的 Dictionary
类型进行比较。两种类型都关联键和值。这些键和值可以是任何类型的对象。每个键只出现一次,但您可以多次使用相同的值。
在这两种语言中,字典都基于哈希表,这意味着键需要是可哈希的。在 Dart 中,每个对象都包含一个哈希值,而在 Swift 中,您需要在将对象存储在 Dictionary
中之前显式地应用 Hashable
协议。
以下是一些使用字面量创建的简单 Map
和 Dictionary
示例。
final gifts = {
'first': 'partridge',
'second': 'turtle doves',
'fifth': 'golden rings',
};
final nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
let gifts = [
"first": "partridge",
"second": "turtle doves",
"fifth": "golden rings",
]
let nobleGases = [
2: "helium",
10: "neon",
18: "argon",
]
以下代码示例概述了您可以在 Dart Map
上执行的基本操作。第一个示例展示了如何使用 key
运算符从 Map
中检索值。
final gifts = {'first': 'partridge'};
final gift = gifts['first']; // 'partridge'
使用 containsKey
方法检查键是否已存在于 Map
中。
final gifts = {'first': 'partridge'};
assert(gifts.containsKey('fifth')); // false
使用索引赋值运算符 ([]=
) 添加或更新 Map
中的条目。如果 Map
中尚未包含该键,则添加该条目。如果键存在,则更新条目的值。
final gifts = {'first': 'partridge'};
gifts['second'] = 'turtle'; // Gets added
gifts['second'] = 'turtle doves'; // Gets updated
要从 Map
中删除条目,请使用 remove
方法,要删除满足给定测试的所有条目,请使用 removeWhere
方法。
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
关键字曾经是创建新类实例所必需的,现在是可选的,不再推荐。
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 有一些语法糖来简化此操作。
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);
与函数类似,构造函数也可以接受可选的位置参数或命名参数。
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
直接设置的任何字段之后运行,但在构造函数体之前运行。
class Point {
...
Point(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
...
}
初始化列表是使用断言的好地方。
命名构造函数
#与 Swift 不同,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
。
class ImmutablePoint {
final double x, y;
const ImmutablePoint(this.x, this.y);
}
这也意味着您可以将该类用作常量值,使该对象成为编译时常量。
const ImmutablePoint origin = ImmutablePoint(0, 0);
构造函数重定向
#您可以从其他构造函数调用构造函数,例如,为了防止代码重复或为参数添加额外的默认值。
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);
}
工厂构造函数
#当您不需要创建新的类实例时,可以使用工厂构造函数。一个例子是,如果可以返回缓存的实例。
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 中,方法都是为对象提供行为的函数。
void doSomething() { // This is a function
// Implementation..
}
class Example {
void doSomething() { // This is a method
// Implementation..
}
}
func doSomething() { // This is a function
// Implementation..
}
class Example {
func doSomething() { // This is a method
// Implementation..
}
}
Getter 和 Setter
#您可以通过在字段名前添加 get
或 set
关键字来定义 getter 和 setter。您可能还记得,每个实例字段都有一个隐式 getter,以及一个 setter(如果适用)。在 Swift 中,语法略有不同,因为 get
和 set
关键字需要在属性语句中定义,并且只能定义为语句,而不是表达式。
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;
}
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
修饰符。以下示例声明了一个抽象类,该类具有一个抽象方法和一个包含默认实现的方法。
// 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 都以不同的方式实现接口。例如
abstract class Animal {
int getLegs();
void makeNoise();
}
class Dog implements Animal {
@override
int getLegs() => 4;
@override
void makeNoise() => print('Woof woof');
}
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` 引用超类。
abstract class Animal {
// Define constructors, fields, methods...
}
class Dog extends Animal {
// Define constructors, fields, methods...
}
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 中复制类似的行为。
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 {
}
// 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
用 `mixin` 替换 `class` 关键字可以防止 mixin 被用作普通类。
mixin Walker {
walk() => print('Walks legs');
}
// Impossible, as Walker is no longer a class.
class Bat extends Walker {}
由于你可以使用多个 mixin,当它们用于同一个类时,它们的方法或字段可能会相互重叠。它们甚至可能与使用它们的类或该类的超类重叠。为了解决这个问题,Dart 将它们堆叠在一起,因此它们添加到类的顺序很重要。
举个例子
class Bird extends Animal with Consumer, Flyer {
当在一个 Bird
实例上调用方法时,Dart 从堆栈底部开始,首先检查它自己的类 Bird
,该类优先于其他实现。如果 Bird
没有实现,Dart 会继续向上移动堆栈,接下来是 Flyer
,然后是 Consumer
,直到找到实现。如果找不到实现,最后会检查父类 Animal
。
扩展方法
#与 Swift 一样,Dart 提供了扩展方法,允许您为现有类型添加功能,具体来说是方法、getter、setter 和运算符。Dart 和 Swift 中创建扩展的语法非常相似。
extension <name> on <type> {
(<member definition>)*
}
extension <type> {
(<member definition>)*
}
例如,以下来自 Dart SDK 的 String
类的扩展允许解析整数
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
}
print('21'.parseInt() * 2); // 42
extension String {
func parseInt() -> Int {
return Int(self) ?? 0
}
}
print("21".parseInt() * 2) // 42
虽然扩展在 Dart 和 Swift 中很相似,但有一些关键区别。以下部分介绍了最重要的区别,但请查看 扩展方法 以获取完整概述。
命名扩展
#虽然不是强制性的,但您可以在 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 中,您不能使用扩展为类添加额外的构造函数,但您可以添加一个创建该类型实例的静态扩展方法。请考虑以下示例
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
注解来指示您有意覆盖成员
class Animal {
void makeNoise => print('Noise');
}
class Dog implements Animal {
@override
void makeNoise() => print('Woof woof');
}
在 Swift 中,您将 override
关键字添加到方法定义中
class Animal {
func makeNoise() {
print("Noise")
}
}
class Dog: Animal {
override func makeNoise() {
print("Woof woof");
}
}
泛型
#与 Swift 一样,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
。
通过逗号分隔定义多个泛型。
// 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
类用于缓存特定类型。
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)。
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
字面量时,您可以显式定义泛型类型,如下所示
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
也是如此,它也使用泛型定义其 key
和 value
类型(class Map<K, V>)
。
// 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 支持隔离,它们类似于轻量级线程,这里不作介绍。每个隔离都拥有自己的事件循环。有关更多信息,请参阅 隔离的工作原理。
期货
#普通 Swift 没有 Dart 的 Future
的对应物。但是,如果您熟悉 Apple 的 Combine 框架或 RxSwift 或 PromiseKit 等第三方库,您可能仍然知道此对象。
简而言之,future 表示异步操作的结果,该结果将在稍后时间可用。如果您有一个返回 String
的 Future
(Future<String>
)而不是 String
的函数,您基本上接收的是一个可能在将来某个时间存在的 value。
当 future 的异步操作完成时,该 value 将变为可用。但是,您应该记住,future 也可以用错误而不是 value 完成。
例如,如果您发出 HTTP 请求,并立即收到 future 作为响应。结果进来后,future 用该 value 完成。但是,如果 HTTP 请求失败,例如由于互联网连接中断,future 将用错误而不是 value 完成。
期货也可以手动创建。创建期货最简单的方法是定义和调用一个async
函数,这将在下一节中讨论。当您需要一个值为Future
的值时,您可以使用Future
类轻松地将其转换为一个Future
。
String str = 'String Value';
Future<String> strFuture = Future<String>.value(str);
异步/等待
#虽然期货不是 vanilla Swift 的一部分,但 Dart 中的async/await
语法有 Swift 的对应部分,并且以类似的方式工作,尽管没有Future
对象。
与 Swift 一样,函数可以标记为async
。Dart 中的区别在于任何async
函数总是隐式返回一个Future
。例如,如果您的函数返回一个String
,则此函数的异步对应项返回一个Future<String>
。
throws
关键字放在 Swift 中的async
关键字之后(但仅当函数可抛出时),在 Dart 的语法中不存在,因为 Dart 异常和错误不会被编译器检查。相反,如果异步函数中发生异常,则返回的Future
将使用该异常失败,然后可以适当地处理该异常。
// 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';
}
此异步函数可以按如下方式调用
String stringFuture = await fetchString();
print(str); // "String Value"
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 的await
与Future
类一起使用。因此,await
也适用于async
函数,因为所有async
函数在 Dart 中都返回期货。
等待期货会挂起当前函数的执行并将控制权返回给事件循环,事件循环可以处理其他事情,直到期货完成,无论是带有值还是错误。在那之后的一段时间,await
表达式将评估为该值或抛出该错误。
当它完成时,将返回未来的值。你只能在 `async` 上下文中使用 `await`,就像 Swift 一样。
// We can only await futures within an async context.
asyncFunction() async {
String returnedString = await fetchString();
print(returnedString); // 'String Value'
}
当等待的未来失败时,会在带有 `await` 关键字的行上抛出一个错误对象。你可以使用常规的 `try-catch` 块来处理它。
// 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 的流应该很熟悉。
对于那些不熟悉 `Streams`、`AsyncSequence`、`Publishers` 或 `Observables` 的人来说,这个概念如下:`Stream` 本质上就像一个 `Future`,但它有多个值随着时间的推移而分布,就像一个事件总线。可以监听流,以接收值或错误事件,并且当不再发送事件时,可以关闭流。
监听
#要监听流,你可以在 `async` 上下文中将流与 `for-in` 循环结合起来。`for` 循环会为每个发出的项目调用回调方法,并在流完成或出错时结束。
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` 语句来处理它。
try {
await for (final value in stream) { ... }
} catch (err) {
print('Stream encountered an error! $err');
}
这不是监听流的唯一方法:你还可以调用它的 `listen` 方法并提供一个回调,该回调在流发出值时被调用。
Stream<int> stream = ...
stream.listen((int value) {
print('A value has been emitted: $value');
});
`listen` 方法有一些可选的回调,用于错误处理或流完成时。
stream.listen(
(int value) { ... },
onError: (err) {
print('Stream encountered an error! $err');
},
onDone: () {
print('Stream completed!');
},
);
`listen` 方法返回一个 `StreamSubscription` 实例,你可以使用它来停止监听流。
StreamSubscription subscription = stream.listen(...);
subscription.cancel();
创建流
#与未来一样,你有几种不同的方法来创建流。两种最常见的方法是使用异步生成器或 `SteamController`。
异步生成器
#异步生成器函数的语法与同步生成器函数相同,但使用async*
关键字代替sync*
,并返回Stream
而不是Iterable
。这种方法类似于Swift中的AsyncStream
结构体。
在异步生成器函数中,yield
关键字将给定值发射到流中。然而,yield*
关键字与流而不是其他可迭代对象一起使用。这允许来自其他流的事件被发射到此流。在以下示例中,该函数只有在新发射的流完成之后才会继续。
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 一样,您可以使用三个斜杠 (///
) 来定义文档注释。
/// The number of characters in this chunk when unsplit.
int get length => ...
在文档注释中,用方括号将类型、参数和方法名称括起来。
/// Returns the [int] multiplication result of [a] * [b].
multiply(int a, int b) => a * b;
虽然支持 JavaDoc 样式的文档注释,但您应该避免使用它们,而应使用 ///
语法。
/**
* The number of characters in this chunk when unsplit.
* (AVOID USING THIS SYNTAX, USE /// INSTEAD.)
*/
int get length => ...
库和可见性
#Dart 的可见性语义与 Swift 的类似,Dart 库大致相当于 Swift 模块。
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` 个模块。
属于同一个库的所有文件都可以访问该库中的所有私有对象。但出于安全原因,一个文件仍然需要允许特定文件访问其私有对象,否则任何文件(即使来自项目外部)都可以注册到你的库并访问可能敏感的数据。换句话说,私有对象不会跨库共享。
library animals;
part 'parrot.dart';
class _Animal {
final String _name;
_Animal(this._name);
}
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 之间的主要区别。此时,你可能需要考虑转到 Dart 或 Flutter(一个使用 Dart 构建精美、原生编译、跨平台应用程序的开源框架,从单个代码库构建)的通用文档,在那里你可以找到有关该语言的深入信息以及实用的入门方法。