Flutter跨平台开发-语音基础

Dart For Objcer

本文档是一个面向Objcer 的更加精炼的 Dart 语言概览,删除了相同的语法概念,仅剩差异部分,或 dart 独有的部分。未提及的,均可以沿用 objc 经验。

数据类型

所有数据类型都继承自 Object类。(包括int、String、函数以及 null 等等)。All is Object.

常用内置数据类型:

  • num
    • int
    • double
  • String
  • bool (true | false)
  • List ,数组
  • Set,集合
  • Map,字典

变量 & 常量

变量

dart 默认声明的为变量,变量声明主要有以下几种形式:

  • 类型约定:

    1
    2
    3
    4
    String name; // 默认值为 null:未初始化的所有变量拥有一个默认的初始化值:null,包括 num。
    name = 'Bob';

    int age = 18;
  • 类型推断,通过 关键词 var 来声明

    1
    2
    var name = 'Bob'; //name is String 
    name = 123; //编译报错
  • 使用动态类型,通过 关键词 dynamic 来声明, 类似 OC 中的 id

    1
    2
    dynamic name = 'Bob'; //name is String 
    name = 123; //name is int

一般情况下,我们使用 var 来声明变量,让编译器帮我们推断其类型;

常量

常量声明涉及到两个关键字 finalconst。使用示例如下:

1
2
3
4
5
6
7
final String name; // 编译报错:未初始化
final String name = 'dann'; //✅
name = 'dann2'; //编译报错,提示仅能赋值一次

const String name; // 编译报错:未初始化
const String name = 'dann'; //✅
name = 'dann2'; //编译报错,常量变量不能被赋值
  • final 用于声明==运行时==常量;
  • const 用于声明==编译时==常量;

示例说明如下:

1
2
3
4
5
final abc = 1 + 2;	//✅
const bb = 1 + 2; //✅

final x = DateTime.now(); // ✅
const x = DateTime.now(); // ❌

函数

一般函数形式:

1
2
3
4
5
6
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}

//函数体只包含单个表达式的简写,同上
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

函数参数分为两种:

  • 必要参数

    1
    2
    3
    bool isNoble(int atomicNumber/* 必要参数 */){
    ...
    }
  • 可选参数:一个函数==仅可==包含一种可选参数

    • 命名参数:{type param1, type param2, ...}

      1
      2
      3
      bool isNoble(int atomicNumber/* 必要参数 */, {bool bold = false, @required Widget child}){
      ...
      }
    • 位置参数:[type param1, type param2, ...],

      1
      2
      3
      bool isNoble(int atomicNumber/* 必要参数 */, [bool bold]){
      ...
      }

函数注意点:

  • 必要在前,可选在后;

  • 可对参数设置默认值 isNoble(int atomicNumber = 110)

  • 即使是可选参数,也可通过关键字 @required 将其约束为 必要的(调用函数时,必须对该参数赋值)

  • 函数也是对象,可作为参数传递

    1
    2
    3
    4
    5
    6
    7
    8
    void printElement(int element) {
    print(element);
    }

    var list = [1, 2, 3];

    // 将 printElement 函数作为参数传递。
    list.forEach(printElement);

匿名函数

类似 OC 中的 Block

1
2
3
4
5
6
7
void main() {
var list = ['apples', 'bananas', 'oranges'];
// forEach(void Function(String) f) -> void
list.forEach((item){
print(item);
});
}

表达式

表达式基本继承自类 C 语言,OC中没有的表达式,有如下几个:

  • ~/ 除并取整

    1
    2
    print(5/2 == 2.5);  //true, 此处不同于 OC ,返回整数
    print(5 ~/ 2 == 2); //true
  • as 类型转换 | is is! 类型判断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 类型检查
    if (emp is Person) {
    emp.firstName = 'Bob';
    }

    // 类型检查
    if (emp is! Person) {
    //TODO:
    }

    //此时要确保 emp 是 Person 类型,否则会抛出异常
    (emp as Person).firstName = 'Bob';
  • .. 级联 在 同一个对象 上连续 调用 多个对象的变量或方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    querySelector('#confirm') // 获取对象 (Get an object).
    ..text = 'Confirm' // 使用对象的成员 (Use its members).
    ..classes.add('important')
    ..onClick.listen((e) => window.alert('Confirmed!'));

    //equal to
    var button = querySelector('#confirm');
    button.text = 'Confirm';
    button.classes.add('important');
    button.onClick.listen((e) => window.alert('Confirmed!'));

    //返回值为 void 的方法则不能使用级联运算符
    var sb = StringBuffer();
    sb.write('foo')
    ..write('bar'); // 出错:void 对象中没有方法 write
  • ?? 空判断 expr1 ?? expr2 expr1 非空,则返回其值,否则执行 expr2 并返回值

    1
    2
    3
    4
    var num;
    print(num??123); // 123
    num = 456;
    print(num??123); // 456
  • ??= 空则赋值

    1
    2
    3
    4
    5
    6
    7
    var a = 123;
    a ??= 456;
    print(a); //123

    var b;
    b ??= 456;
    print(b); //456
  • ?. 条件访问成员

    1
    2
    3
    4
    List numbs;
    print(numbs?.length); // null
    numbs = [1, 2, 3];
    print(numbs?.length); // 3

