温故知新-Dart

53 阅读4分钟

序言

做开发有很长一段时间了,现在处于离职Gap阶段,这期间一是休息调整状态(主要天挺热的),二是对工作期间的总结。我入行是iOS开发,后面为了提高效率,以及对齐不同端的业务逻辑,团队选择了Flutter,以module方式做混合开发。最初使用的Flutter版本是2.5.3,2年后我们把Flutter版本升级到了3.7.12,写文章时最新的版本是3.32.8,对之前所学的内容做一个总结和更新。

基本类型

  1. Dart里面的基本类型包括 Number(int,double)、List、String、bool、Function、Set、Map等;这些基本类型不管什么语言都有,区别可能就是Dart中都是对象类型,在使用对比起来,确实Dart体验要好一点(Api统一、一致的错误处理,也符合一切皆对象的思想),也更安全。
  2. 新增了Record类型,声明时也支持位置参数和命名参数 (类型A,类型B)或({类型A,类型B})。Record类型用处还是很大的,在3.7.12版本是没有这个类型的,比如数值类的展示,有些公司会要求超过万亿要转化为数值+单位的形式展示,再比如有些数据处理完后要返回多个结果,当然可以采用创建类或使用Map去解决,但是有时候真没必要(冗余一个类、Map的key还要定义常量)。这时候Record类型就太方便了。
({int a, int b}) recordAB = (a: 1, b: 2); 
({int x, int y}) recordXY = (x: 3, y: 4);
var record = ('first', a: 2, b: true, 'last');
print(record.$1); // Prints 'first' 
print(record.a); // Prints 2 
print(record.b); // Prints true 
print(record.$2); // Prints 'last'

  1. 增强型枚举
enum PlanetType { terrestrial, gas, ice }
enum Planet {
  mercury(planetType: PlanetType.terrestrial, moons: 0, hasRings: false),
  venus(planetType: PlanetType.terrestrial, moons: 0, hasRings: false),
  
  uranus(planetType: PlanetType.ice, moons: 27, hasRings: true),
  neptune(planetType: PlanetType.ice, moons: 14, hasRings: true);

  /// A constant generating constructor
  const Planet({
    required this.planetType,
    required this.moons,
    required this.hasRings,
  });

  /// All instance variables are final
  final PlanetType planetType;
  final int moons;
  final bool hasRings;

  bool get isGiant => planetType == PlanetType.gas || planetType == PlanetType.ice;
}   
    
  1. 类修饰符:

abstract:定义不能被实例化的类,通常包含抽象方法

base:只能被继承(extends),不能被实现(implements),确保子类继承父类的实现

final:不能被继承或实现,创建不可扩展的类

interface:只能被实现(implements),不能被继承(extends),定义契约

sealed:密封类,创建有限的子类集合,常用于模式匹配,只能在同一库中被继承

mixin:提供可重用的代码片段,可以被多个类混入

异步

  1. Future:
/// 方式一
Future(() => {},).then((value) {}).catchError((error) {}).whenComplete(() {});
/// 方式二:使用async/await

  1. Stream:实际工作中基本没遇到过,也没使用过,TODO一下,后面着重学习下;

IsoLate

Flutter是单线程,在主ioslate中运行。每一个Dart的isolate都是独立的运行线程,他们无法与其他isolate共享内存,因此也不会存在锁的问题。不同的isolate之间只能通过互发消息通信,所以开销是低于多线程的。

1.spawnUri():
spawnUri方法有三个必须的参数,
第一个是Uri,指定一个新Isolate代码文件的路径,
第二个是参数列表,类型是List,
第三个是动态消息。
需要注意,用于运行新Isolate的代码文件中,必须包含一个main函数,它是新Isolate的入口方法,
该main函数中的args参数列表,正对应spawnUri中的第二个参数。
如不需要向新Isolate中传参数,该参数可传空List
import 'dart:isolate'; 
void main() { 
    print("main isolate start");
    create_isolate();
    print("main isolate stop"); 
} 
// 创建一个新的 isolate 
create_isolate() async { 
    ReceivePort rp = new ReceivePort(); 
    SendPort port1 = rp.sendPort; 
    Isolate newIsolate = await Isolate.spawnUri(new Uri(path: "./other_task.dart"), ["hello, isolate", "this is args"], port1);
    SendPort port2; 
    rp.listen((message) { 
    print("main isolate message: $message"); 
    if (message[0] == 0){ 
        port2 = message[1]; 
    }else{ 
        port2?.send([1,"这条信息是 main isolate 发送的"]); 
     } 
    }); 
    // 可以在适当的时候,调用以下方法杀死创建的 isolate
    // newIsolate.kill(priority: Isolate.immediate); 
  }

