为有牺牲多壮志,敢教日月换新天。

Swift对比Dart

热烈欢迎,请直接点击!!!

进入博主App Store主页,下载使用各个作品!!!

注:博主将坚持每月上线一个新app!!!

前言 

Dart、Swift 目前是开发用的比较多的语言,他们都是比较现代化的语言,在语法方面都比较像,本文会对其语言进行一下对比,便于记忆和语言过渡。本次的对比是基于最新Dart和Swift的版本进行。

预览

变量声明

Swift  中语法如下: 

var name:String
var age:Int
var height:Double

//赋值语法
var name = "Summer"
var age = 6
var height = 1.12

Dart 中语法如下:

String name;
int age;
double height;

//赋值语法
var name = "Summer";
var age = 6;
var height = 1.12;

由于两种语言都有类型推断,所以可以通过表达式中的值推断出类型。 Dart 需要增加分号, Swift 中可以不加分号。

更多类型,对比下文 Swift 会更严格一些对于混合型的需要加 Any 表明,个人其实也同意 Swift 的做法,如果类型为 Any 意味着你将失去编译器的类型检查机制,隐藏的挖坑。对于动态类型 Swift 使用 Any 关键词,而 Dart 使用 dynamic 。 在读取 JSON 等数据时,通常会使用dynamic类型。

  • Swift 

var mapArgsMix = ["arg1":"hello", "arg2":"world", "arg3":2020] as [String:Any]
var listArgsMix = ["hello","world",2020] as [Any]
var mapArgs = ["arg1":"hello", "arg2":"world"] //[String:String]
var listArgs = ["hello","world"]  //[String]
  • Dart  

var maxArgs = {"arg1":"hello", "arg2":"world","arg3":2020} // Map<String,Object>
var listArgs = ["hello","world",2020] //List<Object>

Swift 的类型扩展阅读链接,Dart 的在此链接

Dart 文档有如下描述:

分析器可以推断字段、方法、局部变量和大多数泛型类型参数的类型。当分析器没有足够的信息来推断特定类型时,将使用动态类型。

Swift 文档中有如下描述:

Swift 广泛使用类型推断,允许您省略代码中许多变量和表达式的类型或部分类型。例如,不是写 var x:Int = 0,而是可以写 var x = 0,完全省略类型 - 编译器正确地推断出 x 为 Int 类型的值。

可变与不可变变量声明

我们编程过程经常用到可变变量与不可变变量,两者都可以通过 var 来声明可修改变量,声明不可变变量的语法有所不同。 

  • Swift

var age = 60
age = 18
  • Dart

var age = 60;
age = 18

对于 Swift 使用 let 关键字, Dart 使用 final 和 const 来声明。 let 和 final 的作用相似,都可以声明不可变变量,而且 Swift 的声明非可选类型和 Dart 的 final 声明的变量必须初始化,否则会编译报错。 Dart 的 const 是编译时常量,相对 final 会增加严格,如同是 final 和 const 声明的数组,前者声明的数组变量内部可以修改,但是 const 声明的数组内部也不允许修改,而 Swift 用 let 声明的数组编译时就不允许访问下标,想一想 NSArray  和 NSMutaleArray 关系就理解了。

  • Swift

let age = 60
age = 18 //Cannot assign to value: 'age' is a 'let' constant

let children = ["summer","winter"]
children[0] = "spring" //Cannot assign through subscript: 'children' is a 'let' constant
  • Dart

final age = 60;
age = 18; //'age' can't be used as a setter because it's final.

final child = ["summer","winter"];
child[0] = "Spring";

const balls = ["football","basketball"];
balls[0] = ["ping-pong"];// 运行时报错 UnsupportedError (Unsupported operation: Cannot modify an unmodifiable list)

注意:Dart 文档定义了两个关键字 final 和 const,其工作方式如下:

如果您不打算更改变量值,请使用 final 或 const,而不是 var 或类型。final 变量只能设置一次;const 变量是编译时常量。(Const 变量是隐式 final。)final 顶层类型变量或类变量在第一次使用时被初始化。

在 Dart 网站上的这篇文章中可以找到进一步的解释:

final 意味着一次赋值。final 变量或字段必须具有 initializer。 一旦赋值,就不能改变 final 变量的值。

在 Swift 中,我们用 let 声明常量。

