歡迎光臨
每天分享高質量文章

Dart vs Swift

來自:知識小集(ID:iOS-Tips)
作者:Andrea Bizzotto
原文鏈接:https://medium.com/coding-with-flutter/dart-vs-swift-a-comparison-6491e945dc17

Dart 和 Swift 是我最喜歡的編程語言。我在商業和開原始碼中廣泛使用它們。

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

  • 突出顯示兩者之間的差異;

  • 作為開發人員從一種語言轉移到另一種語言(或使用兩者)的參考。

一些背景:

  • Dart 支持 Flutter,這是 Google 用於從單一代碼庫構建漂亮的本機應用程式的框架。

  • Swift 通過 iOS,macOS,tvOS 和 watchOS 為 Apple 的 SDK 提供支持。

以下是兩種語言的主要特征(Dart 2.1 和 Swift 4.2)的比較。由於深入討論每個功能超出了本文的範圍,因此更多的信息可以參考各自的文件。

目錄

  • 對照表

  • 變數

  • 型別推斷

  • 可變/不可變變數

  • 函式

  • 命名和未命名引數

  • 可選和預設引數

  • 閉包

  • 元組

  • 控制流

  • 集合

  • Nullability & Optionals

  • 繼承

  • 屬性

  • 協議/抽象類

  • Mixins

  • 擴展

  • 列舉

  • 結構體

  • 錯誤處理

  • 泛型

  • 訪問控制

  • 異步編程:Future

  • 異步編程:Stream

  • 記憶體管理

  • 編譯和執行

  • 其它未涵蓋功能

對照表

變數

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


編譯器會自動解析 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 文件定義了兩個關鍵字 final 和 const,其工作方式如下:

如果您不打算更改變數值,請使用 final 或 const,而不是 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”341.84)


在 Dart 中,我們通過省略花括號({})來定義未命名的引數:

void foo(String name, int age, double height);
foo(‘Andrea’341.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 = 0double height = 0.0]);
foo(‘Andrea’34); // name: ‘Andrea’, age: 34, height: 0.0
// named optional parameters
void foo({String name, int age = 0double 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”341.84)
print(t.0// prints “Andrea”
print(t.1// prints 34
print(t.2// prints 1.84


Dart 中有一個單獨三方包支持元組:

const t = const Tuple3<Stringintdouble>(‘Andrea’341.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 = [123]; // list literal
list.length; // 3
list[1]; // 2


Swift 中陣列是內置型別:

var emptyArray = [Int]() // empty array
var array = [123// 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()
var fruits = Set([“apple”“banana”])


Maps / Dictionaries

Swift 文件對 map/dictionary 有一個很好的定義:

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

Dart 中的 map 定義如下:

var namesOfIntegers = MapString>(); // 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<dynamicdynamic> 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
}


請註意 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
}


幾個註意事項:

  • 在這裡,我們使用斷言來彌補我們沒有 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 {
  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 有五個訪問級別:openpublicinternalfile-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 沒有關鍵字 publicprotected 和 private。如果識別符號以下劃線 _ 開頭,則它私有的。

例如:

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/await 或 Future API

作為一個例子,讓我們看看我們如何使用異步編程:

  • 使用服務器驗證用戶

  • 儲存訪問令牌以保護儲存

  • 獲取用戶個人資料信息

在 Dart 中,這可以通過結合使用 Future 和 async/await 來完成:

Future 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 中有詳細描述:

  • Async/Await proposal for Swift

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

異步編程:Stream

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

Dart 文件描述如下:

Stream 是一個異步事件序列。

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

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

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

Stream 是一個廣泛的主題,這裡不詳細討論。

記憶體管理

Dart 使用高級垃圾回收(garbage collection)方案管理記憶體。

Swift 通過自動取用計數(ARC)管理記憶體。

這可以保證良好的性能,因為記憶體在不再使用時會立即釋放。

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

在 Swift 中,我們需要考慮物件的生命周期和所有權,並正確使用適當的關鍵字(weakstrongunowned)以避免迴圈取用。

編譯和執行

首先來看看 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 中通過 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 的所有語言功能,能更自然地開發出健壯的程式。

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

無論如何,我們可以希望兩種語言在發展過程中互相借鑒最好的想法。

    已同步到看一看
    赞(0)

    分享創造快樂