class Person { const Person(this.name); final String name; } final Person bob = Person("Bob"); print(bob == Person("Bob")); // false那么如果需要它们相等,如下代码所示,我们需要 override == 操作符去自定义所需的判断逻辑,这样看起来貌似也不麻烦,但是如果一个类参数很多,那么类似的重复性代码就会很多,这时候就需要 equatable 这个包来减轻工作量。
class Person { const Person(this.name); final String name; @override bool operator ==(Object other) => identical(this, other) || other is Person && runtimeType == other.runtimeType && name == other.name; @override int get hashCode => name.hashCode; }如下代码所示,通过 equatable 你只需要 extends Equatable ,然后 override props 参数即可实现对应的 ==自定义,这样从代码层级上看是不是更清晰简约了?
import 'package:equatable/equatable.dart'; class Person extends Equatable { const Person(this.name); final String name; @override List<Object> get props => [name]; } // 堆代码 duidaima.com class Person2 extends Equatable { const Person2(this.name, [this.age]); final String name; final int? age; @override List<Object?> get props => [name, age]; }当然,对于 equatable 还是有一些限制,例如所有成员变量都必须是 final, 因为 Dart 官方在说明自定义 == 逻辑就表示过, 用可变值覆盖 hashCode 可能会破坏基于哈希的集合:
@override bool operator ==(Object other) { return identical(this, other) || other is Equatable && runtimeType == other.runtimeType && equals(props, other.props); } @override int get hashCode => runtimeType.hashCode ^ mapPropsToHashCode(props);首先自定义的 equals 判断其实就是对于两个 class 的 props 列表进行拆分判断,这里主要需要注意的是,由于类变量可以是任何对象,那么也就可以能是集合,例如 Map、Set 等,所以需要用到 Dart 的 DeepCollectionEquality 对象来处理,可以减轻很多判断的工作量。
const DeepCollectionEquality _equality = DeepCollectionEquality(); /// Determines whether [list1] and [list2] are equal. bool equals(List<Object?>? list1, List<Object?>? list2) { if (identical(list1, list2)) return true; if (list1 == null || list2 == null) return false; final length = list1.length; if (length != list2.length) return false; for (var i = 0; i < length; i++) { final unit1 = list1[i]; final unit2 = list2[i]; if (_isEquatable(unit1) && _isEquatable(unit2)) { if (unit1 != unit2) return false; } else if (unit1 is Iterable || unit1 is Map) { if (!_equality.equals(unit1, unit2)) return false; } else if (unit1?.runtimeType != unit2?.runtimeType) { return false; } else if (unit1 != unit2) { return false; } } return true; } bool _isEquatable(Object? object) { return object is Equatable || object is EquatableMixin; }而对于生成哈希,equatable 用了 Jenkins 哈希算法,核心就是将任意长度的数值转换为固定长度的哈希值,算法的实现也相对简单,它只需要利用位移操作和迭代来生成哈希值,通过不断递归将所有参数进行 Jenkins 哈希计算,例如:
int mapPropsToHashCode(Iterable<Object?>? props) { return _finish(props == null ? 0 : props.fold(0, _combine)); } int _combine(int hash, Object? object) { if (object is Map) { object.keys .sorted((Object? a, Object? b) => a.hashCode - b.hashCode) .forEach((Object? key) { hash = hash ^ _combine(hash, [key, (object! as Map)[key]]); }); return hash; } if (object is Set) { object = object.sorted((Object? a, Object? b) => a.hashCode - b.hashCode); } if (object is Iterable) { for (final value in object) { hash = hash ^ _combine(hash, value); } return hash ^ object.length; } hash = 0x1fffffff & (hash + object.hashCode); hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); return hash ^ (hash >> 6); } int _finish(int hash) { hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); hash = hash ^ (hash >> 11); return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); }到这里我们大概就解析了 equatable 的作用和实现,但是其实在使用上还不够优雅简介,因为需要手写 props 和显式继承的操作,还是让人觉得侵入性太强,那么这时候就该说宏编程的作用了。
@Equatable() class Person { const Person(this.name); final String name; }可以看到,他就是一个正常的 class ,你只需要添加 @Equatable() 注释,它就拥有了前面所说的 equatable class 的特性,这样看是不是优雅和简单了不少?并且和之前旧的 build_runner 等不同,它不会在你项目里直接生产 .g.dart 的文件。在引入带有宏编程的 equatable 包之后,只需要运行 flutter run --enable-experiment=macros ,就可以直接得到之前一样的结果:
.现在你就可以通过 dart pkg/vm/bin/dump_kernel.dart xxxxxx/app.dill xxxxxx/app.dill.txt 去 dump kernel ,这里的 pkg/vm/bin/dump_kernel.dart 路径就是前面 sdk 下的路径。