函数声明

Swift 和 Dart 中函数都是一等公民,因此都可以作为参数传递,变量声明,作为返回值等。

  • Swift,在 Swift 中,我们使用 -> T 表示法作为后缀。如果没有返回值(Void),则不需要这样做

func foo()
func bar() -> Int
  • Dart ,在 Dart 中,返回类型在方法名称之前:

void foo();
int bar();

查看关于函数的介绍更多: SwiftDart

参数命名与未命名(un-named)参数 

Swift 和 Dart 都支持参数命名和非参数命名的方式传参,两种实现方式都很友好。看几个例子。

  • Swift

//带参数名的传参,参数默认为命名参数
func foo(name: String, age: Int, height: Double)
foo(name: "summer", age: 6, height: 1.12)

//不带参数传参,使用下划线(_) 作为外部参数来定义未命名的参数
func foo(_ name: String,_ age: Int,_ height: Double)
foo("summer",16,1.12)
  • Dart

//带参数名的传参,使用花括号({})定义命名参数
void foo({String name, int age, double height});
foo(name: "summer", age: 6, height: 1.12);
foo(age: 6,name: "summer",height: 1.12);//支持参数顺序变换

//不带参数传参,通过省略花括号({})来定义未命名的参数
void foo(String name, int age, double height); // 对比上面的函数去掉了大括号
foo("summer",16,1.12);

从调用的角度看都是一样的,Dart 还有个好用的语法糖。如 foo({ this.name, this.age, this.height }) ,可以保证传参名字和内部变量名称相同。

函数参数可选和默认值

两者都支持参数可选,也支持设置默认值。设置默认值后可以通过传参时指定值覆盖掉默认值,提供这个语法很大的好处是读代码的时候可以一目了然,那些值不传的话默认值会不会设置。

  • Swift,在 Swift 中,您可以通过在该参数的类型之后为参数赋值来为函数中的任何参数定义默认值。如果定义了默认值,则可以在调用函数时省略该参数。

func foo(name: String, age: Int = 0, height: Double = 0.8)
foo(name:"spring",age:3)
  • Dart,可选参数可以是位置参数,也可以是命名参数,但不能同时。

void foo(String name, [int age = 0, double height = 0.8]);//指定顺序
void foo(String name, {int age = 0, double height = 0.8});//同上
void foo({String name, int age = 0, double height = 0.8});//不指定顺序

foo(name:"spring",age:3);

更多阅读查看这里 Swift 文档, Dart 的文档

闭包

由于函数作为一等公民,因此可以做为参数进行传递,

作为顶层(first-class)对象,函数可以作为参数传递给其他函数,或者分配给变量。

在此上下文中,函数也称为闭包。

  • Swift

let list = ["car","bike","motorcycle"]
list.forEach { (item) in
    print("this transport is \(item)")
}

//或者如下
list.forEach({print("this transport is \($0)")})

在 Swift 中可以用 $0 、 $1 代替第几个参数,可以简化写法

  • Dart,注意使用箭头符号(=>)。这可以代替花括号内的单个 return 语句。

final list = ["car","bike","motorcycle"];

list.forEach((element) {print("this transport is $element");});
//或者
list.forEach((element)=>print("this transport is $element"));

这里如果直接 return 的匿名函数写法可以简化为 ()=>dosomething 闭包经常用在异步编程中作为回调参数,我们将会在异步编程这一节详细介绍。

查看更多:Swift 闭包介绍,Dart 异步编程

元组(Tuple)

元组在函数返回多个值的时候非常有帮助, Dart 目前还不支持,有第三方库的实现,但是不推荐,可读性没有 Swift 的元组清晰,建议返回定义的数据结构解决这个问题。

  • Swift,元组将多个值分组为单个复合值。元组中的值可以是任何类型,并且不必具有相同的类型。

let tpHasName = (max: 3, min: 0)//推荐写法print("max is \(tpHasName.max) and min is \(tpHasName.min)")
let tpNoName = (3,0)
print("first is \(tpNoName.0) and second is \(tpNoName.1)")

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

控制流

Swift 的 IF 条件和循环控制都还中规中矩,到 Switch 的时候就放飞自我了。Dart 相对来说没有那么多语法,相对简单。

循环

  • Swift

