目录
Dart语言特性
-
Dart 的特性
Dart 是少数同时支持 JIT(Just In Time,即时编译)和 AOT(Ahead of Time,运行前编译)的语言之一。语言在运行之前通常都需要编译,JIT 和 AOT 则是最常见的两种编译模式。
JIT 在运行时即时编译,在开发周期中使用,可以动态下发和执行代码,开发测试效率高,但运行速度和执行性能则会因为运行时即时编译受到影响。
AOT 即提前编译,可以生成被直接执行的二进制代码,运行速度快、执行性能表现好,但每次执行前都需要提前编译,开发测试效率低。
总结来讲,在开发期使用 JIT 编译,可以缩短产品的开发周期。Flutter 最受欢迎的功能之一热重载,正是基于此特性。而在发布期使用 AOT,就不需要像 React Native 那样在跨平台 JavaScript 代码和原生 Android、iOS 代码之间建立低效的方法调用映射关系。所以说,Dart 具有运行速度快、执行性能好的特点。
-
内存分配与垃圾回收
Dart VM 的内存分配策略比较简单,创建对象时只需要在堆上移动指针,内存增长始终是线性的,省去了查找可用内存的过程。
Dart 的垃圾回收,则是采用了多生代算法。新生代在回收内存时采用“半空间”机制,触发垃圾回收时,Dart 会将当前半空间中的“活跃”对象拷贝到备用空间,然后整体释放当前空间的所有内存。回收过程中,Dart 只需要操作少量的“活跃”对象,没有引用的大量“死亡”对象则被忽略,这样的回收机制很适合 Flutter 框架中大量 Widget 销毁重建的场景。
-
单线程模型
Dart 中并没有线程,只有 Isolate(隔离区)。Isolates 之间不会共享内存,就像几个运行在不同进程中的 worker,它们通过事件循环(Event Looper)在事件队列(Event Queue)上传递消息通信。所以如果想要在 Dart 中实现并发是可以通过 Isolate 实现的。Isolate 的这种类似于线程但不共享内存,独立运行的 worker的机制,就可以让 Dart 实现无锁的快速分配。
-
无需单独的声明式布局语言
Dart 声明式编程布局易于阅读和可视化,使得 Flutter 并不需要类似 JSX 或 XML 的声明式布局语言。所有的布局都使用同一种格式,也使得 Flutter 很容易提供高级工具使布局更简单,就突出一个上手简单。
-
一切皆对象
Dart语言是一门纯对象的编程语言。这意味着Dart语言在运行时所处理的值都是对象,甚至包括一些其他语言常见的基本类型的值(如数值、布尔值等)以及函数都是对象,无一例外。
而且Dart语言对所有数据都是统一处理的,这大大方便了所有与语言相关的人员,如语言的设计者、语言的实现者以及重要的开发人员。
举一个例子,集合类能够操作任意类型的数据,使用者无须关注自动转箱、拆箱的问题。也就是说,开发人员无须关注底层的细节实现,这样开发人员就彻底地解放了。Dart语言采用统一对象模型的同时,简化了系统实现者的任务。
-
面向接口编程
面向对象的核心思想是关注对象的行为而非对象的内部实现。遗憾的是,这一原则往往被忽略或误解。Dart语言往往通过以下几种方式来维护这个原则。
(1)Dart语言没有final方法,允许重写除内置操作符外的所有方法。
(2)Dart语言是基于接口而不是基于类的。所以在Dart语言中,任何类都隐含一个接口,能够被其他类实现,而不管其他类是否使用了同样的底层实现,除了数值、布尔值和字符串类。
(3)Dart语言把对象都进行了抽象的封装,确保所有外部操作都通过存取的方式来改变对象的状态。
(4)Dart类的构造函数允许对对象进行缓存,或者从子类创建,因此使用构造函数并不意味着绑定了一个具体的实现。
-
类型可选
类型可选照顾了那些不愿意与类型系统“打交道”的开发人员。因此,完全可以把Dart语言当成一门动态的类型语言使用。Dart语言的具体定义如下:
● 类型在语法层面上是可选的;
● 类型对运行时的语义没有影响。
虽然类型可选,但只要代码中有类型注解,就意味着代码有了额外的文档,所有的开发人员都能从中受益。对于代码中存在的类型不一致或者遗漏,Dart编译器会告警,但不会报错。同时,Dart编译器也不会拒绝一段类型不一致或者遗漏的代码。因此,使用类型不会限制开发人员的工作流程,类型不一致或者遗漏的代码依然可以用来测试和实验。
Dart基础语法
编程语言基础学习构造图
面向对象编程语言文件基础知识构造,方便语言学习,如下图:
dart文件基础语法
Java或者C等其他编程语言基础的程序员,可以直接看如下图中的代码,进行查漏补缺学习,轻松掌握dart基础语法。
库
-
导入库
import 'package:http/http.dart'; //导入Http库
import 'package:http/http.dart' as htp; //as 避免命名冲突
import 'package:http/http.dart' show http; //显示http中的库函数或成员
import 'package:http/http.dart' hide http; //隐藏http中的库函数或成员
import 'helloworld.dart'; //导入自定义的本地库
import 'http://helloword//hello.dart'; //导入网络库
最后的导入网络库时,可以使用任意URI,但不建议在开发中这么做,因为只要库的位置发生变化,就会影响你的代码,这种URI导入方式适用于只求速度但不求完美的实验性任务。
建议开发中使用前几种方式导入库。
-
拆分库
有时候,一个库如果过于庞大,可能会需要把库拆分到多个文件中,而不是保存在一个文件中。这时就需要使用part进行库的拆分,如下代码:
library game;
part 'hundouluo.dart';
part 'hejindantou.dart';
part 'cikexintiao.dart';
每个part都指向了一个part所对应的URI,这些URI与导入语句遵循同样的规则,而且所有的part都共享同一个作用域,即导入它们的库的内部命名空间,而且包含所有导入。如果通过part进行拆分导入,那么part导入的单个模块的声明方式如下代码所示:
//hundouluo.dart文件开头
part of game;
类
// 创建一个类
// lib/animal.dart
class Animal {
String? name;
Animal({this.name});
Animal.fromJson(Map<String, dynamic> json) {
name = json['name'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['name'] = name;
return data;
}
void eat() {
print('$name is eating!');
}
}
// main.dart
void main() {
Animal a = Animal(name: '果果');
a.eat();
}
输出结果:果果 is eating
-
抽象和接口
Dart语言中没有接口(interface)的关键字,但是有实现(implements)关键字,Dart中可以将类(是否为抽象无关)当做隐式接口直接使用,当需要使用接口时,可以声明类来代替。
// 抽象类作为接口
abstract class Sleep{
void sleep();
}
// 普通类作为接口
class Play{
void play(){}
}
class Cat extends Animal implements Sleep, Play {
@override
void eat() {
print("eat");
}
@override
void sleep() {
print(" is sleeping");
}
@override
void play() {
print("is beating colala");
}
}
void main() {
var cat = = Cat();
cat.name = "果果";
cat.eat();
cat.sleep();
cat.play();
}
输出结果:果果 is eating
输出结果:果果 is sleeping
输出结果:果果 is beating colala
-
继承
// 继承一个类
// lib/cat.dart
class Cat extends Animal {
}
void main() {
Cat cat = Cat();
cat.name = "colala";
cat.eat();
}
输出结果:colala is eating
-
多态
// lib/cat.dart
class Cat {
// 重写一个方法
drink() {
print('$name is drinking!');
}
}
void main() {
Cat cat = Cat();
cat.name = "colala";
cat.eat();
cat.drink();
}
输出结果:colala is eating
输出结果:colala is drinking
-
mixin
类的混入
mixin一般用于描述一种具有某种功能的组块,而某一对象可以拥有多个不同功能的组块。
mixin用于修饰类,和abstract类似,该类可以拥有成员变量、普通方法、抽象方法,但是不可以实例化。
mixins不是一种在经典意义上获得多重继承的方法。
mixins是一种抽象和重用一系列操作和状态的方法。
它类似于扩展类所获得的重用,但它与单继承兼容,因为它是线性的。
1、简单应用
最简单的mixin由mixin & with关键字组成。
‘教师‘ 一种能力是 ‘绘画’
void main() {
Teacher().draw();
}
mixin DrawFunc {
String content = '..';
String what();
void draw() {
print('I can draw ${what()}');
}
}
class Teacher with DrawFunc {
String what() => "car";
}
2、限定类型(mixin…on)
当我们在mixin上使用了on关键字,那么mixin只能在那个类的子类上使用,而mixin可以调用那个类的方法。
限定 ‘绘画’ 这种能力只能够用在 ‘人类’ 上面
void main() {
Teacher().draw();
}
class Person {}
mixin DrawFunc on Person {
String content = '..';
String what();
void draw() {
print('I can draw ${what()}');
}
}
class Teacher extends Person with DrawFunc {
String what() => "car";
}
3、多个类型
在 ‘绘画’ 的基础上,我们增加一种新的能力 ‘唱歌’
void main() {
Teacher().draw();
Teacher().sing();
}
class Person {}
mixin DrawFunc on Person {
String content = '..';
String what();
void draw() {
print('I can draw ${what()}');
}
}
mixin SingFunc on Person {
void sing() {
print('I can sing');
}
}
class Teacher extends Person with DrawFunc, SingFunc {
String what() => "car";
}
4、组合组块
- mixin:定义了组块。
- on:限定了使用mixin组块的宿主必须要继承于某个特定的类;在mixin中可以访问到该特定类的成员和方法。
- with:负责组合组块,而with后面,这一点需要注意,例如下面这样:
void main() {
Teacher().draw();
Teacher().sing();
Teacher().dance();
}
class Person {}
mixin DrawFunc on Person {
String content = '..';
String what();
void draw() {
print('I can draw ${what()}');
}
}
mixin SingFunc on Person {
void sing() {
print('I can sing');
}
}
abstract class DanceFunc {
void dance() {
print('I can dance');
}
}
class Teacher extends Person with DrawFunc, SingFunc, DanceFunc {
String what() => "car";
}
总结就是,mixin可以理解为一个个的功能组块,哪些宿主需要哪些功能就with到上去。
on关键字一方面是为了限制组块的应用场景,也可以为多个组块提供公共的基础功能。
类型
-
var
void main(){
var apple="apple";
print(apple);
}
//创建字符串方式
/*
3种创建字符串的方式。
(1)使用单引号、双引号创建字符串。
(2)使用3个单引号或者双引号创建多行字符串。
(3)使用r创建原始字符串。
*/
var press=" POSTS & TELECOM PRESS ";
var press2=' POSTS & TELECOM PRESS ';
var press3=''' POSTS &
TELECOM
PRESS''';
var press4=" POSTS & TELECOM "
"PRESS";
var press2=r' POSTS & TELECOM PRESS \n';
//字符串操作
/*
Dart语言中操作字符串的几种方式。
(1)运算符操作:+、*、==、[]等。
(2)插值表达式:${name}。
(3)常用的字符串属性:length、isEmpty、isNotEmpty等。
(4)常用的方法:contains()、subString()、replaceAll()、split()、trimRight()、trimLeft()、trim()、toUpperCase()、toLowerCase()、lastIndexOf()、indexOf()、endsWith()、startsWith()等。
*/
var str1='hello';
var str2='world';
var str3=str1+' '+ str2;//字符串加操作
print(str3);
print(str3.toUpperCase());//全部转换为大写
print(str3.toLowerCase());//全部转换为小写
print(print(str3.startsWith('Hello'));)//是否包含子字符串
print(str3.length);//输出字符串长度
print(str3.split(' '));//按空格' '分割字符串,并转换为列表
print(str3.replaceAll(' ', '我是空格'));//将字符串替换
//字符串拼接方式
var str='hello ' 'world';//用单引号拼接
var str1="hello " "world";//用双引号拼接
var str2="hello " 'world';//用单、双引号拼接
print(str);
print(str1);
print(str2);
//flutter开发中,字符串插值操作
var name='Li YuanJing';
print("My name is ${name}");
上面代码定义一个叫作apple的字符串变量,创建字符串方式和字符串操作。
注意
Dart语言是一门强类型语言,与JavaScript这种弱类型语言有明显的区别:对于Dart语言,在第一次赋值的时候,如果你已经将某个变量赋值为字符串类型,那么在之后的代码中就不能把这个变量更改为其他类型;而对于JavaScript,可以随意改变类型。这一点需要特别注意。但是假如你在使用Dart语言时,定义了一个变量后需要改变其类型,可以使用后文讲解的dynamic关键字。
我们需要注意字符串操作的要点:
第一,字符串里单引号嵌套单引号,或者双引号嵌套双引号,必须加入反斜杠(\)进行转义;
第二,推荐单引号嵌套双引号,或者双引号嵌套单引号,这样混合使用比较方便。
-
const和final
const time='2020-05-03';
const time=DataTime.now();//这行代码在编译器中会报错
final time='2020-05-03';
final time=DataTime.now();//这行代码不会报错
const与final的共同点是初始化后都无法更改;
两者的区别:const值在编译时会检查值,而final值在运行时才检查值。
所以,不能给const定义的常量赋值为不确定的值。
-
Object和dynamic
Object在dart中是所有对象的父类。
var name1='liyuanjing';
Object name2='liyuanjing';
dynamic name3='liyuanjing';
dynamic告诉编译器:“我们不用做类型检测,并且知道自己在做什么。”
开发中尽量确定变量类型,才能提高程序的安全性。
//利用dynamic ,使用is 和 as
dynamic map={'name': 'liyuanjing', 'age':'27'};
if(map is Map<String,String>){
print('类型与你想的一致');
}
var map2=map as Map<String,String>;
在上面的代码中,is是判断类型时使用的,as是转换类型时使用的,is!与is的功能刚好相反。一般来说,如果在is测试之后还有一些关于对象的表达式,可以把as当作is测试的一种简写形式。
-
int、double、Map、List和Set
//第一段代码
double a=300000000000;
a=1.2;
//第二段代码
int b=22222222222222;
b=11.1;//这行代码会报错
上面代码是int与double区别。dart中没有float类型。
注意
虽然在Dart语言中一切皆对象,但是不能定义int的子类型,也不能用另一个类型实现int。这些限制是一种妥协,即Dart语言以牺牲可以继承或实现任意类型为代价来实现高性能。
同样,double也不能定义其子类型。另外,如果两个浮点数的底层位模式所表示的浮点数相同,则它们相同。在大多数情况下,这意味着如果两个浮点数相等,则它们相同,但也有例外:
(1)浮点数−0.0与0.0相等但不相同;
(2)NaN(非数值)与自身不相等但相同。
//通过直接赋值创建
var list=[1,2,3];
//通过new创建
var list=new List();
//创建一个不可变的列表
const list=const[1,2,3];
//使用列表的方法
var list=["apple","banana","cherry"];
print(list.length);//输出列表的长度
list.add("bayberry");//末尾添加"bayberry字符串
print(list);
list.remove("apple")//删除apple字符串
print(list);
list.insert(1, 'dates');//在1索引插入dates字符串
print(list);
print(list.indexOf("cherry"));//获取cherry字符串所在位置
print(list.sublist(2));//去除前两个元素后的新的列表
list.forEach(print);//遍历并输出列表
list.shuffle();//打乱列表顺序
print(list);
//创建Map方式
Map map={'name':'liyuanjing', 'age':'27'};
Map map =new Map()
..['name']='liyuanjing'
..['age']='27';
//操作Map数据
Map map=new Map();
map['name']='liyuanjing';
map['age']=27;
map.remove('name');//删除某个键为name的数据
print(map);
map.clear();//清空键值对
print(map);
//Set是一个无序的、元素唯一的集合,无法通过 Set[index] 的方式取值
// 字面量
var subjects = {'dart', 'flutter'};
print(subjects); // {dart, flutter}
Set<int> nums = <int>{1, 2, 3};
print(nums); // {1, 2, 3}
// 构造函数
Set<String> fruits = new Set<String>();
fruits.add('apple');
fruits.add('banana');
print(fruits); // {apple, banana}
函数
-
主函数
//Dart语言
void main(){
}
//Flutter项目
void main()=>runApp(MyApp());
-
自定义函数
//可选参数,使用“{}”
void setUser({String name,int age}){
//...
}
//必选参数
void setUser(String name,int age){ //第1种定义必选参数方式
//...
}
//import 'package:meta/meta.dart';//必须引入这个包,才能使用修饰符@required
void setUser({@required String name,int age}){ //第2种定义必选参数方式
//...
}
setUser(name: 'liyuanjinglyj');//调用第二个函数
//可选位置参数,使用“[]”
void setUser({String name,int age,[String company]}){
if(company!=null){
print('这个人有公司备注,公司为:${company}');
}
}
//默认参数函数
void setUser({String name='liyuanjinglyj',int age=27}){
//...
}
setUser();//调用函数
//函数作为参数传递
//列表遍历,print为函数
List list=[1,2,3,4];
list.forEach(print);
//传递自定义函数
void printName(String name){
print(name);
}
void main(){
var fruits=['apple','banana','apricot'];
fruits.forEach(printName);
}
//函数作为变量
var night= (good){
print(good+' night');
};
night('good');
操作控制语句/符
-
循环和选择语句
//for..in循环语句
//除了列表,Dart语言中实现了Iterable接口的类,都可以使用for-in循环进行遍历。
for(int i in [1,2,3,4])print(i);
for(var i in [1,2,3,4])print(i);
//经典for循环语句
for(int i=1;i<=100;i++)print(i);
for(var i=1;i<=100;i++)print(i);
//while,先判断后执行
int i=1;
while(i<=100){
print(i);
i++;
}
//do...while,先执行后判断
int i=1;
do{
print(i);
i++;
}while(i<=100);
//switch
var grade='周杰伦';
switch(grade){
case '周杰伦':
print('歌手');
break;
case '屠呦呦':
print('科学家');
break;
default:
break;
}
-
运算符、空安全和异常处理
Dart 支持的运算符
下表从最高到最低显示了 Dart 的运算符关联性和运算符优先级,它们是 Dart 运算符关系的近似值。
描述 | 操作 | 关联性 |
一元后缀 | expr ++ expr -- () [] ?[] . ?. ! | 没有任何 |
一元前缀 | - expr ! expr ~ expr ++ expr -- expr await expr | 没有任何 |
乘法 | * / % ~/ | 左边 |
累积 | + - | 左边 |
位移 | << >> >>> | 左边 |
按位与 | & | 左边 |
按位异或 | ^ | 左边 |
按位或 | | | 左边 |
关系和类型测试 | >= > <= < as is is! | 没有任何 |
相等 | == != | 没有任何 |
逻辑与 | && | 左边 |
逻辑或 | | | 左边 |
如果为空 | ?? | 左边 |
条件 | expr1 ? expr2 : expr3 | 右边 |
级联 | .. ?.. | 左边 |
分配 | = *= /= += -= &= ^= ETC。 | 右边 |
扩展 | ... ...? | 没有任何 |
自定义类操作符
//自定义类“+”操作符
class Point{
var x,y;
Point(this.x,this.y);
operator +(p)=>new Point(x+p.x, y+p.y);//自定义类"+"操作符
@override
String toString() {
// TODO: implement toString
return "x="+x.toString()+";y="+y.toString();
}
}
void main() {
Point s_one=new Point(10, 10);
Point s_two=new Point(20, 20);
print(s_one+s_two);
}
可以看到,operator+( p)=>new Point(x+p.x, y+p.y)自定义了类“+”操作符,然后,我们就可以用操作符“+”完成两个点类对象的相加操作。
空安全 Null safety
- 空安全的目的是让开发人员对代码中的 Null 可见且可控,并且确保它不会传递至某些位置从而引发崩溃。相关关键字有以下这些:
- ?
- !
- late
如果可以为空值的变量(null), 在类型声明处加上 ?。
String? name = null;
在您已经明确一个非空变量一定会在使用前初始化,而 Dart 分析器仍然无法明确的情况下,您可以在变量的类型前加上late。
void main() {
late TextEditingController textEditingController;
init() {
textEditingController = TextEditingController();
}
}
当您正在调用一个可空的变量或者表达式时,请确保您自己处理了空值。例如:您可以使用 if 条件句、?? 操作符 或是 ?. 操作符来处理可能为空的值。
// 使用 ?? 操作符来避免将非空变量赋予空值
Cat cat = Cat();
String name = cat.name ?? "果果";
如果您能确定一条可空的表达式不为空,您可以在其后添加 ! 让 Dart 处理为非空。
Cat cat = Cat();
cat.name = "果果";
String name = cat.name!;
一旦您开始使用空安全,当操作对象可能为空时,您将不再能使用成员访问符(.),取而代之的是可空版本的?.
Cat? cat;
String? name = cat?.name;
更详细的运算符使用,可以自行百度,很多例子。
//异常捕获,throw只是表达式不是语句,dart任何对象都可以作为异常被抛出。
throw Exception('我是个异常');
//捕获异常结构体
try{
...(1)
}on String catch(e){
...(2)
}catch(e){
...(3)
}finally{
...(4)
}
在上面的结构体中,(1)中的代码就是捕获特定类型的异常,在(2)中,我们预料到可能会抛出一个字符串类型的异常,如果不是字符串类型的异常,则(3)将捕获任意可能出现的异常,(4)中不管有没有异常发生,只要有这个结构体,就会被运行。
注意
虽然throw与try-catch语句是Dart语言中常用的异常处理方式,但其实还有一种异常处理方式——rethrow。捕获的异常经检查之后,如果我们发现本地不需要处理异常,并且异常应该在调用链中向上传播,那么rethrow就可以发挥作用了,它在catch子句中将捕获的异常重新抛出。不过rethrow基本用不到,了解一下就可以了。
异步编程
Dart 是一门单线程编程语言。异步 IO + 事件循环,Dart异步主要可以通过以下关键字来实现:
Future
async
await
Future 对象封装了Dart 的异步操作,它有未完成(uncompleted)和已完成(completed)两种状态。completed 状态也有两种:一种是代表操作成功,返回结果;另一种代表操作失败,返回错误。
-
Future
Future 对象封装了Dart 的异步操作,它有未完成(uncompleted)和已完成(completed)两种状态。completed 状态也有两种:一种是代表操作成功,返回结果;另一种代表操作失败,返回错误。
Future<String> fetchUserName() {
//想象这是个耗时的获取用户名称操作
return Future(() => '果果');
}
void main() {
fetchUserName().then((result){print(result)})
print('after fetch user name ...');
}
通过.then来回调成功结果,main会先于Future里面的操作,输出结果:
flutter: after fetch user name ...
flutter: 果果
那如果我们想要先打印名称咋办,换一下掉用方式
Future<String> fetchUserName() {
//想象这是个耗时的获取用户名称操作
return Future(() => '果果');
}
void main() {
fetchUserName().then((result){
print(result);
print('after fetch user name ...');
}
)
}
输出结果:
flutter: 果果
flutter: after fetch user name ...
-
await和async
Future 同名构造器是 factory Future(FutureOr computation()),它的函数参数返回值为 FutureOr 类型,我们发现还有很多 Future 中的方法比如Future.then、Future.microtask 的参数类型也是 FutureOr,这个对象其实是个特殊的类型,它没有类成员,不能实例化,也不可以继承,只是一个语法糖。
abstract class FutureOr<T> {
FutureOr._() {
throw new UnsupportedError("FutureOr can't be instantiated");
}
}
想象一个这样的场景:
- 调用登录接口;
- 根据登录接口返回的token获取用户信息;
- 缓存用户信息到本机。
Future<String> login(String name,String password){
//登录
}
Future<User> fetchUserInfo(String token){
//获取用户信息
}
Future saveUserInfo(User user){
// 保存用户信息
}
Future 可以这样写:
login('name','password')
.then((token) => fetchUserInfo(token))
.then((user) => saveUserInfo(user));
但是这种看着有点别扭,所以我们可以换成关键字处理,换成async和await则可以这样:
void doLogin() async {
String token = await login('name','password'); //await 必须在 async 函数体内
User user = await fetchUserInfo(token);
await saveUserInfo(user);
}
需要注意的是如果声明了 async 的函数,返回值是必须是 Future 对象。即便你在 async 函数里面直接返回 T 类型数据,编译器会自动帮你包装成 Future 类型的对象,如果是 void 函数,则返回 Future 对象。在遇到 await 的时候,又会把 Futrue 类型拆包,又会原来的数据类型暴露出来,所以请注意,await 所在的函数必须添加 async 关键词。
await 的代码发生异常,捕获方式跟同步调用函数一样:
void doLogin() async {
try {
var token = await login('name','password');
var user = await fetchUserInfo(token);
await saveUserInfo(user);
} catch (err) {
print('Caught error: $err');
}
}
语法糖
Future<String> getUserInfo() async {
return 'aaa';
}
//等价于:
Future<String> getUserInfo() async {
return Future.value('aaa');
}
-
Completer
在Flutter中,Completer是一个类,用于实现异步操作的等待和结果处理。Completer允许您创建一个Future对象,并在该对象的值(或错误)可用时解决该对象。
Completer对象包含两个主要方法:complete()和completeError()。complete()方法将Future对象标记为已完成,并将其结果设置为指定的值,而completeError()方法将Future对象标记为已完成,并将其结果设置为指定的错误。使用Completer时,您可以通过Future对象的then()方法或await关键字来等待异步操作的结果。
以下是一个示例,演示如何使用Completer来处理异步操作的结果:
Completer<int> completer = Completer<int>();
// 假设在两秒钟后异步返回一个值
Future.delayed(Duration(seconds: 2), () {
completer.complete(42);
});
// 使用then()方法等待异步操作的结果
completer.future.then((value) {
print('异步操作的结果为:$value');
});
在上面的示例中,创建了一个Completer对象,并通过Future.delayed()方法模拟一个异步操作,该操作在两秒钟后返回一个值。然后,使用then()方法等待异步操作的结果,并在结果可用时打印该结果。
Completer是Flutter中处理异步操作的重要工具之一,特别是在需要处理多个异步操作的情况下,Completer可以更好地控制异步操作的执行顺序和结果处理。
Isolate
在Flutter中,Isolate是一种轻量级的、独立的执行线程,它与主线程(也称为UI线程)并行运行,并可以执行CPU密集型任务,而不会阻塞UI线程。Flutter中的每个Isolate都有自己的内存空间,可以独立于其他Isolate进行操作。
Isolate可以在Flutter应用程序中实现并发性,可以将一些耗时的任务(如网络请求、文件操作、复杂计算等)分配给Isolate,从而使UI线程不会被阻塞,从而提高应用程序的性能和响应速度。
Flutter中可以通过使用compute()函数或Isolate.spawn()方法来创建和使用Isolate。compute()函数是Flutter提供的一个方便的API,它允许您在后台Isolate中执行耗时的计算,并在完成后返回结果。例如,以下代码演示了如何使用compute()函数来计算斐波那契数列的第20项:
int fibonacci(int n) {
if (n <= 0) {
return 0;
} else if (n == 1) {
return 1;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
Future<int> calculateFibonacci(int n) async {
return await compute(fibonacci, n);
}
int result = await calculateFibonacci(20);
Isolate.spawn()方法允许您创建新的Isolate,并将指定的函数作为Isolate的入口点来执行。例如,以下代码演示了如何使用Isolate.spawn()方法来创建一个新的Isolate,并在其中执行耗时的计算:
Future<void> calculate() async {
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(calculateFibonacci, 20, onExit: receivePort.sendPort);
int result = await receivePort.first;
print('计算结果为:$result');
}
void calculateFibonacci(int n) {
// 计算斐波那契数列的第n项,并将结果发送到主Isolate
int result = fibonacci(n);
SendPort sendPort = IsolateNameServer.lookupPortByName('main_send_port');
sendPort.send(result);
}
在上面的示例中,首先创建一个ReceivePort对象,然后使用Isolate.spawn()方法创建一个新的Isolate,并将calculateFibonacci函数作为入口点来执行。在calculateFibonacci函数中,计算斐波那契数列的第n项,并将结果发送到主Isolate。在calculate()函数中,使用ReceivePort来等待新Isolate的结果,并将其打印出来。
Isolate是Flutter中处理并发性和异步操作的重要工具之一,特别是在需要执行耗时的计算或操作的情况下,使用Isolate可以使应用程序保持流畅并响应迅速。
那么这两个如何联合使用呢?
通常,Completer对象用于异步操作的等待和结果处理。当需要执行耗时的操作时,可以创建一个新的Isolate来执行该操作,并使用Completer来等待操作完成并获取结果。在Isolate中,可以使用SendPort来将结果发送回主Isolate,然后在主Isolate中使用Completer来获取结果。
以下是一个示例,演示如何使用Completer和Isolate一起实现异步操作和并发执行:
Future<int> calculateFibonacci(int n) async {
Completer<int> completer = Completer<int>();
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(calculate, [n, receivePort.sendPort]);
receivePort.listen((result) {
completer.complete(result);
receivePort.close();
});
return completer.future;
}
void calculate(List<dynamic> args) {
int n = args[0];
SendPort sendPort = args[1];
// 计算斐波那契数列的第n项
int result = fibonacci(n);
// 将结果发送回主Isolate
sendPort.send(result);
}
int fibonacci(int n) {
if (n <= 0) {
return 0;
} else if (n == 1) {
return 1;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
int result = await calculateFibonacci(20);
在上面的示例中,首先创建一个Completer对象和一个ReceivePort对象。然后使用Isolate.spawn()方法创建一个新的Isolate,并将calculate函数作为入口点来执行。在calculate函数中,计算斐波那契数列的第n项,并将结果发送到主Isolate。在主Isolate中,通过监听ReceivePort来等待新Isolate的结果,并使用Completer来获取结果。
使用Completer和Isolate一起实现异步操作和并发执行可以提高应用程序的性能和响应速度,特别是在需要执行耗时的操作时。但是需要注意,使用Isolate也会带来一些额外的开销和复杂性,因此应根据实际需求进行权衡和选择。
泛型
//List泛型示例
void main(){
List animal=new List<String>();
animal.addAll(['老虎','狮子','豹子','秃鹰']);
animal.add(1234);
}
这段代码最后添加了1234到animal泛型变量之中,这在其他语言之中肯定会报错,但是在Dart语言中不会,在Dart语言中只会在运行时报错。
为什么要在程序中使用泛型?
//不使用泛型
abstract class AnimalTiger{
Object getName();
void setName(String name);
}
abstract class AnimalLion{
Object getName();
void setName(String name);
}
可以看到,代码清单2-59定义了两个动物的抽象类,它们的方法都一样。但是这个时候有一个需求:需要写出所有动物的抽象类,并且它们的方法都和上面一样。难道要一行一行输入代码吗?这显然不现实,此时需要用泛型转换一下,如下代码:
abstract class Animal<T>{
Object getName();
void setName(String name);
}
这样,通过泛型就可以在只定义一个抽象类的情况下写出所有动物的抽象类,非常方便。而且,我们还可以通过泛型限制参数,如下代码:
//通过泛型限制参数
class Animal{}
class Tiger extends Animal{}
class Lion extends Animal{}
class MyAnimal<T extends Animal>{
}
这样,我们就可以像使用List<T>泛型一样使用类泛型,也实现了限制其类型的行为。因此,在程序中使用泛型是非常便捷的。
参考:
Flutter App开发:从入门到实战