| 作者:Andrea Bizzotto
| 原文鏈接:medium.com/coding-with…
Dart 和 Swift 是我最喜歡的編程語言京闰。我在商業(yè)和開源代碼中廣泛使用它們敬鬓。
本文提供了 Dart 和 Swift 之間的比較讨惩,旨在:
- 突出顯示兩者之間的差異垃它;
- 作為開發(fā)人員從一種語言轉(zhuǎn)移到另一種語言(或使用兩者)的參考笆凌。
一些背景:
- Dart 支持 Flutter,這是 Google 用于從單一代碼庫構(gòu)建漂亮的本機(jī)應(yīng)用程序的框架澄峰。
- Swift 通過 iOS馋没,macOS,tvOS 和 watchOS 為 Apple 的 SDK 提供支持钉迷。
以下是兩種語言的主要特征(Dart 2.1
和 Swift 4.2
)的比較至非。由于深入討論每個(gè)功能超出了本文的范圍,因此更多的信息可以參考各自的文檔糠聪。
目錄
- 對照表
- 變量
- 類型推斷
- 可變/不可變變量
- 函數(shù)
- 命名和未命名參數(shù)
- 可選和默認(rèn)參數(shù)
- 閉包
- 元組
- 控制流
- 集合
- Nullability & Optionals
- 類
- 繼承
- 屬性
- 協(xié)議/抽象類
- Mixins
- 擴(kuò)展
- 枚舉
- 結(jié)構(gòu)體
- 錯(cuò)誤處理
- 泛型
- 訪問控制
- 異步編程:Future
- 異步編程:Stream
- 內(nèi)存管理
- 編譯和執(zhí)行
- 其它未涵蓋功能
對照表
變量
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
在此示例中荒椭,不需要類型注釋。這是因?yàn)閮煞N語言都可以從賦值右側(cè)的表達(dá)式推斷出類型舰蟆。
類型推斷
類型推斷意味著我們可以在 Dart 中編寫以下代碼:
var arguments = {'argA': 'hello', 'argB': 42}; // Map<String, Object>
編譯器會(huì)自動(dòng)解析 arguments
的類型趣惠。
在 Swift 中,同樣可以寫成:
var arguments = [ "argA": "hello", "argB": 42 ] // [ String : Any ]
更多細(xì)節(jié)
Dart 文檔有如下描述:
分析器可以推斷字段身害、方法味悄、局部變量和大多數(shù)泛型類型參數(shù)的類型。當(dāng)分析器沒有足夠的信息來推斷特定類型時(shí)塌鸯,將使用動(dòng)態(tài)類型侍瑟。
Swift 文檔中有如下描述:
Swift 廣泛使用類型推斷,允許您省略代碼中許多變量和表達(dá)式的類型或部分類型丙猬。例如涨颜,不是寫 var x:Int = 0
,而是可以寫 var x = 0
茧球,完全省略類型 - 編譯器正確地推斷出 x 為 Int
類型的值庭瑰。
動(dòng)態(tài)類型
可以使用 Dart 中的 dynamic
關(guān)鍵字和 Swift 中的 Any
關(guān)鍵字聲明可以是任何類型的變量。
在讀取 JSON
等數(shù)據(jù)時(shí)袜腥,通常會(huì)使用動(dòng)態(tài)類型见擦。
可變/不可變變量
變量可以聲明為可變或不可變钉汗。
為了聲明可變變量羹令,兩種語言都使用 var
關(guān)鍵字鲤屡。
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 文檔定義了兩個(gè)關(guān)鍵字 final
和 const
,其工作方式如下:
如果您不打算更改變量值肪凛,請使用
final
或const
堰汉,而不是var
或類型。final
變量只能設(shè)置一次伟墙;const
變量是編譯時(shí)常量翘鸭。(Const
變量是隱式final
。)final 頂層類型變量或類變量在第一次使用時(shí)被初始化戳葵。
在 Dart 網(wǎng)站上的這篇文章中可以找到進(jìn)一步的解釋:
final 意味著一次賦值就乓。final 變量或字段必須具有
initializer
。 一旦賦值拱烁,就不能改變 final 變量的值生蚁。
在 Swift 中,我們用 let
聲明常量戏自。
常量聲明會(huì)在程序中引入常量命名值邦投。使用
let
關(guān)鍵字聲明常量,并具有以下形式:
let constant name: type = expression
常量聲明定義常量名稱和初始化表達(dá)式值之間的不可變綁定擅笔;設(shè)置常量值后志衣,無法更改。
函數(shù)
函數(shù)在 Swift 和 Dart 中都是一等公民猛们。
這意味著就像對象一樣念脯,函數(shù)可以作為參數(shù)傳遞,保存為屬性或作為結(jié)果返回阅懦。
作為初始比較和二,我們可以看到如何聲明不帶參數(shù)的函數(shù)。
在 Dart 中耳胎,返回類型在方法名稱之前:
void foo();
int bar();
在 Swift 中惯吕,我們使用 -> T
表示法作為后綴。如果沒有返回值(Void)怕午,則不需要這樣做:
func foo()
func bar() -> Int
命名及未命名(un-named)參數(shù)
兩種語言都支持命名和未命名的參數(shù)废登。
在 Swift 中,參數(shù)默認(rèn)為命名參數(shù):
func foo(name: String, age: Int, height: Double)
foo(name: "Andrea", age: 34, height: 1.84)
在 Dart 中郁惜,我們使用花括號({})定義命名參數(shù):
void foo({String name, int age, double height});
foo(name: 'Andrea', age: 34, height: 1.84);
在 Swift 中堡距,我們使用下劃線(_)
作為外部參數(shù)來定義未命名的參數(shù):
func foo(_ name: String, _ age: Int, _ height: Double)
foo("Andrea", 34, 1.84)
在 Dart 中甲锡,我們通過省略花括號({})來定義未命名的參數(shù):
void foo(String name, int age, double height);
foo('Andrea', 34, 1.84);
可選和默認(rèn)參數(shù)
兩種語言都支持默認(rèn)參數(shù)。
在 Swift 中羽戒,您可以通過在該參數(shù)的類型之后為參數(shù)賦值來為函數(shù)中的任何參數(shù)定義默認(rèn)值缤沦。如果定義了默認(rèn)值,則可以在調(diào)用函數(shù)時(shí)省略該參數(shù)易稠。
func foo(name: String, age: Int = 0, height: Double = 0.0)
foo(name: "Andrea", age: 34) // name: "Andrea", age: 34, height: 0.0
在 Dart 中缸废,可選參數(shù)可以是位置參數(shù),也可以是命名參數(shù)驶社,但不能同時(shí)企量。
// 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
閉包
作為頂層(first-class)對象,函數(shù)可以作為參數(shù)傳遞給其他函數(shù)亡电,或者分配給變量届巩。
在此上下文中,函數(shù)也稱為閉包份乒。
這是一個(gè)函數(shù)的 Dart 示例恕汇,它迭代一個(gè) item 列表,使用閉包來打印每個(gè)項(xiàng)目的索引和內(nèi)容:
final list = ['apples', 'bananas', 'oranges'];
list.forEach((item) => print('${list.indexOf(item)}: $item'));
閉包帶有一個(gè)參數(shù)(item
)冒嫡,打印該項(xiàng)的索引和值拇勃,并且不返回任何值。
注意使用箭頭符號(=>)
孝凌。這可以代替花括號內(nèi)的單個(gè) 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)")})
在這種情況下方咆,我們不為傳遞給閉包的參數(shù)指定名稱,而使用 $0
代替第一個(gè)參數(shù)蟀架。這完全是可選的瓣赂,我們?nèi)匀豢梢允褂妹麉?shù):
list.forEach({ item in print("\(String(describing: list.firstIndex(of: item))) \(item)")})
閉包通常用作 Swift 中異步代碼的完成塊(請參閱下面有關(guān)異步編程
的部分)。
元組
Swift 文檔的描述如下:
元組將多個(gè)值分組為單個(gè)復(fù)合值片拍。元組中的值可以是任何類型煌集,并且不必具有相同的類型。
這些可以用作小型輕量級類型捌省,在定義具有多個(gè)返回值的函數(shù)時(shí)非常有用苫纤。
以下是如何在 Swift 中使用元組:
let t = ("Andrea", 34, 1.84)
print(t.0) // prints "Andrea"
print(t.1) // prints 34
print(t.2) // prints 1.84
Dart 中有一個(gè)單獨(dú)三方包支持元組:
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 語句栗弟。
在這里介紹這些將是相當(dāng)冗長的,所以請參考官方文檔工闺。
集合(arrays, sets, maps)
Arrays / Lists
數(shù)組是有序的對象組乍赫。
在 Dart 中瓣蛀,使用 List
對象來表示數(shù)組:
var emptyList = <int>[]; // empty list
var list = [1, 2, 3]; // list literal
list.length; // 3
list[1]; // 2
Swift 中數(shù)組是內(nèi)置類型:
var emptyArray = [Int]() // empty array
var array = [1, 2, 3] // array literal
array.count // 3
array[1] // 2
Sets
Swift 文檔中的描述:
Set 在集合中存儲(chǔ)相同類型的不同值,沒有定義的順序雷厂。當(dāng)項(xiàng)目的順序不重要時(shí)惋增,或者當(dāng)您需要確保元素僅出現(xiàn)一次時(shí),您可以使用集合而不是數(shù)組罗侯。
Dart 中 Set 類的定義:
var emptyFruits = Set<String>();
var fruits = Set<String>.from(['apple', 'banana']); // set from Iterable
Swift 中的示例:
var emptyFruits = Set<String>()
var fruits = Set<String>(["apple", "banana"])
Maps / Dictionaries
Swift 文檔對 map/dictionary
有一個(gè)很好的定義:
字典存儲(chǔ)相同類型的鍵與集合中相同類型的值之間的關(guān)聯(lián)器腋,而沒有特定的排序溪猿。每個(gè)值都與唯一鍵相關(guān)聯(lián)钩杰,該唯一鍵充當(dāng)字典中該值的標(biāo)識符。
Dart 中的 map 定義如下:
var namesOfIntegers = Map<Int,String>(); // empty map
var airports = { 'YYZ': 'Toronto Pearson', 'DUB': 'Dublin' }; // map literal
Swift 中 map
稱為字典:
var namesOfIntegers = [Int: String]() // empty dictionary
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] // dictionary literal
Nullability & Optionals
在Dart中诊县,任何對象都可以為 null
讲弄。并且嘗試訪問 null
對象的方法或變量會(huì)導(dǎo)致空指針異常。這是計(jì)算機(jī)程序中最常見的錯(cuò)誤來源依痊。
從一開始避除,Swift
就多了一個(gè)選擇,一個(gè)內(nèi)置的語言功能胸嘁,用于聲明對象是否可以有值瓶摆。看看文檔:
您可以在可能缺少值的情況下使用 Optional性宏。
Optional
表示兩種可能性:要么存在值群井,您可以解開可選項(xiàng)以訪問該值,或者根本沒有值毫胜。
與此相反书斜,我們可以使用非 Optional 變量來保證它們始終具有值:
var x: Int? // optional
var y: Int = 1 // non-optional, must be initialized
注意:說 Swift 變量是可選的與 Dart 變量可以為 null 是大致相同。
如果沒有對選項(xiàng)的語言級支持酵使,我們只能在運(yùn)行時(shí)檢查變量是否為 null
荐吉。
使用 Optional,我們在編譯時(shí)對這些信息進(jìn)行編碼口渔。我們可以解開 Optional 以安全地檢查它們是否包含值:
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"
如果我們知道變量必須有值样屠,我們可以使用 non-optional
的值:
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"
上面的第一個(gè)例子在 Dart 中的實(shí)現(xiàn)如下:
void showOptional(int x) {
if (x != null) {
print(x);
} else {
print('no value');
}
}
showOptional(null) // prints "no value"
showOptional(5) // prints "5"
第二個(gè)如下實(shí)現(xiàn):
void showNonOptional(int x) {
assert(x != null);
print(x);
}
showNonOptional(null) // [runtime error] Uncaught exception: Assertion failed
showNonOptional(5) // prints "5"
有 optional 意味著我們可以在編譯時(shí)而不是在運(yùn)行時(shí)捕獲錯(cuò)誤。及早捕獲錯(cuò)誤會(huì)讓代碼更安全缺脉,錯(cuò)誤更少痪欲。
Dart 缺乏對 optional 的支持在某種程度上通過使用斷言(以及用于命名參數(shù)的 @required 注釋)得到緩解。
這些在 Flutter SDK
中廣泛使用枪向,但會(huì)產(chǎn)生額外的樣板代碼勤揩。
類
類是用面向?qū)ο笳Z言編寫程序的主要構(gòu)建塊。
Dart 和 Swift 都支持類秘蛔,但有一些差異陨亡。
語法
這里有一個(gè)帶有 initializer
和三個(gè)成員變量的 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;
}
請注意在 Dart 構(gòu)造函數(shù)中使用的 this.[propertyName]
傍衡。這是用于在構(gòu)造函數(shù)運(yùn)行之前設(shè)置實(shí)例成員變量的語法糖。
工廠構(gòu)造函數(shù)
在 Dart 中负蠕,可以使用工廠構(gòu)造函數(shù)蛙埂。
在實(shí)現(xiàn)并不總是創(chuàng)建其類的新實(shí)例的構(gòu)造函數(shù)時(shí),請使用
factory
關(guān)鍵字遮糖。
工廠構(gòu)造函數(shù)的一個(gè)實(shí)際用例是從 JSON 創(chuàng)建模型類時(shí):
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 使用單繼承模型绣的,這意味著任何類只能有一個(gè)超類。Swift類可以實(shí)現(xiàn)多個(gè)接口(也稱為協(xié)議)欲账。
Dart 類具有基于 mixin
的繼承屡江。如文檔描述:
每個(gè)對象都是一個(gè)類的實(shí)例,所有類都來自
Object
赛不〕图危基于Mixin
的繼承意味著雖然每個(gè)類(除了Object)只有一個(gè)超類,但是類體可以在多個(gè)類層次結(jié)構(gòu)中重用踢故。
以下是 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 中稱為實(shí)例變量文黎,在 Swift 中只是屬性。
在 Swift 中殿较,存儲(chǔ)和計(jì)算屬性之間存在區(qū)別:
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 中耸峭,我們有相同的區(qū)分:
class Circle {
Circle({this.radius});
final double radius; // stored property
double get diameter => radius * 2.0; // computed property
}
除了計(jì)算屬性的 getter
之外,我們還可以定義 setter
淋纲。
使用上面的例子劳闹,我們可以重寫 diameter
屬性以包含一個(gè) setter
:
var diameter: Double { // computed property
get {
return radius * 2.0
}
set {
radius = newValue / 2.0
}
}
在 Dart 中,我們可以像這樣添加一個(gè)單獨(dú)的 setter
:
set diameter(double value) => radius = value / 2.0;
屬性觀察者
這是 Swift 的一個(gè)特有功能帚戳。如文檔描述:
屬性觀察者負(fù)責(zé)觀察并響應(yīng)屬性值的變化玷或。每次設(shè)置屬性值時(shí)都會(huì)調(diào)用屬性觀察者,即使新值與屬性的當(dāng)前值相同片任。
這是他們的使用方式:
var diameter: Double { // read-only computed property
willSet(newDiameter) {
print("old value: \(diameter), new value: \(newDiameter)")
}
didSet {
print("old value: \(oldValue), new value: \(diameter)")
}
}
協(xié)議/抽象類
這里我們討論用于定義方法和屬性偏友,而不指定它們的實(shí)現(xiàn)方式的結(jié)構(gòu)。這在其他語言中稱為接口对供。
在 Swift 中位他,接口稱為協(xié)議。
protocol Shape {
func area() -> Double
}
class Square: Shape {
let side: Double
init(side: Double) {
self.side = side
}
func area() -> Double {
return side * side
}
}
Dart有一個(gè)類似的結(jié)構(gòu)产场,稱為抽象類鹅髓。抽象類無法實(shí)例化。但是京景,他們可以定義具有實(shí)現(xiàn)的方法窿冯。
上面的例子在 Dart 中可以這樣寫:
abstract class Shape {
double area();
}
class Square extends Shape {
Square({this.side});
final double side;
double area() => side * side;
}
Mixins
在 Dart 中,mixin
只是一個(gè)常規(guī)類确徙,可以在多個(gè)類層次結(jié)構(gòu)中重用醒串。
以下代碼演示了我們使用 NameExtension mixin
擴(kuò)展我們之前定義的 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'
擴(kuò)展
擴(kuò)展是 Swift 語言的一個(gè)特性执桌。如文檔描述:
擴(kuò)展為現(xiàn)有的類,結(jié)構(gòu)芜赌,枚舉或協(xié)議類型添加新功能仰挣。這包括擴(kuò)展那些無法訪問原始源代碼的類型的能力(稱為追溯建模)。
在 Dart 中使用 mixins
是無法實(shí)現(xiàn)這一點(diǎn)的缠沈。
借用上面的例子膘壶,我們可以像這樣擴(kuò)展 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"
擴(kuò)展的內(nèi)容比我在這里介紹的要多得多,特別是當(dāng)它們與協(xié)議和泛型一起使用時(shí)洲愤。
擴(kuò)展的一個(gè)非常常見的用例是為現(xiàn)有類型添加協(xié)議一致性颓芭。例如,我們可以使用擴(kuò)展來為現(xiàn)有模型類添加序列化功能禽篱。
枚舉
Dart 對枚舉有一些非承蠓ィ基本的支持。
而 Swift 中的枚舉非常強(qiáng)大躺率,因?yàn)樗鼈冎С株P(guān)聯(lián)類型:
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
參數(shù)是如何互斥的。
在 Dart 中万矾,我們無法將其他值與枚舉相關(guān)聯(lián)悼吱,上面的代碼可以按以下方式實(shí)現(xiàn):
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
}
幾個(gè)注意事項(xiàng):
- 在這里,我們使用斷言來彌補(bǔ)我們沒有 optional 的事實(shí)良狈。
- 編譯器無法幫助我們檢查所有可能的情況后添。這是因?yàn)槲覀儾皇褂?
switch
來處理響應(yīng)。
總之薪丁,Swift 枚舉比 Dart 強(qiáng)大且富有表現(xiàn)力遇西。
像 Dart Sealed Unions 這樣的第三方庫提供了類似于 Swift 枚舉的功能,可以幫助填補(bǔ)空白严嗜。
結(jié)構(gòu)體
在 Swift 中粱檀,我們可以定義結(jié)構(gòu)和類。
這兩種結(jié)構(gòu)都有許多共同點(diǎn)漫玄,也有一些不同之處茄蚯。
主要區(qū)別在于:
類是引用類型,結(jié)構(gòu)體是值類型
文檔中的描述如下:
值類型是一種類型睦优,其值在被賦值給變量或常量時(shí)被復(fù)制渗常,或者在傳遞給函數(shù)時(shí)被復(fù)制。 Swift 中所有結(jié)構(gòu)和枚舉都是值類型汗盘。這意味著您創(chuàng)建的任何結(jié)構(gòu)和枚舉實(shí)例 - 以及它們所有的值類型的屬性 - 在代碼中傳遞時(shí)始終會(huì)被復(fù)制皱碘。 與值類型不同,引用類型在分配給變量或常量時(shí)或者傳遞給函數(shù)時(shí)不會(huì)被復(fù)制隐孽。而是使用對同一現(xiàn)有實(shí)例的引用癌椿。
要了解這意味著什么家凯,請考慮以下示例,其中我們重新使用 Person 類使其變?yōu)榭勺儯?/p>
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
如失,我們有:
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
結(jié)構(gòu)體的內(nèi)容比我在這里介紹的要多得多绊诲。
結(jié)構(gòu)體可用于處理 Swift 中的數(shù)據(jù)和模型,從而產(chǎn)生具有更少錯(cuò)誤的強(qiáng)大代碼褪贵。
錯(cuò)誤處理
使用 Swift 文檔中的定義:
錯(cuò)誤處理是響應(yīng)程序中的錯(cuò)誤條件并從中恢復(fù)的過程掂之。
Dart 和 Swift 都使用 try/catch
作為處理錯(cuò)誤的技術(shù),但存在一些差異脆丁。
在 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 中槽卫,我們顯式聲明方法何時(shí)可以拋出異常跟压。這是通過 throws
關(guān)鍵字完成的,并且任何錯(cuò)誤都必須符合錯(cuò)誤協(xié)議:
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
}
}
在處理錯(cuò)誤時(shí)歼培,我們在 do/catch
塊內(nèi)使用 try
關(guān)鍵字震蒋。
var account = BankAccount(balance: 100)
do {
try account.withdraw(amount: 50) // ok
try account.withdraw(amount: 200) // throws
} catch AccountError.insufficientFunds {
print("Insufficient Funds")
}
請注意,當(dāng)調(diào)用拋出異常的方法時(shí)躲庄,try
關(guān)鍵字是如何使用的查剖。
錯(cuò)誤本身是強(qiáng)類型的,所以我們可以有多個(gè) catch
塊來覆蓋所有可能的情況噪窘。
try, try?, try!
Swift 提供了一種處理錯(cuò)誤的不那么繁瑣的方法笋庄。
我們可以使用不帶 do/catch
塊的 try?
。這將會(huì)忽略任何異常:
var account = BankAccount(balance: 100)
try? account.withdraw(amount: 50) // ok
try? account.withdraw(amount: 200) // fails silently
或者倔监,如果我們確定某個(gè)方法不會(huì)拋出異常直砂,我們可以使用 try!
:
var account = BankAccount(balance: 100)
try! account.withdraw(amount: 50) // ok
try! account.withdraw(amount: 200) // crash
上面的示例將導(dǎo)致程序崩潰。所以浩习,在生產(chǎn)代碼中不建議使用 try!
静暂,它更適合編寫測試。
總之瘦锹,Swift 中錯(cuò)誤處理的顯式性質(zhì)在 API 設(shè)計(jì)中非常有益籍嘹,因?yàn)樗梢院苋菀椎刂婪椒ㄊ欠窨梢話伋觥?/p>
同樣,在方法調(diào)用時(shí)使用 try
讓我們能關(guān)注到可能拋出錯(cuò)誤的代碼弯院,迫使我們考慮錯(cuò)誤情況辱士。
在這方面,錯(cuò)誤處理讓 Swift 比 Dart 更安全听绳、更可靠颂碘。
泛型
Swift 文檔描述:
泛型代碼使您能夠根據(jù)需求編寫可以使用任何類型的靈活的可重用的函數(shù)和類型。您可以編寫避免重復(fù)的代碼,并以清晰头岔、抽象的方式表達(dá)其意圖塔拳。
兩種語言都支持泛型。
泛型的最常見用例之一是集合峡竣,例如數(shù)組靠抑、集合和映射。
我們可以使用它們來定義我們自己的類型适掰。以下是我們?nèi)绾卧?Swift 中定義通用 Stack 類型:
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 中非常有用非常強(qiáng)大,它們可用于在協(xié)議中定義類型約束和相關(guān)類型类浪。
訪問控制
Swift 文檔描述如下:
訪問控制限制從其他源文件和模塊中的代碼訪問你的代碼载城。此功能可以隱藏代碼的實(shí)現(xiàn)細(xì)節(jié),并指定一個(gè)首選接口费就,通過該接口可以訪問和使用該代碼诉瓦。
Swift 有五個(gè)訪問級別:open
, public
, internal
, file-private
和 private
。
這些關(guān)鍵字用于處理模塊和源文件的上下文中力细。文檔描述如下:
模塊是一個(gè)代碼分發(fā)單元 - 一個(gè)框架或應(yīng)用程序睬澡,它作為一個(gè)單元構(gòu)建和發(fā)布,可以在另一個(gè)模塊中使用 Swift 的
import
關(guān)鍵字導(dǎo)入艳汽。
open
和 public
訪問級別可讓代碼在模塊外部訪問猴贰。
private
和 file-private
訪問級別可讓代碼無法在其定義的文件之外訪問。
例如:
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
Dart 中的訪問級別更簡單河狐,僅限于 public
和 private
。文檔描述如下:
與 Java 不同瑟捣,Dart 沒有關(guān)鍵字
public
馋艺,protected
和private
。如果標(biāo)識符以下劃線_
開頭迈套,則它私有的捐祠。
例如:
class HomePage extends StatefulWidget { // public
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> { ... } // private
Dart 和 Swift 中訪問控制的設(shè)計(jì)目標(biāo)不同。因此桑李,訪問級別非常不同踱蛀。
異步編程:Future
異步編程是 Dart 中真正閃耀的地方。
在處理任務(wù)時(shí)需要某種形式的異步編程贵白,例如:
- 從 Web 下載內(nèi)容
- 與后端服務(wù)通信
- 執(zhí)行長時(shí)間運(yùn)行的操作
在這些情況下率拒,最好不要阻塞執(zhí)行的主線程,這可能會(huì)使我們的程序卡住禁荒。
Dart 文檔描述如下:
異步操作可讓您的程序在等待某個(gè)任務(wù)完成時(shí)去執(zhí)行其它操作猬膨。Dart 使用 Future 對象來表示異步操作的結(jié)果。要使用 Future呛伴,可以使用
async/await
或Future API
勃痴。
作為一個(gè)例子谒所,讓我們看看我們?nèi)绾问褂卯惒骄幊蹋?/p>
- 使用服務(wù)器驗(yàn)證用戶
- 存儲(chǔ)訪問令牌以保護(hù)存儲(chǔ)
- 獲取用戶個(gè)人資料信息
在 Dart 中,這可以通過結(jié)合使用 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
劣领,我們只能通過閉包來實(shí)現(xiàn)這一點(diǎn):
func getUserProfile(credentials: UserCredentials, completion: (_ result: UserProfile) -> Void) {
networkService.signIn(credentials) { accessToken in
secureStorage.storeToken(accessToken) {
networkService.getProfile(accessToken, completion: completion)
}
}
}
由于嵌套的 completion
塊,這導(dǎo)致了“厄運(yùn)金字塔(pyramid of doom)”铁材。在這種情況下尖淘,錯(cuò)誤處理變得非常困難。
在 Dart 中衫贬,上面代碼中的處理錯(cuò)誤只需在代碼周圍添加一個(gè) try/catch
塊到 getUserProfile
方法即可德澈。
作為參考偎窘,有人建議將來向 Swift 中添加 async/await
丛肮。在下面這個(gè) proposal 中有詳細(xì)描述:
在實(shí)現(xiàn)之前,開發(fā)人員可以使用第三方庫固耘,例如 Google 的 Promises
庫葬毫。
異步編程:Stream
Dart 將 Stream
作為核心庫的一部分來實(shí)現(xiàn)镇辉,但 Swift 沒有。
Dart 文檔描述如下:
Stream 是一個(gè)異步事件序列贴捡。
Stream 是響應(yīng)式程序的基礎(chǔ)忽肛,它們在狀態(tài)管理中發(fā)揮著重要作用。
例如烂斋,Stream 是搜索內(nèi)容的絕佳選擇屹逛,每次用戶更新搜索字段中的文本時(shí),都會(huì)發(fā)出一組新結(jié)果汛骂。
Stream 不包含在 Swift 核心庫中罕模。不過第三方庫(如 RxSwift
)提供了對流的支持。
Stream 是一個(gè)廣泛的主題帘瞭,這里不詳細(xì)討論淑掌。
內(nèi)存管理
Dart 使用高級垃圾回收(garbage collection)方案管理內(nèi)存。
Swift 通過自動(dòng)引用計(jì)數(shù)(ARC)管理內(nèi)存蝶念。
這可以保證良好的性能抛腕,因?yàn)閮?nèi)存在不再使用時(shí)會(huì)立即釋放。
然而媒殉,它確實(shí)將部分負(fù)擔(dān)地從編譯器轉(zhuǎn)移到開發(fā)人員担敌。
在 Swift 中,我們需要考慮對象的生命周期和所有權(quán)适袜,并正確使用適當(dāng)?shù)年P(guān)鍵字(weak
, strong
, unowned
)以避免循環(huán)引用柄错。
編譯和執(zhí)行
首先來看看 JIT
和 AOT
編譯器之間的重要區(qū)別:
JIT
JIT
編譯器在程序執(zhí)行期間運(yùn)行,也就是即時(shí)編譯。
JIT
編譯器通常與動(dòng)態(tài)語言一起使用售貌,其中類型不是提前確定的给猾。JIT
程序通過解釋器或虛擬機(jī)(VM)運(yùn)行。
AOT
在運(yùn)行之前颂跨,AOT 編譯器在創(chuàng)建程序期間運(yùn)行敢伸。
AOT 編譯器通常與靜態(tài)語言一起使用,后者知道數(shù)據(jù)的類型恒削。AOT 程序被編譯為本機(jī)機(jī)器代碼池颈,在運(yùn)行時(shí)由硬件直接執(zhí)行。
下面引用了 Wm Leler
的這篇文章:
當(dāng)在開發(fā)期間完成 AOT 編譯時(shí)钓丰,它總是導(dǎo)致更長的開發(fā)周期(對程序進(jìn)行更改和能夠執(zhí)行程序以查看更改結(jié)果之間的時(shí)間)躯砰。 但 AOT 編譯讓程序的運(yùn)行更可預(yù)測,而不會(huì)在運(yùn)行時(shí)暫停進(jìn)行分析和編譯携丁。AOT 編譯的程序也可以快速啟動(dòng)(因?yàn)樗鼈円呀?jīng)被編譯)琢歇。 相反,JIT 編譯提供了更快的開發(fā)周期梦鉴,但可能導(dǎo)致執(zhí)行速度變慢或更加笨拙李茫。特別是,JIT 編譯器的啟動(dòng)時(shí)間較慢肥橙,因?yàn)楫?dāng)程序開始運(yùn)行時(shí)魄宏,JIT 編譯器必須在執(zhí)行代碼之前進(jìn)行分析和編譯。研究表明存筏,如果開始執(zhí)行的時(shí)間超過幾秒鐘宠互,很多人都會(huì)放棄。
作為一種靜態(tài)語言椭坚,Swift 是提前編譯的名秀。
Dart 則同時(shí)支持 AOT
和 JIT
。與 Flutter 一起使用時(shí)藕溅,這提供了顯著的優(yōu)勢〖逃埽看看下面的描述:
在開發(fā)過程中使用 JIT 編譯巾表,使用更快的編譯器。然后略吨,當(dāng)應(yīng)用程序準(zhǔn)備好發(fā)布時(shí)集币,將它編譯為 AOT。因此翠忠,借助先進(jìn)的工具和編譯器鞠苟,Dart 可以提供兩全其美的優(yōu)勢:極快的開發(fā)周期,快速的執(zhí)行和啟動(dòng)時(shí)間。 - Wm Leler
使用 Dart当娱,可以兩全其美吃既。
Swift 有 AOT 編譯的主要缺點(diǎn)。即編譯時(shí)間隨著代碼庫的大小而增加跨细。
對于中型應(yīng)用程序(10K 到 100K 行之間)鹦倚,編譯應(yīng)用程序很容易花費(fèi)幾分鐘。
對于 Flutter 應(yīng)用程序來說并非如此冀惭,無論代碼庫的大小如何震叙,我們都會(huì)不斷進(jìn)行亞秒級熱加載。
其它未涵蓋功能
本文未涵蓋以下功能散休,因?yàn)樗鼈冊?Dart 和 Swift 中非常相似:
- 運(yùn)算符
- 字符串
- Swift 中的可選鏈(在 Dart 中稱為條件成員訪問)媒楼。
并發(fā)
- 并發(fā)編程在 Dart 中通過
isolate
來提供。 - Swift 使用
Grand Central Dispatch(GCD)
和分發(fā)隊(duì)列戚丸。
Dart 中缺失的那些我喜歡的 Swift 特性
Structs
- 帶關(guān)聯(lián)類型的 Enums
Optionals
Swift 中缺失的那些我喜歡的 Dart 特性
- JIT 編譯器
- Future 和
await/async
- Stream 和
yield/async*
結(jié)論
Dart 和 Swift 都是出色的語言划址,非常適合構(gòu)建現(xiàn)代移動(dòng)應(yīng)用程序及其他應(yīng)用程序。
這兩種語言都有自己獨(dú)特的優(yōu)點(diǎn)昏滴。
在比較過移動(dòng)應(yīng)用程序開發(fā)和兩種語言的工具時(shí)猴鲫,我覺得 Dart 占了上風(fēng)。這是由于 JIT 編譯器谣殊,它是 Flutter 中有狀態(tài)熱加載的基礎(chǔ)拂共。
在構(gòu)建應(yīng)用程序時(shí),熱加載可以大大提高生產(chǎn)力姻几,因?yàn)樗梢詫㈤_發(fā)周期從幾秒或幾分鐘加速到不到一秒鐘宜狐。
開發(fā)時(shí)間比計(jì)算時(shí)間更耗費(fèi)資源。
因此蛇捌,優(yōu)化開發(fā)人員的時(shí)間是一個(gè)非常明智的舉措抚恒。
另一方面,我覺得 Swift 有一個(gè)非常強(qiáng)大的類型系統(tǒng)络拌。類型安全性融入 Swift 的所有語言功能俭驮,能更自然地開發(fā)出健壯的程序。
一旦我們拋開個(gè)人偏好春贸,編程語言就是工具混萝。作為開發(fā)人員,我們的任務(wù)是為工作選擇最合適的工具萍恕。
無論如何逸嘀,我們可以希望兩種語言在發(fā)展過程中互相借鑒最好的想法。