let list = ["car","bike","motorcycle"]
for item in list {
    print(item)
}

for i in 0..<list.count {
    print(list[i])
}

for i in 0...2 {
    print(list[i])
}

var i = 0
while i < list.count {
    print(list[i])
    i += 1;
}

var j = 0
repeat {
    print(list[j])
    j += 1
}while j < list.count
  • Dart

var list = ["car","bike","motorcycle"];

void loops(){
    for(var item in list){
      print(item);
    }
    
    for(int i=0;i<list.length;i++){
      print(list[i]);
    }


    var k = 0;
    do {
      print(list[k]);
      k++;
    } while (k < list.length);

    var j = 0;
    while (j < list.length){
      print(list[j]);
      j++;
    }
  }

IF

条件的判断上 Swift 不用 () 来包含条件,这与 let 语法解包可能有一定关系。 Dart 采用的是传统的 () 包含条件。

  • Swift

var name:String? = "summer"
if let n = name {
    print(n)
}

var num = 7
if num > 10 {
    print("num is big than 10")
} else if num < 5 {
    print("num is smaller than 5")
} else {
    print("this num between 5 and 10")
}
  • Dart

var num = 7;

if (num > 10){
    print("num is big than 10")
} else if (num < 5){
    print("num is smaller than 5")
} else {
    print("this num between 5 and 10")
}

Switch

结合枚举 Swift 可以用 Switch 玩出各种特色,除此之外还可以 switch 字符,元组, case 可以增加多个条件,通过默认不需要写 break ,默认只执行匹配到第一个 case ,可以通过 fallthrough 像其他语言一样匹配下一个 case 。Dart 相对比较简单就是普通的语句。

  • Swift

var num = 8

switch num {
case 0...9:
    print("we meet in 0..9")
    fallthrough
case 7..<10:
    print("we meet in >=7 && < 10")
case 5,6,7,8:
    print("we meet  in 5 6 7 8")
default:
    print("we meet nothing")
    break;
}

var tp = (x:0,y:10)
switch tp {
case (_,10):
    print("y is match")
case (let x, 10):
    print("y is match and x is \(x)")
case let (x,y) where x == y :
    print("x is equal to y and x is \(x) y is \(y)")
case let (x,y):
    print("x:\(x) and y:\(y) is normal")
}
  • Dart

    var lang = "ch";
    switch (lang) {
      case "ch":
        print('language is ch');
        break;
      case "en":
        print('language is en');
        break;
      default:
        print("language is $lang");
    }

其他

Swift 中还有一些独特的关键字,如 defer , guard 查看官网文档

集合类型

集合类型是非常常用的类型。

数组(list)

  • Swift,数组是内置类型。

var emptyArray = [Int]()
var list = [1, 2, 4]
list.count // 3
list[1] // 1
  • Dart,使用 List 对象来表示数组。

var emptyArray = <int>[];
var list = [1, 2, 4];
list.length; // 3
list[1] // 1

集合(Sets) 

  • Swift,Set 在集合中存储相同类型的不同值,没有定义的顺序。当项目的顺序不重要时,或者当您需要确保元素仅出现一次时,您可以使用集合而不是数组。map 称为字典。

var emptySets = Set<String>()
var transpot = Set<String>(["car", "bike", "motorcycle"])
  • Dart

var emptySets = <String>{};
var transport = {"car", "bike", "motorcycle"};

键值类型(Map)

  • Swift,字典存储相同类型的键与集合中相同类型的值之间的关联,而没有特定的排序。每个值都与唯一键相关联,该唯一键充当字典中该值的标识符。

var emptyMap = [String:Int]();
var nameOfAge = ["summer" : 6, "spring" : 3];
  • Dart

var emptyMap = Map<String,Int>();
var emptyMap = <String,Int>();

var nameOfAge = {"summer" : 8, "spring" : 3};

更多的介绍查看这里 Swift 集合类型,Dart 集合类型

空值和可选类型

在Dart中,任何对象都可以为 null。在 Dart 访问 null 类型会产生空引用异常,Swift 自诞生开始就支持了 optional 类型,本来在运行时才能进行的检查,我们可以在编译期就支持空值的处理,Dart 目前也已经支持,截止到这片文章发布,该功能还处于 测试阶段,Dart 团队还不建议在生产环境使用。

