Dart 2.15 发布,快来看看新特性!

本文内容主要翻译自官方的 Dart 2.15特性介绍文章,原文链接:Announcing Dart 2.15

概述

Dart 2.15是伴随着 Flutter 2.8发布的,Flutter 2.8没有太大的更新,更多的是性能优化。这对开发者来说是个好消息,不用担心升级的兼容性问题。关于 Flutter 2.8,可以看一下大神恋猫de小郭写的Flutter 2.8 release 发布,快来看看新特性吧,这篇介绍得很详细了。本篇来介绍一下 Dart 2.15版本的新特性。

使用 worker isolates 实现了快速并发

现代的设备配备的 CPU 都拥有多核,能够同时运行多项任务。对于大多数 Dart 程序,这些 CPU 核心的使用面向开发者是透明的 —— Dart 运行时系统默认会在一个单核心上运行你的 Dart代码,然后使用其他核心来执行系统级的任务,例如异步的 I/O 操作,比如写入文件或发起网络请求。

但是,你的 Dart 代码本身可能也需要并行运行。例如,你可能有一个连续的动画和一个耗时长的任务,例如解析一个大的 JSON 文件。如果附加的任务执行过长,可能导致 UI 界面掉帧或者迟滞。通过将这些附加的任务移到单独的一个核心,可以使动画继续在主线程上执行而不被中断。

Dart 的并发模型基于 isolates —— 与其他 isolate 隔离的独立执行单元,从而避免了大量并发共享内存导致的并发 bug。Dart 中的 isolate 不允许共享可变对象,而是通过消息传递的机制在不同的 isolate 之间共享数据。在 Dart 2.15版本中,对 isolates 做了一系列重大的强化更新。

Dart 2.15对 isolates的工作机制做了重新设计与开发,引入了一个新的概念:isolate groups。在同一个 isolate group 里的 isolate 可以在组内共享程序运行时大量内部数据。这使得组内的独立 isolate 的开销更低。对于已经存在的 isolate group 来说,启动一个新的 isolate 速度相比之前要快上100倍,这是因为我们不再需要初始化程序结构,并且新产生的 isolate 消耗的内存是旧版本的1/10,甚至是1/100

同时,isolate groups 依旧不允许不同的 isolate 共享可变的对象。isolate group 是通过共享堆(shared heap)实现的,这使得后续可以有更好的兼容性。我们可以将对象从一个 isolate传递到另一个,这可用于那些执行返回大量内存块数据的 worker isolate 任务。举个例子,一个worker isolate通过网络调用获取数据,并将数据解析为一个大型的 JSON 对象,最后将这个 JSON 对象传递给main isolate。在 Dart 2.15版本前,返回的结果需要使用深拷贝,如果拷贝的时长超过了画面帧渲染的负荷,会导致 UI 卡顿。

在Dart 2.15版本中,worker isolate 可以调用 Isolate.exit()方法,将它的结果作为其中的参数。然后 Dart 运行时会将包含改结果的内存从worker isolate 传递给 main isolate,而无需进行拷贝操作。在 Flutter 2.8中已经更新了 compute()方法以便利用Isolate.exit() 的这一优势。如果在 Flutter 2.8中使用了 compute()方法,那么升级到了 Flutter 2.8之后会自动获得 worker isolate 的这一性能提升。

最后,Dart 重写了 isolate 的消息传递机制,可以将小型到中型大小的消息提高接近8倍的传递速度。发送消息尤其明显,而且接收消息基本上是在确定的时间内完成。同时,还扩展了 isolates 之间传递的对象类型,包括了函数、闭包和 stacktrace。更多内容,可以查看 SendPort.send()方法的 API 文档。 ​

官方提供了 Dart 2.15版本如何使用isolates 的文档,通过也提供了示例代码

新的语言特性:Constructor tear-offs

Constructor tear-offs 这个术语不太好翻译,其实就是指在程序中我们可以引用对象的方法作为对象进行赋值或作为参数传递。下面是一个例子:

class Greeter {
  final String name;
  Greeter(this.name);
  
  void greet(String who) {
    print('$name says: Hello $who!');
  }
}
void main() {
  final m = Greeter('Michael');
  final g = m.greet; // g holds a function pointer to m.greet.
  g('Leaf'); // Invokes and prints "Michael says: Hello Leaf!"
}
复制代码

变量 g 引用了对象 m 的 greet 方法,然后 g 就类似函数指针了,可以直接来调用。这种方式在 Dart 的核心库中非常常见,例如下面是将上面m对象的 greet 方法传递给了数组的 forEach方法。

final m = Greeter('Michael');
['Lasse', 'Bob', 'Erik'].forEach(m.greet);
// Prints "Michael says: Hello Lasse!", "Michael says: Hello Bob!",
// "Michael says: Hello Erik!"
复制代码

在之前是不支持构造函数使用tear-off这种形式的,而实际上在 Flutter 构建 UI 的场合却需要这种形式。在 Dart 2.15版本就支持这个特性了,下面的代码构建了一个包含多个 Text 组件的 Column 组件,在 map 方法中可以使用 Text 的tear-off 形式来构建 Text 组件了。

class FruitWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
        children: ['Apple', 'Orange'].map(Text.new).toList());
  }
}
复制代码