流程控制

流程控制语句也基本继承自 C 语言;按已有 OC 习惯使用即可;

异常

关键字:

  • throw
  • try
  • catch
  • finally

流程及使用习惯,类 Java,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void breedMoreLlamas(){
if(1){
//TODO:
}else{
throw 'Out of llamas!';
}
}

try {
breedMoreLlamas();
} catch (e) {
print('Error: $e'); // 先处理异常。
} finally {
cleanLlamaStalls(); // 然后清理。
}

  • 一个类只有一个父类;
  • 类可扩展,同OC;

成员使用

1
2
3
4
5
6
7
8
9
10
var p = Point(2, 2);

// 为实例变量 y 赋值。
p.y = 3;

// 获取 y 的值。
assert(p.y == 3);

// 调用变量 p 的 distanceTo() 方法。
num distance = p.distanceTo(Point(4, 4));

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Point {
num x, y;

Point(num x, num y) {
this.x = x; //使用 this 关键字引用当前实例; OC 中的self
this.y = y;
}

// 语法糖: 在构造函数体执行前用于设置 x 和 y 的, equal to above
Point(this.x, this.y);
}

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

// new 关键字可选
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

常量构造函数

两个使用相同构造函数相同参数值构造的编译时常量是同一个对象:

1
2
3
4
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // 同一个实例!

命名式构造函数

  • 声明一个类多个命名式构造函数来表达更明确的意图;
  • 不能被继承;
1
2
3
4
5
6
7
8
9
10
11
class Point {
num x, y;

Point(this.x, this.y);

// 命名式构造函数
Point.origin() {
x = 0;
y = 0;
}
}

初始化列表

除了调用父类构造函数之外,还可以在构造函数体执行之前初始化实例变量。每个实例变量之间使用逗号分隔。

1
2
3
4
5
6
7
8
// Initializer list sets instance variables before
// the constructor body runs.
// 使用初始化列表在构造函数体执行前设置实例变量。
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}

其他构造函数

详情参见Dart API 文档:

  • 重定向构造函数 :类似OC 中的 非指定构造函数
  • 工厂构造函数:关键字 factory, 使用该构造函数构造类的实例时==并非==总是会返回新的实例对象。例如,工厂构造函数可能会从缓存中返回一个实例,或者返回一个子类型的实例。==工厂中不可以访问 this==

获取对象类型

关键字 runtimeType

1
print('The type of a is ${a.runtimeType}');

方法 & 属性

使用同OC, 但使用 . 语法

每个属性都有 Getter 方法, 对于 非 final 属性,还有 Setter 方法。 可以通过 关键字 getset 为额外的属性(如计算属性),添加 Getter 和 Setter 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Rectangle {
num left, top, width, height;

Rectangle(this.left, this.top, this.width, this.height);

// 定义两个计算产生的属性:right 和 bottom。
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}

void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}

抽象类

使用关键字 abstract 标识类可以让该类成为 抽象类,抽象类将无法被实例化。抽象类常用于声明接口方法、有时也会有具体的方法实现。如果想让抽象类同时可被实例化,可以为其定义工厂构造函数

1
2
3
4
5
6
7
8
9
10
11
abstract class Doer {
// 定义实例变量和方法等等……

void doSomething(); // 定义一个抽象方法。
}

class EffectiveDoer extends Doer {
void doSomething() {
// 提供一个实现,所以在这里该方法不再是抽象的……
}
}

隐式接口

骚操作

接口 即 OC 中的协议。

==每一个类都隐式地定义了一个接口并实现了该接口,这个接口包含所有这个类的实例成员以及这个类所实现的其它接口。如果想要创建一个 A 类支持调用 B 类的 API 且不想继承 B 类,则可以实现 B 类的接口。==

一个类可以通过关键字 implements 来实现一个或多个接口并实现每个接口定义的 API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Person 类的隐式接口中包含 greet() 方法。
class Person {
// _name 变量同样包含在接口中,但它只是库内可见的。
final _name;

// 构造函数不在接口中。
Person(this._name);

// greet() 方法在接口中。
String greet(String who) => '你好,$who。我是$_name。';
}

// Person 接口的一个实现。
class Impostor implements Person {
get _name => '';

String greet(String who) => '你好$who。你知道我是谁吗?';
}

String greetBob(Person person) => person.greet('小芳');

void main() {
print(greetBob(Person('小芸')));
print(greetBob(Impostor()));
}

实现多个接口,使用逗号分隔:

1
class Point implements Comparable, Location {...}

扩展

子类化

使用 extends 关键字来创建一个子类,并可使用 super 关键字引用一个父类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class TV{
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}