注意:说 Swift 变量是可选的与 Dart 变量可以为 null 是大致相同。如果没有对选项的语言级支持,我们只能在运行时检查变量是否为 null。使用 Optional,我们在编译时对这些信息进行编码。我们可以解开 Optional 以安全地检查它们是否包含值

  • Swift,您可以在可能缺少值的情况下使用 Optional。Optional 表示两种可能性:要么存在值,您可以解开可选项以访问该值,或者根本没有值。

var x:Int? //optional
var y:Int = 1 //non-optional must be initialized
var z:Int? = 2

func foo(name:String?){
    guard let name = name else {
        print("no value")
        return
    }
    print(name)
    print(z!)//如果确定不是null,可以使用 ! 解包
}

foo(name: nil) //no value
foo(name:"summer") //summer
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,如果我们知道变量必须有值,我们可以使用 non-optional 的值。

var int? x;
var double? f = 1.3;

print(f!) //已知 f 不为空 可以直接解包
print(x?.floor()) // 如果x 是null,则返回null,否者返回运行的结果
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"

可以看到除了声明方式有些不一样,其他方面都很像。其实这也说明一个问题,可选类型的发明是为了解决空引用的问题而诞生的,如果方式方法相同,本质是一样的解决方案。学习这些语言特性的时候多从设计语言的人的角度看看,就很容易理解,各种设计是为了解决问题而诞生的,不是为了存在而存在。更多阅读 Swift 可选类型链, Dart null-safety

有 optional 意味着我们可以在编译时而不是在运行时捕获错误。及早捕获错误会让代码更安全,错误更少。Dart 缺乏对 optional 的支持在某种程度上通过使用断言(以及用于命名参数的 @required 注释)得到缓解。这些在 Flutter SDK 中广泛使用,但会产生额外的样板代码。

类(Class)

类在两个语言中都有实现,语法有些区别。由于 Swift 中对可选类型的约定,如果是非可选类型必须在初始化的时候设置值,否则会编译不通过。比如下面代码除 name 之外其他的属性必须都设置值。

  • Swift

class Person{
    var name:String?
    let age:Int
    let height:Double
    
    init(age: Int, height: Double) {
        self.age = age
        self.height = height
    }
}

let p = Person(age: 6, height: 1.12)
  • Dart

class Person{
  Person(this.name,this.age,this.height);
  final String name;
  final int age;
  final double height;
}

注意这里, this.[propertyname] 是 Dart 提供的语法糖,可以快速实现原始属性的赋值。

构造器功能扩展

  • Swift

Swift 结构体和类都支持构造器,Swift 中默认是不调用父类的 init 方法的,类还支持 convenience ,更多的内容查看 Swift 的 初始化(非常推荐)介绍。

class Human{
    var legs:String?
    var hands:String?
    init(legs:String?,hands:String?) {
        self.legs = legs
        self.hands = hands
    }
}

class Person:Human{
    var name:String?
    let age:Int
    let height:Double
    
    override init(legs: String?, hands: String?) { //重写父类的初始化方法
        self.age = 0;
        self.height = 0;
        super.init(legs: legs ?? "biglegs", hands: hands ?? "bighands");//初始化完自己后再调用父类
    }
    
    init(name: String?, age: Int, height: Double) { //designated initializer
        self.name = name
        self.age = age
        self.height = height
        super.init(legs: "humanHands", hands: "humanLegs") //调用父类designated initializer
        self.hands = "bobyhands" //访问父类属性必须在初始化父类后再访问
    }
    
    init(age:Int,height:Double){ //designated initializer
        self.age = age
        self.height = height
        super.init(legs: "humanHands", hands: "biglegs")
    }
    
    convenience init() { //convenience方法
        self.init(age:10,height:1.1)
        self.name = "noname"
    }
}
convenience方法
1、swift中构造函数有特殊规定:分为designed和convenience函数,其中convenience函数必须表用类自身的构造函数,通常是init(......),也就是说convenience是对构造函数的进一步扩展
2、每一个convenience最终的调用都是designed函数
3、每一个designed调用的父类必须是一个designed
init(style: UITableViewStyle) {
    super.init(nibName:nil,bundle:nil)
       // Custom initialization
       data = ["1","2","3","4","5"]
    }
    convenience init(style: UITableViewStyle, data:NSArray ) {
       self.init(style: style) //designed函数必须最先调用
       self.data = data
    }
