iOS開發(fā)-Dart vs Swift

| 作者: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.1Swift 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)鍵字 finalconst,其工作方式如下:

如果您不打算更改變量值肪凛,請使用 finalconst堰汉,而不是 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
}

請注意 dataerror 參數(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-privateprivate

這些關(guān)鍵字用于處理模塊和源文件的上下文中力细。文檔描述如下:

模塊是一個(gè)代碼分發(fā)單元 - 一個(gè)框架或應(yīng)用程序睬澡,它作為一個(gè)單元構(gòu)建和發(fā)布,可以在另一個(gè)模塊中使用 Swift 的 import 關(guān)鍵字導(dǎo)入艳汽。

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

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

例如:

public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}

Dart 中的訪問級別更簡單河狐,僅限于 publicprivate。文檔描述如下:

與 Java 不同瑟捣,Dart 沒有關(guān)鍵字 public馋艺,protectedprivate。如果標(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/awaitFuture API勃痴。

作為一個(gè)例子谒所,讓我們看看我們?nèi)绾问褂卯惒骄幊蹋?/p>

  • 使用服務(wù)器驗(yàn)證用戶
  • 存儲(chǔ)訪問令牌以保護(hù)存儲(chǔ)
  • 獲取用戶個(gè)人資料信息

在 Dart 中,這可以通過結(jié)合使用 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劣领,我們只能通過閉包來實(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í)行

首先來看看 JITAOT 編譯器之間的重要區(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í)支持 AOTJIT。與 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ā)展過程中互相借鑒最好的想法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末允粤,一起剝皮案震驚了整個(gè)濱河市崭倘,隨后出現(xiàn)的幾起案子翼岁,更是在濱河造成了極大的恐慌,老刑警劉巖司光,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件琅坡,死亡現(xiàn)場離奇詭異,居然都是意外死亡飘庄,警方通過查閱死者的電腦和手機(jī)脑蠕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跪削,“玉大人谴仙,你說我怎么就攤上這事∧胙危” “怎么了晃跺?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長毫玖。 經(jīng)常有香客問我掀虎,道長,這世上最難降的妖魔是什么付枫? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任烹玉,我火速辦了婚禮,結(jié)果婚禮上阐滩,老公的妹妹穿的比我還像新娘二打。我一直安慰自己,他們只是感情好掂榔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布继效。 她就那樣靜靜地躺著,像睡著了一般装获。 火紅的嫁衣襯著肌膚如雪瑞信。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天穴豫,我揣著相機(jī)與錄音凡简,去河邊找鬼。 笑死精肃,一個(gè)胖子當(dāng)著我的面吹牛潘鲫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肋杖,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挖函!你這毒婦竟也來了状植?” 一聲冷哼從身側(cè)響起浊竟,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎津畸,沒想到半個(gè)月后振定,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肉拓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年后频,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暖途。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卑惜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驻售,到底是詐尸還是另有隱情露久,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布欺栗,位于F島的核電站毫痕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏迟几。R本人自食惡果不足惜消请,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望类腮。 院中可真熱鬧臊泰,春花似錦、人聲如沸存哲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祟偷。三九已至察滑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間修肠,已是汗流浹背贺辰。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嵌施,地道東北人饲化。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像吗伤,于是被迫代替她去往敵國和親吃靠。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容