
原文連結: https://medium.com/coding-with-flutter/dart-vs-swift-a-comparison-6491e945dc17
發表日期: 2018.12.27
Dart和Swift是我最喜歡的兩種程式設計語言。我已經在商業和開源代碼中廣泛使用了它們。
本文提供了Dart和Swift的并排比較,旨在:
- 突出顯示兩者之間的差異。
- 為開發人員從一種語言轉移到另一種語言(或同時使用兩種語言)提供參考。
一些背景:
- Dart支援Flutter(Google的架構),該架構用于從單個代碼庫建構漂亮的本機應用程式。
- Swift在iOS,macOS,tvOS和watchOS上為Apple的SDK提供支援。
在兩種語言(自Dart 2.1和Swift 4.2起)的主要功能之間進行了以下比較。由于深入讨論每個功能超出了本文的範圍,是以我在适當的地方提供了一些參考,以供進一步閱讀。
文章目錄
- 比較表
- 變量
- 類型推斷
- 可變/不可變變量
- 函數
- 命名和未命名參數
- 可選參數和預設參數
- 閉包
- 元組
- 控制流
- 集合(arrays, sets, maps)
- 可空性和可選
- 類
- 繼承
- 屬性
- 協定/抽象類
- Mixins
- 擴充名
- 枚舉
- 結構體
- 錯誤處理
- 泛型
- 通路控制
- 異步程式設計:Future
- 異步程式設計: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中,可以這樣寫:
更多細節
引用Dart文檔:
分析器可以推斷字段,方法,局部變量和大多數通用類型參數的類型。當分析器沒有足夠的資訊來推斷特定類型時,它将使用動态類型。
引用Swift文檔:
Swift廣泛使用類型推斷,允許您在代碼中省略許多變量和表達式的類型或部分類型。例如,您可以不寫Int類型:
var x: Int = 0
,編譯器可以正确地推斷出名
var x = 0
為的type值類型為
x
。
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
閱讀更多:
- Dart函數
- Swift函數
命名和未命名參數
兩種語言都支援命名和未命名參數。
在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
來表示第一個參數。這是完全可選的,如果願意,我們可以使用命名參數:
在Swift中,閉包通常用作異步代碼的完成塊(請參閱下面有關異步程式設計的部分)。
閱讀更多:
- Dart匿名函數
- 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。
在這裡介紹這些将是相當冗長的,是以請參考官方文檔:
- Swift控制流
- Dart控制流
集合(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中。這在此處詳細記錄:
- Swift的Async / Await建議
在實作此功能之前,開發人員可以使用第三方庫,例如Google的Promises庫。
至于Dart,可以在這裡找到出色的文檔:
- Dart異步程式設計:Future
異步程式設計: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使用Grand Central Dispatch(GCD)和排程隊列。
Dart缺少我最喜歡的Swift功能
- 結構體
- 帶有關聯類型的枚舉
- Optionals
Swift缺少我最喜歡的Dart功能
- JIT即時編譯器
- Future 和 await/async
- Stream 和 yield/async*
結論
Dart和Swift都是出色的語言,非常适合建構現代移動應用程式及其他。
兩種語言都沒有優勢,因為它們都有自己獨特的長處。
在檢視移動應用程式開發和這兩種語言的工具時,我覺得Dart占了上風。這歸因于JIT編譯器,它是Flutter中有狀态熱加載的基礎。
熱加載可以在建構應用程式時極大地提高生産力,因為它可以将開發周期從幾秒鐘或幾分鐘縮短到不到一秒鐘。
開發人員時間比計算時間更稀缺。
是以,優化開發人員時間是非常明智的舉動。
另一方面,我認為Swift具有非常強大的類型系統。類型安全性融入了所有語言功能,并且更自然地導緻了健壯的程式。
一旦我們放棄個人喜好,程式設計語言就是工具。為開發人員選擇最合适的工具是我們的工作。
無論如何,我們都希望兩種語言在發展過程中能夠互相借鑒最好的想法。