Dart vs Swift

| 做者:Andrea Bizzottohtml

| 原文連接:medium.com/coding-with…git

Dart 和 Swift 是我最喜歡的編程語言。我在商業和開源代碼中普遍使用它們。github

本文提供了 Dart 和 Swift 之間的比較,旨在:express

  • 突出顯示二者之間的差別;
  • 做爲開發人員從一種語言轉移到另外一種語言(或使用二者)的參考。

一些背景:編程

  • Dart 支持 Flutter,這是 Google 用於從單一代碼庫構建漂亮的本機應用程序的框架。
  • Swift 經過 iOS,macOS,tvOS 和 watchOS 爲 Apple 的 SDK 提供支持。

如下是兩種語言的主要特徵(Dart 2.1Swift 4.2)的比較。因爲深刻討論每一個功能超出了本文的範圍,所以更多的信息能夠參考各自的文檔。json

目錄

  • 對照表
  • 變量
  • 類型推斷
  • 可變/不可變變量
  • 函數
  • 命名和未命名參數
  • 可選和默認參數
  • 閉包
  • 元組
  • 控制流
  • 集合
  • Nullability & Optionals
  • 繼承
  • 屬性
  • 協議/抽象類
  • Mixins
  • 擴展
  • 枚舉
  • 結構體
  • 錯誤處理
  • 泛型
  • 訪問控制
  • 異步編程:Future
  • 異步編程:Stream
  • 內存管理
  • 編譯和執行
  • 其它未涵蓋功能

對照表

變量

Dart 中變量聲明語法以下:swift

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,而是能夠寫 var x = 0,徹底省略類型 - 編譯器正確地推斷出 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 文檔定義了兩個關鍵字 finalconst,其工做方式以下:

若是您不打算更改變量值,請使用 finalconst,而不是 var 或類型。final 變量只能設置一次;const 變量是編譯時常量。(Const 變量是隱式 final。)final 頂層類型變量或類變量在第一次使用時被初始化。

在 Dart 網站上的這篇文章中能夠找到進一步的解釋:

final 意味着一次賦值。final 變量或字段必須具備 initializer。 一旦賦值,就不能改變 final 變量的值。

在 Swift 中,咱們用 let 聲明常量。

常量聲明會在程序中引入常量命名值。使用 let 關鍵字聲明常量,並具備如下形式:

let constant name: type = expression
複製代碼

常量聲明定義常量名稱和初始化表達式值之間的不可變綁定;設置常量值後,沒法更改。

函數

函數在 Swift 和 Dart 中都是一等公民。

這意味着就像對象同樣,函數能夠做爲參數傳遞,保存爲屬性或做爲結果返回。

做爲初始比較,咱們能夠看到如何聲明不帶參數的函數。

在 Dart 中,返回類型在方法名稱以前:

void foo();
int bar();
複製代碼

在 Swift 中,咱們使用 -> T 表示法做爲後綴。若是沒有返回值(Void),則不須要這樣作:

func foo()
func bar() -> Int
複製代碼

命名及未命名(un-named)參數

兩種語言都支持命名和未命名的參數。

在 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 中,您能夠經過在該參數的類型以後爲參數賦值來爲函數中的任何參數定義默認值。若是定義了默認值,則能夠在調用函數時省略該參數。

func foo(name: String, age: Int = 0, height: Double = 0.0) 
foo(name: "Andrea", age: 34) // name: "Andrea", age: 34, height: 0.0
複製代碼

在 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
複製代碼

閉包

做爲頂層(first-class)對象,函數能夠做爲參數傳遞給其餘函數,或者分配給變量。

在此上下文中,函數也稱爲閉包

這是一個函數的 Dart 示例,它迭代一個 item 列表,使用閉包來打印每一個項目的索引和內容:

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 文檔中的描述:

Set 在集合中存儲相同類型的不一樣值,沒有定義的順序。當項目的順序不重要時,或者當您須要確保元素僅出現一次時,您可使用集合而不是數組。

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 有一個很好的定義:

字典存儲相同類型的鍵與集合中相同類型的值之間的關聯,而沒有特定的排序。每一個值都與惟一鍵相關聯,該惟一鍵充當字典中該值的標識符。

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 對象的方法或變量會致使空指針異常。這是計算機程序中最多見的錯誤來源。

從一開始,Swift 就多了一個選擇,一個內置的語言功能,用於聲明對象是否能夠有值。看看文檔:

您能夠在可能缺乏值的狀況下使用 Optional。Optional 表示兩種可能性:要麼存在值,您能夠解開可選項以訪問該值,或者根本沒有值。

與此相反,咱們可使用非 Optional 變量來保證它們始終具備值:

var x: Int? // optional
var y: Int = 1 // non-optional, must be initialized
複製代碼

注意:說 Swift 變量是可選的與 Dart 變量能夠爲 null 是大體相同。

若是沒有對選項的語言級支持,咱們只能在運行時檢查變量是否爲 null

