Flutter开发(1)—— Dart vs Swift
原文链接: https://medium.com/coding-with-flutter/dart-vs-swift-a-comparison-6491e945dc17
发表日期: 2018.12.27
Dart和Swift是我最喜欢的两种编程语言。我已经在商业和开源代码中广泛使用了它们。
- 突出显示两者之间的差异。
- 为开发人员从一种语言转移到另一种语言(或同时使用两种语言)提供参考。
一些背景:
- Dart支持Flutter(Google的框架),该框架用于从单个代码库构建漂亮的本机应用程序。
- Swift在iOS,macOS,tvOS和watchOS上为Apple的SDK提供支持。
在两种语言(自Dart 2.1和Swift 4.2起)的主要功能之间进行了以下比较。由于深入讨论每个功能超出了本文的范围,因此我在适当的地方提供了一些参考,以供进一步阅读。
目录
- 比较表
- 变量
- 类型推断
- 可变/不可变变量
- 函数
- 命名和未命名参数
- 可选参数和默认参数
- 闭包
- 元组
- 控制流
- 集合(Arrays, Sets, Maps)
- 可空类型和可选类型
- 类
- 继承
- 属性
- 协议/抽象类
- Mixins
- 扩展
- 枚举
- 结构体
- 错误处理
- 泛型
- 访问控制
- 异步编程:Futures
- 异步编程:Streams
- 内存管理
- 编译与执行
- 其他未涵盖的功能
- Dart缺少我最喜欢的Swift功能
- Swift缺少我最喜欢的Dart功能
- 结论
比较表
变量
变量声明语法在Dart中看起来像这样:
String name;
int age;
double height;
而在Swift中像这样:
var name: String
var age: Int
var height: Double
变量初始化在Dart中如下所示:
var name = 'Andrea';
var age = 34;
var height = 1.84;
在Swift中则这样:
var name = "Andrea"
var age = 34
var height = 1.84
在此示例中,不需要类型注释。这是因为两种语言都可以从赋值右侧的表达式推断类型。
类型推断
类型推断意味着我们可以在Dart中编写以下内容:
var arguments = {'argA': 'hello', 'argB': 42}; // Map<String, Object>
类型arguments
由编译器自动解析。
在Swift中,可以这样写:
var arguments = [ "argA": "hello", "argB": 42 ] // [ String : Any ]
更多细节
引用Dart文档:
分析器可以推断字段,方法,局部变量和大多数通用类型参数的类型。当分析器没有足够的信息来推断特定类型时,它将使用动态类型。
引用Swift文档:
Swift广泛使用类型推断,允许您在代码中省略许多变量和表达式的类型或部分类型。例如,
var x: Int = 0
您可以不写Int类型:var x = 0
,编译器可以正确地推断出名x
为的type值类型为Int
。
动态类型
可以使用Dart中的dynamic
关键字和Swift中的Any
关键字声明可以是任何类型的变量。
读取JSON等数据时,通常使用动态类型。
可变/不可变变量
变量可以声明为可变的或不可变的。
为了声明可变变量,两种语言都使用var关键字。
var a = 10; // int (Dart)
a = 20; // ok
var a = 10 // Int (Swift)
a = 20 // ok
为了声明不可变的变量,Dart使用final,而Swift使用let。
final a = 10;
a = 20; // 'a': a final variable, can only be set once.
let a = 10
a = 20 // Cannot assign to value: 'a' is a 'let' constant
注意:Dart文档定义了两个关键字final和const,它们的工作方式如下:
如果您不打算更改变量值,请使用 final 或 const,而不是 var 或类型。final 变量只能设置一次;const 变量是编译时常量。(const 变量是隐式 final。)final 顶层类型变量或类变量在第一次使用时被初始化。
在 Dart 网站上的这篇文章中可以找到进一步的解释:
final 意味着一次赋值。final 变量或字段必须具有 initializer。 一旦赋值,就不能改变 final 变量的值。
简而言之:使用final在Dart中定义不可变变量。
在Swift中,我们用let声明常量。
常量声明将常量命名值引入程序。常量声明使用let关键字声明,并具有以下形式:
let constant name: type = expression
常量声明定义了常量名称和初始值设定项表达式的值之间的不可变绑定;设置常数的值后,将无法更改它。
阅读更多:Swift声明。
函数
函数在Swift和Dart中都是一等公民。
这意味着就像对象一样,函数可以作为参数传递,保存为属性或作为结果返回。
作为初始比较,我们可以看到如何声明不带参数的函数。
在Dart中,返回类型位于方法名称之前:
void foo();
int bar();
在Swift中,我们使用-> T
符号作为后缀。如果没有返回值(Void
),则不需要这样做:
func foo()
func bar() -> Int
阅读更多:
命名和未命名参数
两种语言都支持命名和未命名参数。
在Swift中,参数默认命名:
func foo(name: String, age: Int, height: Double)
foo(name: "Andrea", age: 34, height: 1.84)
在Dart中,我们使用花括号{}
定义命名参数:
void foo({String name, int age, double height});
foo(name: 'Andrea', age: 34, height: 1.84);
在 Swift 中,我们使用下划线_
作为外部参数来定义未命名的参数:
func foo(_ name: String, _ age: Int, _ height: Double)
foo("Andrea", 34, 1.84)
在 Dart 中,我们通过省略花括号{}
来定义未命名的参数:
void foo(String name, int age, double height);
foo('Andrea', 34, 1.84);
阅读更多:Swift中的函数参数标签和参数名称。
可选参数和默认参数
两种语言均支持默认参数。
在Swift中,您可以为函数中的任何参数定义默认值,方法是在该参数的类型之后为该参数分配一个值。如果定义了默认值,则可以在调用函数时忽略该参数。
func foo(name: String, age: Int = 0, height: Double = 0.0)
foo(name: "Andrea", age: 34) // name: "Andrea", age: 34, height: 0.0
阅读更多:Swift中的默认参数值。
在Dart中,可选参数可以是位置参数或名称,但不能同时使用。
// positional optional parameters
void foo(String name, [int age = 0, double height = 0.0]);
foo('Andrea', 34); // name: 'Andrea', age: 34, height: 0.0
// named optional parameters
void foo({String name, int age = 0, double height = 0.0});
foo(name: 'Andrea', age: 34); // name: 'Andrea', age: 34, height: 0.0
了解更多:Dart中的可选参数。
闭包
作为一流的对象,函数可以作为参数传递给其他函数,也可以分配给变量。
在这种情况下,函数也称为闭包。
这是一个Dart函数示例,它使用闭包来打印每个项目的索引和内容,从而遍历项目列表:
final list = ['apples', 'bananas', 'oranges'];
list.forEach((item) => print('${list.indexOf(item)}: $item'));
闭包使用一个参数(item
),输出该项目的索引和值,并且不返回任何值。
请注意箭头符号(=>
)的使用。这可以代替花括号内的单个return语句:
list.forEach((item) { print('${list.indexOf(item)}: $item'); });
Swift中的相同代码如下所示:
let list = ["apples", "bananas", "oranges"]
list.forEach({print("\(String(describing: list.firstIndex(of: $0))) \($0)")})
在这种情况下,我们不为传递给闭包的参数指定名称,而是使用$0
来表示第一个参数。这是完全可选的,如果愿意,我们可以使用命名参数:
list.forEach({ item in print("\(String(describing: list.firstIndex(of: item))) \(item)")})
在Swift中,闭包通常用作异步代码的完成块(请参阅下面有关异步编程的部分)。
阅读更多:
元组
从Swift文档:
元组将多个值分组为一个复合值。元组中的值可以是任何类型,而不必彼此相同。
这些可以用作较小的轻量类型,并且在定义具有多个返回值的函数时很有用。
这是在Swift中使用元组的方法:
let t = ("Andrea", 34, 1.84)
print(t.0) // prints "Andrea"
print(t.1) // prints 34
print(t.2) // prints 1.84
元组在Dart中具有单独的软件包支持:
const t = const Tuple3<String, int, double>('Andrea', 34, 1.84);
print(t.item1); // prints 'Andrea'
print(t.item2); // prints 34
print(t.item3); // prints 1.84
控制流
两种语言都提供各种控制流语句。
例如,if,for,while,switch。
在这里介绍这些将是相当冗长的,所以请参考官方文档:
集合(arrays, sets, maps)
Arrays / Lists
数组是有序的对象组。
在 Dart 中,使用 List 对象来表示数组:
var emptyList = <int>[]; // empty list
var list = [1, 2, 3]; // list literal
list.length; // 3
list[1]; // 2
数组在Swift中具有内置类型:
var emptyArray = [Int]() // empty array
var array = [1, 2, 3] // array literal
array.count // 3
array[1] // 2
sets
引用Swift文档:
集合将相同类型的不同值存储在集合中,没有定义的顺序。当项目的顺序不重要时,或者需要确保某个项目仅出现一次时,可以使用集合而不是数组。
这是用Dart中的Set类定义的。
var emptyFruits = <String>{}; // empty set literal
var fruits = {'apple', 'banana'}; // set literal
同样,在Swift中:
var emptyFruits = Set<String>()
var fruits = Set<String>(["apple", "banana"])
Maps / Dictionaries
Swift文档对地图/字典有很好的定义:
字典在没有定义顺序的情况下将相同类型的键和相同类型的值之间的关联存储在集合中。每个值都与一个唯一键相关联,该键充当字典中该值的标识符。
Maps在Dart中的定义如下:
var namesOfIntegers = Map<Int,String>(); // empty map
var airports = { 'YYZ': 'Toronto Pearson', 'DUB': 'Dublin' }; // map literal
Maps在Swift中称为字典:
var namesOfIntegers = [Int: String]() // empty dictionary
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] // dictionary literal
可空性和可选
在Dart中,任何对象都可以是null
。尝试访问null
对象的方法或变量会导致空指针异常。这是计算机程序中最常见的错误源(如果不是最常见的话)。
从一开始,Swift就具有optionals,这是一种内置的语言功能,用于声明对象是否可以具有值。引用文档:
在可能不存在值的情况下,可以使用可选选项。一个可选的选项代表两种可能性:要么有一个值,要么您可以拆开可选的选项以访问该值,或者根本没有一个值。
与此相反,我们可以使用非可选变量来确保它们将始终具有值:
var x: Int? // optional
var y: Int = 1 // non-optional, must be initialized
注意:说 Swift 变量是可选的与 Dart 变量可以为 null 是大致相同。
如果没有语言级别的对选项的支持,我们只能在运行时检查变量是否为null
。
使用可选参数时,我们在编译时对这些信息进行编码。我们可以拆开可选内容以安全地检查它们是否具有值:
func showOptional(x: Int?) {
// use `guard let` rather than `if let` as best practice
if let x = x { // unwrap optional
print(x)
} else {
print("no value")
}
}
showOptional(x: nil) // prints "no value"
showOptional(x: 5) // prints "5"
如果我们知道一个变量必须有一个值,我们可以使用一个非可选的:
func showNonOptional(x: Int) {
print(x)
}
showNonOptional(x: nil) // [compile error] Nil is not compatible with expected argument type 'Int'
showNonOptional(x: 5) // prints "5"
上面的第一个示例可以在Dart中实现如下:
void showOptional(int x) {
if (x != null) {
print(x);
} else {
print('no value');
}
}
showOptional(null) // prints "no value"
showOptional(5) // prints "5"
第二个是这样的:
void showNonOptional(int x) {
assert(x != null);
print(x);
}
showNonOptional(null) // [runtime error] Uncaught exception: Assertion failed
showNonOptional(5) // prints "5"
拥有可选参数意味着我们可以在编译时而不是运行时捕获错误。尽早发现错误可以使代码更安全,错误更少。
Dart 缺乏对 optional 的支持在某种程度上通过使用断言(以及用于命名参数的 @required 注释)得到缓解。
这些在Flutter SDK中得到了广泛的使用,但是会导致额外的样板代码。
为了记录在案,有人提议将非空类型添加到Dart中。(PS: 如今已经添加了)
类
类是使用面向对象的语言编写程序的主要构建块。
Dart和Swift支持类,但有所不同。
句法
这是带有初始化程序和Swift中三个成员变量的类:
class Person {
let name: String
let age: Int
let height: Double
init(name: String, age: Int, height: Double) {
self.name = name
self.age = age
self.height = height
}
}
与Dart相同:
class Person {
Person({this.name, this.age, this.height});
final String name;
final int age;
final double height;
}
注意this.[propertyName]
Dart构造函数中的用法。这是在构造函数运行之前用于设置实例成员变量的语法糖。
工厂构造函数
在Dart中,可以创建工厂构造函数:
在实现并非总是创建其类的新实例的构造函数时,请使用factory关键字。
工厂构造函数的一个实际用例是从JSON创建模型类时:
class Person {
Person({this.name, this.age, this.height});
final String name;
final int age;
final double height;
factory Person.fromJSON(Map<dynamic, dynamic> json) {
String name = json['name'];
int age = json['age'];
double height = json['height'];
return Person(name: name, age: age, height: height);
}
}
var p = Person.fromJSON({
'name': 'Andrea',
'age': 34,
'height': 1.84,
});
继承
Swift使用单继承模型,这意味着任何类只能有一个超类。Swift类可以实现多个接口(也称为协议)。
Dart类具有基于Mixin的继承。引用文档:
每个对象都是一个类的实例,并且所有类都从Object派生。基于混合的继承意味着尽管每个类(对象除外)都只有一个超类,但是一个类主体可以在多个类层次结构中重用。
这是Swift中的单继承行为:
class Vehicle {
let wheelCount: Int
init(wheelCount: Int) {
self.wheelCount = wheelCount
}
}
class Bicycle: Vehicle {
init() {
super.init(wheelCount: 2)
}
}
在Dart中:
class Vehicle {
Vehicle({this.wheelCount});
final int wheelCount;
}
class Bicycle extends Vehicle {
Bicycle() : super(wheelCount: 2);
}
属性
这些在Dart中称为实例变量,在Swift中仅称为属性。
在Swift中,存储属性和计算属性之间存在区别:
class Circle {
init(radius: Double) {
self.radius = radius
}
let radius: Double // stored property
var diameter: Double { // read-only computed property
return radius * 2.0
}
}
在Dart中,我们有相同的区别:
class Circle {
Circle({this.radius});
final double radius; // stored property
double get diameter => radius * 2.0; // computed property
}
除了计算属性的getter
之外,我们还可以定义setter
。
使用上面的示例,我们可以重写该diameter
属性以包含一个setter:
var diameter: Double { // computed property
get {
return radius * 2.0
}
set {
radius = newValue / 2.0
}
}
在Dart中,我们可以添加一个单独的setter,如下所示:
set diameter(double value) => radius = value / 2.0;
属性观察者
这是Swift的独特功能:
属性观察者观察并响应属性值的变化。每次设置属性值时都会调用属性观察器,即使新值与属性的当前值相同也是如此。
这是如何使用它们:
var diameter: Double { // read-only computed property
willSet(newDiameter) {
print("old value: \(diameter), new value: \(newDiameter)")
}
didSet {
print("old value: \(oldValue), new value: \(diameter)")
}
}
协议/抽象类
在这里,我们讨论一种用于定义方法和属性的构造,而不指定如何实现它们。这在其他语言中被称为接口。
在Swift中,接口称为协议。
protocol Shape {
func area() -> Double
}
class Square: Shape {
let side: Double
init(side: Double) {
self.side = side
}
func area() -> Double {
return side * side
}
}
Dart具有类似的构造,称为抽象类。抽象类无法实例化。但是,他们可以定义具有实现方式的方法。
上面的示例可以在Dart中这样写:
abstract class Shape {
double area();
}
class Square extends Shape {
Square({this.side});
final double side;
double area() => side * side;
}
Mixins
在Dart中,mixin只是一个常规类,可以在多个类层次结构中重用。
以下代码演示了我们使用 NameExtension mixin
扩展我们之前定义的 Person
类:
abstract class NameExtension {
String get name;
String get uppercaseName => name.toUpperCase();
String get lowercaseName => name.toLowerCase();
}
class Person with NameExtension {
Person({this.name, this.age, this.height});
final String name;
final int age;
final double height;
}
var person = Person(name: 'Andrea', age: 34, height: 1.84);
print(person.uppercaseName); // 'ANDREA'
扩展名
扩展是Swift语言的功能。引用文档:
扩展将新功能添加到现有的类,结构,枚举或协议类型。这包括扩展无法访问原始源代码的类型的能力(称为追溯建模)。
Dart中的mixins无法做到这一点。
借用上面的示例,我们可以像这样扩展Person
类:
extension Person {
var uppercaseName: String {
return name.uppercased()
}
var lowercaseName: String {
return name.lowercased()
}
}
var person = Person(name: "Andrea", age: 34, height: 1.84)
print(person.uppercaseName) // "ANDREA"
扩展比我在这里介绍的要多得多,尤其是当它们与协议和泛型一起使用时。
扩展的一种非常常见的用例是将协议一致性添加到现有类型中。例如,我们可以使用扩展将序列化功能添加到现有模型类中。
枚举
Dart对枚举有一些非常基本的支持。
Swift中的枚举功能非常强大,因为它们支持关联的类型:
enum NetworkResponse {
case success(body: Data)
case failure(error: Error)
}
这样就可以编写如下逻辑:
switch (response) {
case .success(let data):
// do something with (non-optional) data
case .failure(let error):
// do something with (non-optional) error
}
请注意data
和error
参数是如何互斥的。
在Dart中,我们无法将其他值与枚举相关联,并且上面的代码可以按照以下方式实现:
class NetworkResponse {
NetworkResponse({this.data, this.error})
// assertion to make data and error mutually exclusive
: assert(data != null && error == null || data == null && error != null);
final Uint8List data;
final String error;
}
var response = NetworkResponse(data: Uint8List(0), error: null);
if (response.data != null) {
// use data
} else {
// use error
}
一些注意事项:
- 在这里,我们使用断言来补偿我们没有可选项的事实。
- 编译器无法帮助我们检查所有可能的情况。这是因为我们不使用
switch
来处理响应。
总而言之,Swift枚举比Dart更强大和更具表现力。
像 Dart Sealed Unions 这样的第三方库提供了类似于 Swift 枚举的功能,可以帮助填补空白。
结构体
在Swift中,我们可以定义结构体和类。
两种构造都有很多共同点,并且有所不同。
主要区别在于:
类是引用类型,结构体是值类型
引用文档:
值类型是一种在将值分配给变量或常量或将其传递给函数时会复制其值的类型。
所有结构和枚举都是Swift中的值类型。这意味着,您创建的任何结构和枚举实例以及它们作为属性具有的任何值类型,都将在它们在代码中传递时始终被复制。
与值类型不同,将引用类型分配给变量或常量或将其传递给函数时,不会复制引用类型。而不是副本,而是使用对相同现有实例的引用。
要了解这意味着什么,请考虑以下示例,在该示例中,我们重新调整了Person
类的用途以使其可变:
class Person {
var name: String
var age: Int
var height: Double
init(name: String, age: Int, height: Double) {
self.name = name
self.age = age
self.height = height
}
}
var a = Person(name: "Andrea", age: 34, height: 1.84)
var b = a
b.age = 35
print(a.age) // prints 35
如果我们重新定义Person
为一个结构,我们有以下内容:
struct Person {
var name: String
var age: Int
var height: Double
init(name: String, age: Int, height: Double) {
self.name = name
self.age = age
self.height = height
}
}
var a = Person(name: "Andrea", age: 34, height: 1.84)
var b = a
b.age = 35
print(a.age) // prints 34
除了我在这里介绍的内容外,还有更多的内容要对结构进行介绍。
结构可以很好地用于处理Swift中的数据和模型,从而导致具有更少错误的健壮代码。
错误处理
使用Swift文档中的定义:
错误处理是对程序中的错误情况做出响应并从中恢复的过程。
Dart和Swift都使用try/catch
了一种处理错误的技术,但有一些区别。
在Dart中,任何方法都可以引发任何类型的异常。
class BankAccount {
BankAccount({this.balance});
double balance;
void withdraw(double amount) {
if (amount > balance) {
throw Exception('Insufficient funds');
}
balance -= amount;
}
}
可以使用一个try/catch
块来捕获异常:
var account = BankAccount(balance: 100);
try {
account.withdraw(50); // ok
account.withdraw(200); // throws
} catch (e) {
print(e); // prints 'Exception: Insufficient funds'
}
在Swift中,我们明确声明方法何时可以引发异常。这是通过throws
关键字完成的,所有错误都必须符合Error
协议:
enum AccountError: Error {
case insufficientFunds
}
class BankAccount {
var balance: Double
init(balance: Double) {
self.balance = balance
}
func withdraw(amount: Double) throws {
if amount > balance {
throw AccountError.insufficientFunds
}
balance -= amount
}
}
处理错误时,我们try
在do/catch
块内使用关键字。
var account = BankAccount(balance: 100)
do {
try account.withdraw(amount: 50) // ok
try account.withdraw(amount: 200) // throws
} catch AccountError.insufficientFunds {
print("Insufficient Funds")
}
请注意,try
在调用可以抛出的方法时,关键字是强制性的。
错误本身是强类型的,因此我们可以有多个catch块来涵盖所有可能的情况。
try, try?, try!
Swift提供了较少冗长的错误处理方式。
我们可以用try?
没有一个do/catch
块。这将导致忽略任何异常:
var account = BankAccount(balance: 100)
try? account.withdraw(amount: 50) // ok
try? account.withdraw(amount: 200) // fails silently
或者,如果我们确定某个方法不会抛出异常,则可以使用try!
:
var account = BankAccount(balance: 100)
try! account.withdraw(amount: 50) // ok
try! account.withdraw(amount: 200) // crash
上面的示例将导致程序崩溃。因此,try!
不建议在生产代码中使用它,它更适合编写测试时使用。
总体而言,Swift中错误处理的显式性质在API设计中非常有好处,因为它使您很容易知道方法是否可以抛出。
同样,try
方法调用的使用引起了对可能抛出的代码的注意,从而迫使我们考虑错误情况。
在这方面,错误处理比Dart更安全,更可靠。
泛型
引用Swift文档:
通用代码使您可以编写灵活,可重用的函数和类型,这些函数和类型可以根据您定义的要求与任何类型一起使用。您可以编写避免重复的代码,并以清晰抽象的方式表达其意图。
两种语言都支持泛型。
泛型最常见的用例之一是集合,例如数组arrays,集合sets和映射maps。
我们可以使用它们来定义我们自己的类型。这是我们Stack
在Swift中定义通用类型的方式:
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
同样,在Dart中,我们将编写:
class Stack<Element> {
var items = <Element>[]
void push(Element item) {
items.add(item)
}
void pop() -> Element {
return items.removeLast()
}
}
泛型在Swift中非常有用且功能强大,可用于在协议中定义类型约束和关联的类型。
访问控制
引用Swift文档:
访问控制限制从其他源文件和模块中的代码访问部分代码。使用此功能,您可以隐藏代码的实现细节,并指定可以访问和使用该代码的首选接口。
Swift具有五个访问级别:open,public,internal,file-private和private。
这些用于处理模块和源文件的上下文中:
模块是代码分发的单个单元—一个框架或应用程序,是作为单个单元构建和交付的,并且可以由另一个模块使用Swift的import关键字导入。
open
和 public
访问级别可让代码在模块外部访问。
private
和 file-private
访问级别可让代码无法在其定义的文件之外访问。
例如:
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
访问级别在Dart中更简单,并且仅限于public和private:
与 Java 不同,Dart 没有关键字
public
,protected
和private
。如果标识符以下划线 _ 开头,则它是私有的。
例子:
class HomePage extends StatefulWidget { // public
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> { ... } // private
Dart 和 Swift 中访问控制的设计目标不同。因此,访问级别非常不同。
异步编程:Future
异步编程是Dart真正发挥作用的领域。
处理用例时,需要某种形式的异步编程,例如:
- 从网络下载内容
- 与后端服务交谈
- 执行长时间运行的操作
在这些情况下,最好不要阻塞执行主线程,这会使我们的程序冻结。
引用Dart文档:
异步操作使您的程序可以在等待操作完成的同时完成其他工作。Dart使用Future对象(Future)来表示异步操作的结果。要使用Future,可以使用async and await或Future API。
作为示例,让我们看看如何使用异步编程来:
- 使用服务器对用户进行身份验证
- 将访问令牌存储到安全存储
- 获取用户个人资料信息
在 Dart 中,这可以通过结合使用 Future 和 async/await 来完成::
Future<UserProfile> getUserProfile(UserCredentials credentials) async {
final accessToken = await networkService.signIn(credentials);
await secureStorage.storeToken(accessToken, forUserCredentials: credentials);
return await networkService.getProfile(accessToken);
}
在Swift中,不支持async/await
,我们只能通过闭包(完成块)来实现:
func getUserProfile(credentials: UserCredentials, completion: (_ result: UserProfile) -> Void) {
networkService.signIn(credentials) { accessToken in
secureStorage.storeToken(accessToken) {
networkService.getProfile(accessToken, completion: completion)
}
}
}
由于嵌套的完成块,这导致了“厄运金字塔”。在这种情况下,错误处理变得非常困难。
在Dart中,只需try/catch
在getUserProfile
方法周围添加一个代码块,即可完成上面代码中的错误处理。
供参考,async/await
将来有一个建议添加到Swift中。这在此处详细记录:
在实现此功能之前,开发人员可以使用第三方库,例如Google的Promises库。
至于Dart,可以在这里找到出色的文档:
异步编程:Streams
流Streams被实现为Dart核心库的一部分,但不是在Swift中实现。
引用Dart文档:
流是一系列异步事件。
流是反应式应用程序的基础,它们在状态管理中起着重要的作用。
例如,流是搜索内容的绝佳选择,每当用户更新搜索字段中的文本时,流就会发出一组新的结果。
流不包含在Swift核心库中。RxSwift之类的第三方库提供流支持等等。
流是一个广泛的主题,此处不予讨论。
阅读更多:Dart异步编程:流
内存管理
Dart通过高级垃圾回收方案管理内存。
Swift通过自动引用计数(ARC)管理内存。
这保证了出色的性能,因为在不再使用内存时会立即释放它。
但是,它确实将负担部分地从编译器转移到了开发人员。
在Swift,我们需要考虑的生命周期和对象的所有权,并使用适当的关键字(weak
,strong
,unowned
),以免保留周期。
阅读更多:Swift自动引用计数。
编译与执行
首先,实时(JIT )编译器和提前(AOT)编译器之间的重要区别是:
JIT编译器
JIT编译器会在程序执行期间运行,并进行即时编译。
JIT编译器通常与动态语言一起使用,在动态语言中类型不是提前固定的。JIT程序通过解释器或虚拟机(VM)运行。
AOT编译器
AOT编译器会在程序创建期间,运行时之前运行。
AOT编译器通常与知道数据类型的静态语言一起使用。AOT程序被编译成本机代码,由硬件在运行时直接执行。
引用Wm Leler的这篇精彩文章:
在开发过程中完成AOT编译时,总是会导致开发周期大大缩短(从对程序进行更改到能够执行程序以查看更改结果之间的时间)。但是AOT编译导致程序可以更可预测地执行,而不会在运行时暂停分析和编译。AOT编译的程序也开始更快地执行(因为它们已经被编译)。
相反,JIT编译提供了更快的开发周期,但可能导致执行速度变慢或变慢。特别是,JIT编译器的启动时间较慢,因为在程序开始运行时,JIT编译器必须在执行代码之前进行分析和编译。研究表明,如果花费超过几秒钟的时间才能开始执行,那么许多人会放弃该应用程序。
作为一种静态语言,Swift会提前进行编译。
Dart可以同时编译AOT和JIT。与Flutter一起使用时,这提供了显着的优势:
JIT编译是在开发过程中使用的,它使用的编译器特别快。然后,当一个应用程序准备发布时,它将被编译为AOT。因此,借助高级工具和编译器,Dart可以提供两全其美的优势:极快的开发周期,快速的执行和启动时间。— Wm Leler
借助Dart,我们将两全其美。
Swift遭受了AOT编译的主要缺点。即,编译时间随着代码库的大小而增加。
对于中型应用程序(在10K到100K行之间),可以很容易地花费几分钟来编译应用程序。
Flutter应用程序并非如此,在该应用程序中,无论代码库的大小如何,我们都能持续获得亚秒级的热加载。
其他功能未涵盖
以下功能未涵盖,因为它们在Dart和Swift中非常相似:
- 运算符
- 字符串
- Swift中的可选链接(在Dart中称为条件成员访问)。
并发
Dart缺少我最喜欢的Swift功能
- 结构体
- 带有关联类型的枚举
- Optionals
Swift缺少我最喜欢的Dart功能
- JIT即时编译器
- Future 和 await/async
- Stream 和 yield/async*
结论
Dart和Swift都是出色的语言,非常适合构建现代移动应用程序及其他。
两种语言都没有优势,因为它们都有自己独特的长处。
在查看移动应用程序开发和这两种语言的工具时,我觉得Dart占了上风。这归因于JIT编译器,它是Flutter中有状态热加载的基础。
热加载可以在构建应用程序时极大地提高生产力,因为它可以将开发周期从几秒钟或几分钟缩短到不到一秒钟。
开发人员时间比计算时间更稀缺。
因此,优化开发人员时间是非常明智的举动。
另一方面,我认为Swift具有非常强大的类型系统。类型安全性融入了所有语言功能,并且更自然地导致了健壮的程序。
一旦我们放弃个人喜好,编程语言就是工具。为开发人员选择最合适的工具是我们的工作。
无论如何,我们都希望两种语言在发展过程中能够互相借鉴最好的想法。