convenience:便利,使用convenience修饰的构造函数叫做便利构造函数
便利构造函数通常用在对系统的类进行构造函数的扩充时使用。
便利构造函数的特点:
1、便利构造函数通常都是写在extension里面
2、便利函数init前面需要加载convenience
3、在便利构造函数中需要明确的调用self.init()
extension UIButton {
    //Swift中类方法是以Class开头的方法,类似于OC中+开头的方法
    class func createButton(imageName: String,bgImageName:String) -> UIButton{
        let btn = UIButton(type: .custom)
        btn.setImage(UIImage(named: imageName), for: .normal)
        btn.sizeToFit()
        return btn
    }
 
    convenience init(imageName: String,bgImageName: String){
        self.init()
        setImage(UIImage(named: imageName), for: .normal)
        setBackgroundImage(UIImage(named: bgImageName), for: .normal)
        sizeToFit()
    }
}
  • Dart

Dart 可以通过 facory 关键词声明一个构造器不一定返回一个新的实例回来,比如单例或者来自 Cache 。

class SingletenSimple{

  static get instance => _getInstance();
  factory SingletenSimple() => _getInstance();
  factory SingletenSimple.single() => _getInstance();// 示例一下其他写法

  static  SingletenSimple _instance;
  SingletenSimple._();
  static SingletenSimple _getInstance(){
    if(_instance == null){
      _instance = SingletenSimple._();
    }
    return _instance;
  }
}

在 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 中,接口称为协议。

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; }

继承,协议,Mixin

Swift 支持单继承类,也支持继承协议,Dart 支持 Mixin 混合模式,实际是和 Swift 一样的效果,继承是多继承,但是 superclass  只有一个。另外截止到当前版本,Dart 没有协议支持,协议实现需要用 abstract 抽象类代替。Dart 的官网对 Mixin 定义

每个对象都是类的实例,都派生自 Object 。基于 Mixin 的继承实现,虽然每个类(除了 Object)都只有一个父类(superclass),但是类的内部可以被其他类复用。

思考一下, Mixin 是不是对多重继承的改进?子继承父的概念没有变,但是又扩大了继承的复用面。从这方面看 Swift 其实也实现了 Mixin ,因为 Swift 支持协议扩展,协议又可以被继承。

  • Swift

protocol Runable{
    func run();
}

extension Runable{
    func run(){
        print("I am running...")
    }
}

class Human{
    var legs:String?
    var hands:String?
}

class Person:Human,Runable{ //继承实现
    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
    }
}

let p = Person(name: "summber", age: 6, height: 1.12);
p.run() // I am running...
  • Dart

abstract class Runable {
  void run(){
    print('I am running...');
  }
}

abstract class Human {
  String legs;
  String hands;
}

class Person extends Human with Runable { //extends 是继承,with 是 mixin 的写法
  String name;
  int age;
  double height;

  Person(this.name,this.age,this.height);
}

var p = Person("summber",6,1.12);
p.run(); // I am running...

更多阅读请查看 Swift 类与结构体,Dart 类的结构

在 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'

属性

Dart 中称为 instance variables(实例变量), Swift 中称为 property (属性)。前面例子里有很多的声明了,这里主要关注一下 get set 的方法和 Swift 中特殊的 Property observers 。

  • Swift

class Circle {
    init(radius:Double){
        self.radius = radius
    }
    var radius:Double // stored property
    var diameter:Double{ // read-only computed property
        get {
            return radius * 2.0
        }
        set {
            radius = newValue / 2.0
        }
    }
    
}
  • Dart

class Circle{
  
  double radius;

  Circle(this.radius);

  double get diameter{
    return radius * 2.0;
  }
  set diameter(double newvalue){
    radius = newvalue / 2.0;
  }
}
class Circle {
  Circle({this.radius});
  final double radius; // stored property
  double get diameter => radius * 2.0; // computed property
}

Swift 支持属性值观察,类似 KVO 

  • Swift

    var diameter:Double{
        willSet(newDiameter){
           print("will set old: \(diameter), new: \(newDiameter)")
        }
        didSet {
            print("did set old:\(oldValue), new: \(diameter)")
        }
    }

阅读更多 Swift 属性篇 Dart 的 instance variablesgetter & setter