使用 Optional,咱們在編譯時對這些信息進行編碼。咱們能夠解開 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"
複製代碼

上面的第一個例子在 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"
複製代碼

有 optional 意味着咱們能夠在編譯時而不是在運行時捕獲錯誤。及早捕獲錯誤會讓代碼更安全,錯誤更少。

Dart 缺少對 optional 的支持在某種程度上經過使用斷言(以及用於命名參數的 @required 註釋)獲得緩解。

這些在 Flutter SDK 中普遍使用,但會產生額外的樣板代碼。

類是用面嚮對象語言編寫程序的主要構建塊。

Dart 和 Swift 都支持類,但有一些差別。

語法

這裏有一個帶有 initializer 和三個成員變量的 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 構造函數中使用的 this.[propertyName]。這是用於在構造函數運行以前設置實例成員變量的語法糖。

工廠構造函數

在 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。基於 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
}
複製代碼

請注意 dataerror 參數是如何互斥的。

在 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
}
複製代碼

幾個注意事項:

  • 在這裏,咱們使用斷言來彌補咱們沒有 optional 的事實。
  • 編譯器沒法幫助咱們檢查全部可能的狀況。這是由於咱們不使用 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,咱們有:

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 關鍵字完成的,而且任何錯誤都必須符合錯誤協議:

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
  }
}
複製代碼

在處理錯誤時,咱們在 do/catch 塊內使用 try 關鍵字。

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 提供了一種處理錯誤的不那麼繁瑣的方法。

咱們可使用不帶 do/catch 塊的 try?。這將會忽略任何異常:

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 讓咱們能關注到可能拋出錯誤的代碼,迫使咱們考慮錯誤狀況。

在這方面,錯誤處理讓 Swift 比 Dart 更安全、更可靠。

泛型

Swift 文檔描述:

泛型代碼使您可以根據需求編寫可使用任何類型的靈活的可重用的函數和類型。您能夠編寫避免重複的代碼,並以清晰、抽象的方式表達其意圖。

兩種語言都支持泛型。

泛型的最多見用例之一是集合,例如數組、集合和映射。

咱們可使用它們來定義咱們本身的類型。如下是咱們如何在 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 中很是有用很是強大,它們可用於在協議中定義類型約束和相關類型。

訪問控制

Swift 文檔描述以下:

訪問控制限制從其餘源文件和模塊中的代碼訪問你的代碼。此功能能夠隱藏代碼的實現細節,並指定一個首選接口,經過該接口能夠訪問和使用該代碼。

Swift 有五個訪問級別:open, public, internal, file-privateprivate

這些關鍵字用於處理模塊和源文件的上下文中。文檔描述以下:

模塊是一個代碼分發單元 - 一個框架或應用程序,它做爲一個單元構建和發佈,能夠在另外一個模塊中使用 Swift 的 import 關鍵字導入。

openpublic 訪問級別可以讓代碼在模塊外部訪問。

privatefile-private 訪問級別可以讓代碼沒法在其定義的文件以外訪問。

例如:

public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
複製代碼

Dart 中的訪問級別更簡單,僅限於 publicprivate。文檔描述以下:

與 Java 不一樣,Dart 沒有關鍵字 publicprotectedprivate。若是標識符如下劃線 _ 開頭,則它私有的。

例如:

class HomePage extends StatefulWidget { // public
  @override
  _HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> { ... } // private
複製代碼

Dart 和 Swift 中訪問控制的設計目標不一樣。所以,訪問級別很是不一樣。

異步編程:Future

異步編程是 Dart 中真正閃耀的地方。

在處理任務時須要某種形式的異步編程,例如:

  • 從 Web 下載內容
  • 與後端服務通訊
  • 執行長時間運行的操做

在這些狀況下,最好不要阻塞執行的主線程,這可能會使咱們的程序卡住。

Dart 文檔描述以下:

異步操做可以讓您的程序在等待某個任務完成時去執行其它操做。Dart 使用 Future 對象來表示異步操做的結果。要使用 Future,可使用 async/awaitFuture API

做爲一個例子,讓咱們看看咱們如何使用異步編程:

  • 使用服務器驗證用戶
  • 存儲訪問令牌以保護存儲
  • 獲取用戶我的資料信息

在 Dart 中,這能夠經過結合使用 Futureasync/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)
    }
  }
}
複製代碼

因爲嵌套的 completion 塊,這致使了「厄運金字塔(pyramid of doom)」。在這種狀況下,錯誤處理變得很是困難。

在 Dart 中,上面代碼中的處理錯誤只需在代碼周圍添加一個 try/catch 塊到 getUserProfile 方法便可。

做爲參考,有人建議未來向 Swift 中添加 async/await。在下面這個 proposal 中有詳細描述:

在實現以前,開發人員可使用第三方庫,例如 Google 的 Promises 庫。

異步編程:Stream

Dart 將 Stream 做爲核心庫的一部分來實現,但 Swift 沒有。