class SmartTV extends TV {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
重写

子类可以重写父类的实例方法、Getter 以及 Setter 方法。使用 @override 来标示:

1
2
3
4
5
class SmartTV extends TV {
@override
void turnOn() {...}
// ···
}

扩展方法

一种向现有库添加功能的方式。extension extensionName on ClassName{...}

1
2
3
4
5
6
7
8
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
}

print('42'.padLeft(5)); // 使用 原始类 自带方法
print('42'.parseInt()); // 使用 扩展方法

Mixin

一种在多重继承中复用某个类中代码的方法模式。

定义一个类继承自 Object 并且不为该类定义构造函数,这个类就是 Mixin 类,除非你想让该类与普通的类一样可以被正常地使用,否则可以使用关键字 mixin 替代 class 让其成为一个单纯的 Mixin 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;

void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}

使用 with 关键字并在其后跟上 Mixin 类的名字来使用 Mixin 模式:

1
2
3
4
5
6
7
8
9
10
11
class Musician extends Performer with Musical {
// ···
}

class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}

使用关键字 on 来指定哪些类可以使用该 Mixin 类,比如有 Mixin 类 A,但是 A 只能被 B 类使用,则可以这样定义 A:

1
2
3
mixin MusicalPerformer on Musician {
// ···
}

枚举

枚举具有较大的局限性,其值不可自定义。

每一个枚举值都有一个名为 index 成员变量的 Getter 方法,该方法将会返回以 0 为基准索引的位置值。例如,第一个枚举值的索引是 0 ,第二个枚举值的索引是 1。以此类推。

1
2
3
4
5
enum Color { red, green, blue }

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

类变量及类方法

使用 static 修饰

类成员
1
2
3
4
5
6
7
8
class Queue {
static const initialCapacity = 16;
// ···
}

void main() {
assert(Queue.initialCapacity == 16);
}

静态变量在其首次被使用的时候才被初始化。

类方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import 'dart:math';

class Point {
num x, y;
Point(this.x, this.y);

static num distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}

void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}

泛型

如今的每个现代编程语言都支持泛型。日常使用中,最常见于集合。常用于需要要求类型安全的情况,好处还有:

  • 适当地指定泛型可以更好地帮助代码生成。
  • 使用泛型可以减少代码重复。
1
2
3
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error

在上述代码中,为集合指定类型为 String,方便编译器检查,避免插入错误类型数据;

1
2
3
4
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}

在上述代码中,T 是一个替代类型。其相当于类型占位符,在开发者调用该接口的时候会指定具体类型。

泛型约束

可能会想限制泛型的类型范围,这时候可以使用 extends 关键字:

1
2
3
4
5
6
7
8
9
10
class Foo<T extends SomeBaseClass> {
// 具体实现……
String toString() => "'Foo<$T>' 的实例";
}

class Extender extends SomeBaseClass {...}

//这时候就可以使用 SomeBaseClass 或者它的子类来作为泛型参数:
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

泛型方法

在方法中,使用泛型

1
2
3
4
5
6
T first<T>(List<T> ts) {
// 处理一些初始化工作或错误检测……
T tmp = ts[0];
// 处理一些额外的检查……
return tmp;
}

异步

不同于 OC 中 通过 GCD 和 NSOperation 来支持异步编程。

Dart 中 使用 Futureawait/async 来支持异步编程。

Future

Future 基本使用上类似于 GCD,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void main() { 
Future(() => print('立刻在Event queue中运行的Future'));

Future.delayed(const Duration(seconds:1), () => print('1秒后在Event queue中运行的Future'));

Future.microtask(() => print('在Microtask queue里运行的Future'));

Future.sync(() => print('同步运行的Future'));

Future(()=> print('task'))
.then((_)=> print('callback1'))
.then((_)=> print('callback2'))
.catchError((error)=>print('$error'))
.whenComplete(()=> print('whenComplete'));
}

//print as follow:
/*
同步运行的Future
在Microtask queue里运行的Future
立刻在Event queue中运行的Future
task
callback1
callback2
whenComplete
1秒后在Event queue中运行的Future
*/

其中Dart的事件循环,类似 OC 中的 Runloop, 大概如下:

1、Dart的入口是main函数,所以main函数中的代码会优先执行;

2、main函数执行完后,会启动一个事件循环(Event Loop)就会启动,启动后开始执行队列中的任务;

3、首先,会按照先进先出的顺序,执行 微任务队列(Microtask Queue)中的所有任务;

4、其次,会按照先进先出的顺序,执行 事件队列(Event Queue)中的所有任务;

为了避免 地狱回调 的问题,dart 又通过引入 await/async 来实现以同步的代码风格,实现异步调用。

await/async

1
2
3
4
5
6
7
8
9
10
11
12
Future<String> getNetworkData() async {
var result = await Future.delayed(Duration(seconds: 3), () {
return "network data";
});

return "请求到的数据:" + result;
}

main() async {
print('print network data...');
print(await getNetworkData());
}

其他

  • isolates dart 中独有的概念,用于代替线程使用,详情参见官网;