创建【other_task.dart】,编写新的Isolate代码

import 'dart:isolate'; 
import 'dart:io'; 
void main(args, SendPort port1) { 
    print("isolate_1 start"); 
    print("isolate_1 args: $args");
    ReceivePort receivePort = new ReceivePort(); 
    SendPort port2 = receivePort.sendPort; 
    receivePort.listen((message){ 
        print("isolate_1 message: $message"); 
    });
    // 将当前 isolate 中创建的SendPort发送到主 isolate中用于通信 
    port1.send([0, port2]); // 模拟耗时5秒 
    sleep(Duration(seconds:5));
    port1.send([1, "isolate_1 任务完成"]); 
    print("isolate_1 stop"); 
}
2.spawn();
除了使用spawnUri,更常用的是使用spawn方法来创建新的Isolate,
我们通常希望将新创建的Isolate代码和main Isolate代码写在同一个文件,
且不希望出现两个main函数,而是将指定的耗时函数运行在新的Isolate,
这样做有利于代码的组织和代码的复用。
spawn方法有两个必须的参数,
第一个是需要运行在新Isolate的耗时函数,
第二个是动态消息,该参数通常用于传送主IsolateSendPort对象。
import 'dart:isolate'; 
import 'dart:io'; 

void main() { 
    print("main isolate start"); 
    create_isolate();
    print("main isolate end");
} 
// 创建一个新的 isolate 
create_isolate() async{ 
    ReceivePort rp = new ReceivePort();
    SendPort port1 = rp.sendPort; 
    Isolate newIsolate = await Isolate.spawn(doWork, port1); 
    SendPort port2; 
    rp.listen((message){
        print("main isolate message: $message"); 
        if (message[0] == 0){ 
            port2 = message[1]; 
        }else{ 
            port2?.send([1,"这条信息是 main isolate 发送的"]); 
        } 
     });
 } 
// 处理耗时任务 
void doWork(SendPort port1){ 
    print("new isolate start");
    ReceivePort rp2 = new ReceivePort(); 
    SendPort port2 = rp2.sendPort; 
    rp2.listen((message){
        print("doWork message: $message"); 
    }); 
    // 将新isolate中创建的SendPort发送到主isolate中用于通信 
    port1.send([0, port2]); 
    // 模拟耗时5秒
    sleep(Duration(seconds:5));
    port1.send([1, "doWork 任务完成"]); 
    print("new isolate end");
}

无论如何,在Dart中创建一个Isolate都显得有些繁琐,可惜的是Dart官方并未提供更高级封装。但是,如果想在Flutter中创建Isolate,则有更简便的API,这是由Flutter官方进一步封装ReceivePort而提供的更简洁API。

使用compute函数来创建新的Isolate并执行耗时任务

import 'package:flutter/foundation.dart'; 
import 'dart:io'; 
// 创建一个新的Isolate,在其中运行任务doWork 
create_new_task() async{ 
    var str = "New Task"; 
    var result = await compute(doWork, str); 
    print(result);
} 
String doWork(String value){ 
    print("new isolate doWork start");
    // 模拟耗时5秒 
    sleep(Duration(seconds:5)); 
    print("new isolate doWork end"); 
    return "complete:$value"; 
}

compute函数有两个必须的参数,第一个是待执行的函数,这个函数必须是一个顶级函数,不能是类的实例方法,可以是类的静态方法,第二个参数为动态的消息类型,可以是被运行函数的参数。需要注意,使用compute应导入'package:flutter/foundation.dart'包。