Dart 文檔描述以下:

Stream 是一個異步事件序列。

Stream 是響應式程序的基礎,它們在狀態管理中發揮着重要做用。

例如,Stream 是搜索內容的絕佳選擇,每次用戶更新搜索字段中的文本時,都會發出一組新結果。

Stream 不包含在 Swift 核心庫中。不過第三方庫(如 RxSwift)提供了對流的支持。

Stream 是一個普遍的主題,這裏不詳細討論。

內存管理

Dart 使用高級垃圾回收(garbage collection)方案管理內存。

Swift 經過自動引用計數(ARC)管理內存。

這能夠保證良好的性能,由於內存在再也不使用時會當即釋放。

然而,它確實將部分負擔地從編譯器轉移到開發人員。

在 Swift 中,咱們須要考慮對象的生命週期和全部權,並正確使用適當的關鍵字(weak, strong, unowned)以免循環引用。

編譯和執行

首先來看看 JITAOT 編譯器之間的重要區別:

JIT

JIT 編譯器在程序執行期間運行,也就是即時編譯

JIT 編譯器一般與動態語言一塊兒使用,其中類型不是提早肯定的。JIT 程序經過解釋器或虛擬機(VM)運行。

AOT

在運行以前,AOT 編譯器在建立程序期間運行。

AOT 編譯器一般與靜態語言一塊兒使用,後者知道數據的類型。AOT 程序被編譯爲本機機器代碼,在運行時由硬件直接執行。

下面引用了 Wm Leler 的這篇文章:

當在開發期間完成 AOT 編譯時,它老是致使更長的開發週期(對程序進行更改和可以執行程序以查看更改結果之間的時間)。 但 AOT 編譯讓程序的運行更可預測,而不會在運行時暫停進行分析和編譯。AOT 編譯的程序也能夠快速啓動(由於它們已經被編譯)。 相反,JIT 編譯提供了更快的開發週期,但可能致使執行速度變慢或更加笨拙。特別是,JIT 編譯器的啓動時間較慢,由於當程序開始運行時,JIT 編譯器必須在執行代碼以前進行分析和編譯。研究代表,若是開始執行的時間超過幾秒鐘,不少人都會放棄。

做爲一種靜態語言,Swift 是提早編譯的。

Dart 則同時支持 AOTJIT。與 Flutter 一塊兒使用時,這提供了顯著的優點。看看下面的描述:

在開發過程當中使用 JIT 編譯,使用更快的編譯器。而後,當應用程序準備好發佈時,將它編譯爲 AOT。所以,藉助先進的工具和編譯器,Dart 能夠提供一箭雙鵰的優點:極快的開發週期,快速的執行和啓動時間。 - Wm Leler

使用 Dart,能夠一箭雙鵰。

Swift 有 AOT 編譯的主要缺點。即編譯時間隨着代碼庫的大小而增長。

對於中型應用程序(10K 到 100K 行之間),編譯應用程序很容易花費幾分鐘。

對於 Flutter 應用程序來講並不是如此,不管代碼庫的大小如何,咱們都會不斷進行亞秒級熱加載。

其它未涵蓋功能

本文未涵蓋如下功能,由於它們在 Dart 和 Swift 中很是類似:

  • 運算符
  • 字符串
  • Swift 中的可選鏈(在 Dart 中稱爲條件成員訪問)。

併發

  • 併發編程在 Dart 中經過 isolate 來提供。
  • Swift 使用 Grand Central Dispatch(GCD)和分發隊列。

Dart 中缺失的那些我喜歡的 Swift 特性

  • Structs
  • 帶關聯類型的 Enums
  • Optionals

Swift 中缺失的那些我喜歡的 Dart 特性

  • JIT 編譯器
  • Future 和 await/async
  • Stream 和 yield/async*

結論

Dart 和 Swift 都是出色的語言,很是適合構建現代移動應用程序及其餘應用程序。

這兩種語言都有本身獨特的優勢。

在比較過移動應用程序開發和兩種語言的工具時,我以爲 Dart 佔了上風。這是因爲 JIT 編譯器,它是 Flutter 中有狀態熱加載的基礎。

在構建應用程序時,熱加載能夠大大提升生產力,由於它能夠將開發週期從幾秒或幾分鐘加速到不到一秒鐘。

開發時間比計算時間更耗費資源。

所以,優化開發人員的時間是一個很是明智的舉措。

另外一方面,我以爲 Swift 有一個很是強大的類型系統。類型安全性融入 Swift 的全部語言功能,能更天然地開發出健壯的程序。

一旦咱們拋開我的偏好,編程語言就是工具。做爲開發人員,咱們的任務是爲工做選擇最合適的工具。

不管如何,咱們能夠但願兩種語言在發展過程當中互相借鑑最好的想法。

關注咱們

歡迎關注咱們的公衆號:iOS-Tips,也歡迎加入咱們的羣組討論問題(加微信 coldlight_hh)。

相關文章
相關標籤/搜索