扩展

Swift 支持对类,现有基本类型,结构体,枚举,协议进行扩展。Swift 可以写出如下的代码,更多的内容查看 Swift 扩展。自 Dart 2.7 开始也支持扩展

  • Swift

extension Int {
    func addOne() -> Self {
       return self + 1
    }
}

print(3.addOne()) // 4 
  • Dart

extension numparer on int {
  int addone(){
    return this + 1;
  }
}
var num = 3;
print(3.addone()); // 4

枚举

Swift 的枚举非常强大,Dart 就只有基本支持。由于 Swift 的枚举表达能力过强,我们这里只讨论基本的样式,更多的请查看 Swift 的枚举

  • Swift

//普通版本
enum Human{
    case man
    case wowan
}

class Person{
    var name:String
    var age:Int
    init(name:String,age:Int) {
        self.name = name
        self.age = age
    }
}

class Girl:Person{
    var bag:String?
}

//常见版本
enum Human{
    case man(he:Person)
    case woman(she:Girl)
    func turn() -> Self {
        switch self {
        case .man(let he):
            print("he \(he.name) will become she")
            return .woman(she: Girl(name: he.name, age: he.age))
        case .woman(let girl):
            print("she \(girl) will become he")
            return .man(he: Person(name: girl.name, age: girl.age))
        }
    }
}

let aMan = Human.man(he: Person(name: "Tom", age: 20))
aMan.turn() //he Tom will become she
enum NetworkResponse {
  case success(body: Data) 
  case failure(error: Error)
}
// 请注意 data 和 error 参数是如何互斥的。 switch (response) { case .success(let data): // do something with (non-optional) data case .failure(let error): // do something with (non-optional) error }
  • Dart

enum Person {
    man,
    woman,
}
// 在 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 中的数据和模型,从而产生具有更少错误的强大代码。

文档中的描述如下:值类型是一种类型,其值在被赋值给变量或常量时被复制,或者在传递给函数时被复制。 Swift 中所有结构和枚举都是值类型。这意味着您创建的任何结构和枚举实例 - 以及它们所有的值类型的属性 - 在代码中传递时始终会被复制。 与值类型不同,引用类型在分配给变量或常量时或者传递给函数时不会被复制。而是使用对同一现有实例的引用。

struct Person {
    var name:String
    let age:Int
    let height:Double
}

let p = Person(name: "Summer", age: 6, height: 1.12)
var q = p
q.name = "Spring"

print(p.name)// Summer

错误处理

错误处理是响应程序中的错误条件并从中恢复的过程。

Swift 和 Dart 都采用了 try/catch 的方式处理错误。两者有少量的不同,Swift 会更严格一些,要产生异常的代码方法名以 throws 结尾,异常的类型必须继承 Error , Dart 可以 throw 任何类型。

  • Swift,在 Swift 中,我们显式声明方法何时可以抛出异常。这是通过 throws 关键字完成的,并且任何错误都必须符合错误协议

enum AccountError: Error {
  case insufficientFunds
}

