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 默认声明的为变量,变量声明主要有以下几种形式:
-
类型约定:
String name; // 默认值为 null:未初始化的所有变量拥有一个默认的初始化值:null,包括 num。 name = 'Bob'; int age = 18;
-
类型推断,通过 关键词
var
来声明var name = 'Bob'; //name is String name = 123; //编译报错
-
使用动态类型,通过 关键词
dynamic
来声明, 类似 OC 中的id
dynamic name = 'Bob'; //name is String name = 123; //name is int
一般情况下,我们使用 var
来声明变量,让编译器帮我们推断其类型;
常量
常量声明涉及到两个关键字 final
和 const
。使用示例如下:
final String name; // 编译报错:未初始化
final String name = 'dann'; //✅
name = 'dann2'; //编译报错,提示仅能赋值一次
const String name; // 编译报错:未初始化
const String name = 'dann'; //✅
name = 'dann2'; //编译报错,常量变量不能被赋值
- final 用于声明==运行时==常量;
- const 用于声明==编译时==常量;
示例说明如下:
final abc = 1 + 2; //✅
const bb = 1 + 2; //✅
final x = DateTime.now(); // ✅
const x = DateTime.now(); // ❌
函数
一般函数形式:
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
//函数体只包含单个表达式的简写,同上
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
函数参数分为两种:
-
必要参数
bool isNoble(int atomicNumber/* 必要参数 */){ ... }
-
可选参数:一个函数==仅可==包含一种可选参数
-
命名参数:
{type param1, type param2, ...}
bool isNoble(int atomicNumber/* 必要参数 */, {bool bold = false, @required Widget child}){ ... }
-
位置参数:
[type param1, type param2, ...]
,bool isNoble(int atomicNumber/* 必要参数 */, [bool bold]){ ... }
-
函数注意点:
必要在前,可选在后;
可对参数设置默认值
isNoble(int atomicNumber = 110)
即使是可选参数,也可通过关键字
@required
将其约束为 必要的(调用函数时,必须对该参数赋值)-
函数也是对象,可作为参数传递
void printElement(int element) { print(element); } var list = [1, 2, 3]; // 将 printElement 函数作为参数传递。 list.forEach(printElement);
匿名函数
类似 OC 中的 Block
void main() {
var list = ['apples', 'bananas', 'oranges'];
// forEach(void Function(String) f) -> void
list.forEach((item){
print(item);
});
}
表达式
表达式基本继承自类 C 语言,OC中没有的表达式,有如下几个:
-
~/
除并取整print(5/2 == 2.5); //true, 此处不同于 OC ,返回整数 print(5 ~/ 2 == 2); //true
-
as
类型转换 |is
is!
类型判断// 类型检查 if (emp is Person) { emp.firstName = 'Bob'; } // 类型检查 if (emp is! Person) { //TODO: } //此时要确保 emp 是 Person 类型,否则会抛出异常 (emp as Person).firstName = 'Bob';
-
..
级联 在 同一个对象 上连续 调用 多个对象的变量或方法。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 并返回值var num; print(num??123); // 123 num = 456; print(num??123); // 456
-
??=
空则赋值var a = 123; a ??= 456; print(a); //123 var b; b ??= 456; print(b); //456
-
?.
条件访问成员List numbs; print(numbs?.length); // null numbs = [1, 2, 3]; print(numbs?.length); // 3
流程控制
流程控制语句也基本继承自 C 语言;按已有 OC 习惯使用即可;
异常
关键字:
throw
try
catch
finally
流程及使用习惯,类 Java,如下:
void breedMoreLlamas(){
if(1){
//TODO:
}else{
throw 'Out of llamas!';
}
}
try {
breedMoreLlamas();
} catch (e) {
print('Error: $e'); // 先处理异常。
} finally {
cleanLlamaStalls(); // 然后清理。
}
类
- 一个类只有一个父类;
- 类可扩展,同OC;
成员使用
var p = Point(2, 2);
// 为实例变量 y 赋值。
p.y = 3;
// 获取 y 的值。
assert(p.y == 3);
// 调用变量 p 的 distanceTo() 方法。
num distance = p.distanceTo(Point(4, 4));
构造函数
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});
常量构造函数
两个使用相同构造函数相同参数值构造的编译时常量是同一个对象:
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // 同一个实例!
命名式构造函数
- 声明一个类多个命名式构造函数来表达更明确的意图;
- 不能被继承;
class Point {
num x, y;
Point(this.x, this.y);
// 命名式构造函数
Point.origin() {
x = 0;
y = 0;
}
}
初始化列表
除了调用父类构造函数之外,还可以在构造函数体执行之前初始化实例变量。每个实例变量之间使用逗号分隔。
// 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
print('The type of a is ${a.runtimeType}');
方法 & 属性
使用同OC, 但使用 .
语法
每个属性都有 Getter
方法, 对于 非 final
属性,还有 Setter
方法。 可以通过 关键字 get
和 set
为额外的属性(如计算属性),添加 Getter 和 Setter 方法:
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
标识类可以让该类成为 抽象类,抽象类将无法被实例化。抽象类常用于声明接口方法、有时也会有具体的方法实现。如果想让抽象类同时可被实例化,可以为其定义工厂构造函数。
abstract class Doer {
// 定义实例变量和方法等等……
void doSomething(); // 定义一个抽象方法。
}
class EffectiveDoer extends Doer {
void doSomething() {
// 提供一个实现,所以在这里该方法不再是抽象的……
}
}
隐式接口
骚操作
接口 即 OC 中的协议。
==每一个类都隐式地定义了一个接口并实现了该接口,这个接口包含所有这个类的实例成员以及这个类所实现的其它接口。如果想要创建一个 A 类支持调用 B 类的 API 且不想继承 B 类,则可以实现 B 类的接口。==
一个类可以通过关键字 implements
来实现一个或多个接口并实现每个接口定义的 API:
// 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()));
}
实现多个接口,使用逗号分隔:
class Point implements Comparable, Location {...}
扩展
子类化
使用 extends
关键字来创建一个子类,并可使用 super
关键字引用一个父类:
class TV{
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTV extends TV {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
重写
子类可以重写父类的实例方法、Getter 以及 Setter 方法。使用 @override
来标示:
class SmartTV extends TV {
@override
void turnOn() {...}
// ···
}
扩展方法
一种向现有库添加功能的方式。extension extensionName on ClassName{...}
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
}
print('42'.padLeft(5)); // 使用 原始类 自带方法
print('42'.parseInt()); // 使用 扩展方法
Mixin
一种在多重继承中复用某个类中代码的方法模式。
定义一个类继承自 Object 并且不为该类定义构造函数,这个类就是 Mixin 类,除非你想让该类与普通的类一样可以被正常地使用,否则可以使用关键字 mixin
替代 class
让其成为一个单纯的 Mixin 类。
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 模式:
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:
mixin MusicalPerformer on Musician {
// ···
}
枚举
枚举具有较大的局限性,其值不可自定义。
每一个枚举值都有一个名为 index
成员变量的 Getter 方法,该方法将会返回以 0 为基准索引的位置值。例如,第一个枚举值的索引是 0 ,第二个枚举值的索引是 1。以此类推。
enum Color { red, green, blue }
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
类变量及类方法
使用 static
修饰
类成员
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
静态变量在其首次被使用的时候才被初始化。
类方法
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);
}
泛型
如今的每个现代编程语言都支持泛型。日常使用中,最常见于集合。常用于需要要求类型安全的情况,好处还有:
- 适当地指定泛型可以更好地帮助代码生成。
- 使用泛型可以减少代码重复。
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
在上述代码中,为集合指定类型为 String,方便编译器检查,避免插入错误类型数据;
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
在上述代码中,T 是一个替代类型。其相当于类型占位符,在开发者调用该接口的时候会指定具体类型。
泛型约束
可能会想限制泛型的类型范围,这时候可以使用 extends
关键字:
class Foo<T extends SomeBaseClass> {
// 具体实现……
String toString() => "'Foo<$T>' 的实例";
}
class Extender extends SomeBaseClass {...}
//这时候就可以使用 SomeBaseClass 或者它的子类来作为泛型参数:
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
泛型方法
在方法中,使用泛型
T first<T>(List<T> ts) {
// 处理一些初始化工作或错误检测……
T tmp = ts[0];
// 处理一些额外的检查……
return tmp;
}
异步
不同于 OC 中 通过 GCD 和 NSOperation 来支持异步编程。
Dart 中 使用 Future
和 await/async
来支持异步编程。
Future
Future
基本使用上类似于 GCD,如下:
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
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 中独有的概念,用于代替线程使用,详情参见官网;