Text.new 引用了 Text 的默认构造方法。同样也可以使用命名构造函数,例如.map(Text.rich)。 ​

tear-off 相关的变更

对于函数指针,Dart 更新了部分语法来保持一致性。现在可以使用特殊的泛型方法来创建非泛型方法。

T id<T>(T value) => value;
var intId = id<int>; // 2.15新写法
int Function(int) intId = id; // 2.15版本以前
复制代码

甚至还可以使用泛型函数对象创建一个非泛型的函数对象:

const fo = id; // 创建一个 id 的函数对象
const c1 = fo<int>; // 2.15版本可用,之前版本会报错
复制代码

同时,对于泛型还可以不指定类型了。

var y = List; // 2.15版本支持
var z = List<int>; //  2.15版本支持
var z = typeOf<List<int>>(); // 2.15之前版本
复制代码

改进 Dart 核心库的枚举

现在我们可以使用.name 属性获取枚举对应的字符串值了。

enum MyEnum {
  one, two, three
}
void main() {
  print(MyEnum.one.name);  // Prints "one".
}
复制代码

同时也可以使用.byName 构建枚举对象:

print(MyEnum.values.byName('two') == MyEnum.two);  // Prints "true".
复制代码

而且还可以将枚举值集合映射为 map。

final map = MyEnum.values.asNameMap(); 
print(map['three'] == MyEnum.three);  // Prints "true".
复制代码

压缩指针

对于只需要支持32位地址空间的指针,Dart 2.15使用压缩指针技术来提高这类指针在64位 SDK 中的空间占用效率。通过这种方式,在内部测试的应用中,节省了近10%的 Dart 堆空间。这个特性对于那些嵌入式开发者来说,是一个可选配置项。Flutter 2.8版本已经默认为安卓开启了该特性,iOS 版本会在接下来的版本支持。

其他特性

其他特性和开发关系不太大,列举如下:

  • 在 Dart SDK 中包含了 Dart DevTools,从而无需单独下载安装。
  • 在插件发布中增加了新的 pub 特性:这主要是安全检测上,比如你在插件中写了你的个人的密钥,那么发布前会提示你,并且中止发布。比如给出下面的警告:
Publishing my_package 1.0.0 to https://pub.dartlang.org:
Package validation found the following errors:
* line 1, column 1 of lib/key.pem: Potential leak of Private Key detected.
╷
1 │ ┌ - - -BEGIN PRIVATE KEY - - -
2 │ │ H0M6xpM2q+53wmsN/eYLdgtjgBd3DBmHtPilCkiFICXyaA8z9LkJ
3 │ └ - - -END PRIVATE KEY - - -
╵
* line 2, column 23 of lib/my_package.dart: Potential leak of Google OAuth Refresh Token detected.
╷
2 │ final refreshToken = "1//042ys8uoFwZrkCgYIARAAGAQSNwF-L9IrXmFYE-sfKefSpoCnyqEcsHX97Y90KY-p8TPYPPnY2IPgRXdy0QeVw7URuF5u9oUeIF0";
复制代码

这种检测可能会出错,因此也可以使用白名单关闭,白名单是忽略某些文件,可以参考:false_secrets。另外就是为插件发布提供了撤回功能,可以在 pub.dev 的管理后台界面进行操作。当版本撤回后,pub 客户端在执行 pub get 或 pub update 时不再解决这个版本的问题。而在 pubspec.lock 文件中,会看到对应的提示。

$ dart pub get
Resolving dependencies…
mypkg 0.0.181-buggy (retracted, 0.0.182-fixed available)
Got dependencies!
复制代码
  • 解决某些字符集的问题:这个是在有些字符集下可能会出现的 bug,例如下面的例子:
main() {
  final accessLevel = 'user';
  if (accessLevel == 'user‮ .⁦// Check if admin⁩ ⁦') {
    print('You are a regular user.');
  } else {
    print('You are an admin.');
  }
}
复制代码

实际上,某些字符集下,上面的程序会打印出‘'You are an admin.’这种在使用双向 Unicode 字符集就会有问题。如果一个字符是从左到右,一个是从右到左那么就会导致看起来一样的字符串实际不相等。Dart 分析工具提供了这类的检测:

$ dart analyze
Analyzing cvetest...                   2.6s
info • bin/cvetest.dart:4:27 • The Unicode code point 'U+202E'
       changes the appearance of text from how it's interpreted
       by the compiler. Try removing the code point or using the 
       Unicode escape sequence '\u202E'. •
       text_direction_code_point_in_literal
复制代码

2022寄语

这是 Dart 在2021年度的最后一次更新,我们对参与构建 Dart生态系统的成员表示感激。感谢所有提供伟大反馈的人员,你们的持续支持给我们持续前行、成长的动力。在 pub.dev 上发布的成千上万的插件帮助我们完善了整个 Dart生态系统。在2022年度,我们计划了很多让人兴奋的事件,我们已经迫不及待期待明年的到来!

个人感受

感觉 Dart 的语言本身已经很成熟了, 版本更新的改动并不多,更多地在底层性能优化、工具链的完善和更友好的体验上。2022年,很可能是 Flutter Web端和桌面端走向成熟的一年。期待2022年第一季度的新版本吧,会是 Flutter 3.0发布吗?

猜你喜欢

转载自juejin.im/post/7039712897609498631