class BankAccount {
  var balance: Double
  init(balance: Double) {
    self.balance = balance
  }
func withdraw(amount: Double) throws {
//相对Dart 增加 throws 关键字 if amount > balance { throw AccountError.insufficientFunds } balance -= amount } } var account = BankAccount(balance: 100) do { try account.withdraw(amount: 50) // ok try account.withdraw(amount: 200) // throws } catch AccountError.insufficientFunds { print("Insufficient Funds") }

Swift 有三种 try try? try! 的使用方式, try? 可以不用 try/catch ,而 try! 适合在完全确定不会有异常的情况下使用,但是不建议在生产环境使用,如果出现异常会发生 Crash,可以写测试案例的使用。

var account = BankAccount(balance: 100)
try? account.withdraw(amount: 50) // ok
try? account.withdraw(amount: 200) // 不会产生异常

var account = BankAccount(balance: 100)
try! account.withdraw(amount: 50) // ok
try! account.withdraw(amount: 200) // crash
  • Dart

class BankAccount{
  double balance;
  BankAccount(this.balance);

  void withdraw(double account){
    if(account > balance){
      throw(Exception('Insufficient funds'));
    }
    balance -= account;
  }
}

var account = BankAccount(balance: 100);
try {
  account.withdraw(50); // ok
  account.withdraw(200); // throws
} catch (e) {
  print(e); // prints 'Exception: Insufficient funds'
}

总体来看 Swift 语言设计的更加健壮一些,可以清晰的看到调用的方法是否会有异常。查看更多的内容 Swift 如何处理错误, Dart 异常处理

泛型

泛型代码使您能够根据需求编写可以使用任何类型的灵活的可重用的函数和类型。您可以编写避免重复的代码,并以清晰、抽象的方式表达其意图。两者都支持泛型, Swift 对泛型的支持更好一些,支持泛型约束和 Associated Type

  • 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 泛型, Dart 的泛型

访问控制

访问控制对于封装代码非常有帮助。Swift 有 5 级控制,分别是 open public  internal file-private private 。Dart 则只有 public 和 private 两种,使用 _ 区分。

  • Swift

- open 和 public 都可以被模块以外的代码调用,不同的是 open 修饰的 class 和成员变量可以被继承和修改,而 public 不可以。因此是定义模块与外界接口使用的关键词。

- internal 是默认的控制等级,可以不用写。可以在模块以内访问,但不可以在模块意外访问

- file_private 修饰的方法和属性只能在当前的定义的文件内访问

- private 修饰的方法和属性只能在当前类中访问

 

这些关键字用于处理模块和源文件的上下文中。文档描述如下:

模块是一个代码分发单元 - 一个框架或应用程序,它作为一个单元构建和发布,可以在另一个模块中使用 Swift 的 import 关键字导入。

open 和 public 访问级别可让代码在模块外部访问。

private 和 file-private 访问级别可让代码无法在其定义的文件之外访问。

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

public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}
  • Dart,与 Java 不同,Dart 没有关键字 publicprotected 和 private。如果标识符以下划线 _ 开头,则它私有的。

class HomePage extends StatefulWidget { // public
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> { ... } // private,其他文件也无法访问

阅读更多 Swift 的 访问控制,Dart 的库与可见性

异步编程:Future

在处理任务时需要某种形式的异步编程,例如:

  • 从 Web 下载内容
  • 与后端服务通信
  • 执行长时间运行的操作

Dart 文档描述如下:

异步操作可让您的程序在等待某个任务完成时去执行其它操作。Dart 使用 Future 对象来表示异步操作的结果。要使用 Future,可以使用 async/await 或 Future API

终于到了 Swift 不擅长的,而 Dart 擅长的一章了。 来自谷歌 Go 语言浓浓的影子 - microTask , Dart 运行时可以开一篇文章单独介绍了,有兴趣的可以先阅读掘金上一篇高质量的文章,官网的资料也不错在此。前两篇文章都没有讲明白异步编程到底是什么,异步编程的的神秘面纱其实在 ES6 中有可以参考的实现,可以阅读阮一峰老师的文章,答案应该是 协程 。Swift 5.5也支持异步编程,应该也会在最近几个版本实现。

下面 Dart 的代码实现了从服务端获取 token ,然后保存到本地,在通过 token 获取用户信息。

Future<UserProfile> getUserProfile(UserCredentials credentials) async {
  final accessToken = await networkService.signIn(credentials);
  await secureStorage.storeToken(accessToken, forUserCredentials: credentials);
  return await networkService.getProfile(accessToken);
}

Swift5.5版本已经支持异步编程:

func listPhotos(inGallery name: String) async -> [String] {
    let result = // ... some asynchronous networking code ...
    return result
}

let photoNames = await listPhotos(inGallery: "Summer Vacation")
let sortedNames = photoNames.sorted()
let name = sortedNames[0]
let photo = await downloadPhoto(named: name)
show(photo)
import Foundation

let handle = FileHandle.standardInput
for try await line in handle.bytes.lines {
    print(line)
}

Dart 的 Steams 是对异步事件的支持,在 flutter 的状态管理上有出色的应用。Steams 也会单独写一篇文章进行探索,关于 Steams 的内容非常多,谷歌的程序员还提出了 BLoC 模式。

异步编程:Stream

Dart 将 Stream 作为核心库的一部分来实现,但 Swift 没有。

Dart 文档描述如下:

Stream 是一个异步事件序列。

Stream 是响应式程序的基础,它们在状态管理中发挥着重要作用。

例如,Stream 是搜索内容的绝佳选择,每次用户更新搜索字段中的文本时,都会发出一组新结果。

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

Stream 是一个广泛的主题,这里不详细讨论。

内存管理

Swift 沿用了 iOS 一贯的传统 ARC(自动引用计数),Dart 采用的是垃圾回收。对于 Swift 要小心循环引用,更加详细的内容查看这里

Swift 通过自动引用计数(ARC)管理内存。这可以保证良好的性能,因为内存在不再使用时会立即释放。然而,它确实将部分负担地从编译器转移到开发人员。

在 Swift 中,我们需要考虑对象的生命周期和所有权,并正确使用适当的关键字(weak, strong, unowned)以避免循环引用。

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

编译和执行

首先科普一下 AOT(ahead-of-time)和 JIT (just-in-time)

  • JIT

在应用启动的时候编译,运行时不断优化生成代码,启动速度会慢一些。适用于动态语言,比如 JavaScript 就是 JIT 方式运行,Dart 采用 JIT 运行时速度与JavaScript 差不多。通常需要运行时环境。

JIT 编译器在程序执行期间运行,也就是即时编译。

JIT 编译器通常与动态语言一起使用,其中类型不是提前确定的。JIT 程序通过解释器或虚拟机(VM)运行。

  • AOT

在运行之前,AOT 编译器在创建程序期间运行。

AOT 编译器通常与静态语言一起使用,后者知道数据的类型。AOT 程序被编译为本机机器代码,在运行时由硬件直接执行。

下面引用了 Wm Leler 的这篇文章:当在开发期间完成 AOT 编译时,它总是导致更长的开发周期(对程序进行更改和能够执行程序以查看更改结果之间的时间)。 但 AOT 编译让程序的运行更可预测,而不会在运行时暂停进行分析和编译。AOT 编译的程序也可以快速启动(因为它们已经被编译)。 相反,JIT 编译提供了更快的开发周期,但可能导致执行速度变慢或更加笨拙。特别是,JIT 编译器的启动时间较慢,因为当程序开始运行时,JIT 编译器必须在执行代码之前进行分析和编译。研究表明,如果开始执行的时间超过几秒钟,很多人都会放弃。

在编译程序的时候就执行完毕,直接将程序编译为机器语言,启动速度快。一般用于静态语言。如 C++, Swift。Dart 支持了 JIT 和 AOT 两种方式的编译。

flutter 在开发的时候使用 JIT 方式,在发布的时候使用 AOT。Dart 为我们提供了两种方式,在开发环节通过 JIT 可以动态更新代码不需要每次重新编译程序,快速调试 UI,发布的时候使用 AOT 方式运行不会有性能损失。

作为一种静态语言,Swift 是提前编译的。Dart 则同时支持 AOT 和 JIT。与 Flutter 一起使用时,这提供了显著的优势。看看下面的描述:

在开发过程中使用 JIT 编译,使用更快的编译器。然后,当应用程序准备好发布时,将它编译为 AOT。因此,借助先进的工具和编译器,Dart 可以提供两全其美的优势:极快的开发周期,快速的执行和启动时间。 - Wm Leler

使用 Dart,可以两全其美。

Swift 有 AOT 编译的主要缺点。即编译时间随着代码库的大小而增加。

对于中型应用程序(10K 到 100K 行之间),编译应用程序很容易花费几分钟。

对于 Flutter 应用程序来说并非如此,无论代码库的大小如何,我们都会不断进行亚秒级热加载。

并发支持

Swift 使用了 GCD 多线程的方式进行支持,线程间通信需要注意锁。Dart 采用的是 isolates 消息通信,不需要考虑锁(并不是完全不需要考虑,对于同一 IO 的访问还是需要)。

最后

举个不太恰当的例子, Swift 这门语言就像个精密机械库,细致,精巧,严谨,安全。Dart 却像个赛克朋克机器人,粗莽,充满力量。Dart 发自 2011 年,年长 Swift 3 年,不过由于其独到的跨平台技术,开发时 JIT 技术,异步编程,也会在以后的移动终端占领一席之地, 在 Webassembly 来到后 Web 时代时,说不准可以统一全部终端。

posted @ 2022-05-26 17:11  为敢技术  阅读(597)  评论(0编辑  收藏  举报