前言
村里的老人说:“真正的强者,都是扮猪吃老虎。”
日常开发中经常需要用到弱引用,Dart 语言里也有提供弱引用的接口 ```WeakReference```,我们可以基于它开发更强大的复杂结构。
在前面的文章中,我们用到了一个以弱引用对象为元素的集合 ```WeakSet```,今天我们就来讲一下它是如何实现的。
Collections
常用的 collections 有 Set、List 和 Map,下面我将为大家介绍其对应的弱引用版本该如何快速实现。
WeakSet
弱集合也是集合,所以它应该实现一个集合接口 ```Set``` 的所有功能。
直接上代码:
class WeakSet<E extends Object> implements Set<E> {
WeakSet() : _inner = {};
final Set<WeakReference<E>> _inner;
@override
void clear() => _inner.clear();
@override
Set<E> toSet() {
Set<E> entries = {};
E? item;
for (WeakReference<E> wr in _inner) {
item = wr.target;
if (item != null) {
entries.add(item);
}
}
return entries;
}
@override
List<E> toList({bool growable = true}) => toSet().toList(growable: growable);
@override
String toString() => toSet().toString();
@override
Iterator<E> get iterator => toSet().iterator;
@override
int get length => toSet().length;
@override
bool get isEmpty => toSet().isEmpty;
@override
bool get isNotEmpty => toSet().isNotEmpty;
@override
bool contains(Object? value) => toSet().contains(value);
@override
bool containsAll(Iterable<Object?> other) => toSet().containsAll(other);
@override
E elementAt(int index) => toSet().elementAt(index);
@override
bool add(E value) => !contains(value) && _inner.add(WeakReference(value));
@override
bool remove(Object? value) {
for (var wr in _inner) {
if (wr.target == value) {
return _inner.remove(wr);
}
}
return false;
}
@override
void forEach(void Function(E element) action) => toSet().forEach(action);
// 从略...
}
首先,我们创建一个内置的集合 _inner,其元素是指向元素类型 E 的弱引用;
接下来实现 toSet() 函数,将 _inner 中所有实际指向的对象提取出来,构建一个新的集合返回;
然后其余的 Set 函数接口都可以基于这个 toSet() 来实现。
同样的思路,我们还可以继续实现列表与映射的弱引用版本。
WeakList
先实现最简单的那些接口函数:
class WeakList<E extends Object> implements List<E> {
WeakList() : _inner = [];
final List<WeakReference<E>> _inner;
@override
void clear() => _inner.clear();
@override
List<E> toList({bool growable = true}) {
List<E> entries = [];
E? item;
for (WeakReference<E> wr in _inner) {
item = wr.target;
if (item != null) {
entries.add(item);
}
}
return entries;
}
@override
Set<E> toSet() => toList().toSet();
@override
String toString() => toList().toString();
@override
Iterator<E> get iterator => toList().iterator;
@override
int get length => toList().length;
@override
bool get isEmpty => toList().isEmpty;
@override
bool get isNotEmpty => toList().isNotEmpty;
@override
E get first => toList().first;
@override
set first(E item) => _inner.first = WeakReference(item);
@override
E get last => toList().last;
@override
set last(E item) => _inner.last = WeakReference(item);
@override
E get single => toList().single;
@override
List<E> operator +(List<E> other) => toList() + other;
@override
E operator [](int index) => toList()[index];
@override
void add(E value) => _inner.add(WeakReference(value));
@override
void addAll(Iterable<E> iterable) {
for (var item in iterable) {
add(item);
}
}
@override
bool contains(Object? element) => toList().contains(element);
@override
E elementAt(int index) => toList().elementAt(index);
@override
void forEach(void Function(E element) action) => toList().forEach(action);
@override
int indexOf(E element, [int start = 0]) => toList().indexOf(element, start);
@override
int lastIndexOf(E element, [int? start]) => toList().lastIndexOf(element, start);
@override
String join([String separator = ""]) => toList().join(separator);
@override
bool remove(Object? value) {
/// Removes the first occurrence of [value] from this list.
for (var wr in _inner) {
if (wr.target == value) {
return _inner.remove(wr);
}
}
return false;
}
// 从略...
}
跟上面的 WeakSet 类似,这里也是先创建一个内部的列表 _inner,其元素是指向实际对象的弱引用;接下来实现 toList() 函数,将内部列表中的实际指向对象提取出来构建新的列表;
然后其余关于 List 的接口函数基本都可以基于这个 toList() 函数来实现。
但是也有不同之处。由于 Set 是无序的,所以即使其中的弱引用所指向的对象已销毁,也不会影响集合运算。
而 List 是有序的,所以一些跟序号相关的操作则需要忽略那些对象已销毁的弱引用元素。
这里我采用的犯法是增加一个 purge() 函数,用于清除那些“无用”的元素,然后再基于它去实现那些和序号相关的接口函数。
class WeakList<E extends Object> implements List<E> {
WeakList() : _inner = [];
final List<WeakReference<E>> _inner;
void purge() => _inner.removeWhere((wr) => wr.target == null);
@override
set length(int size) {
purge();
_inner.length = size;
}
@override
void operator []=(int index, E value) {
purge();
_inner[index] = WeakReference(value);
}
@override
void insert(int index, E element) {
purge();
_inner.insert(index, WeakReference(element));
}
@override
void insertAll(int index, Iterable<E> iterable) {
purge();
for (var item in iterable) {
_inner.insert(index, WeakReference(item));
index += 1;
}
}
@override
E removeAt(int index) {
purge();
var wr = _inner.removeAt(index);
return wr.target;
}
@override
E removeLast() {
purge();
var wr = _inner.removeLast();
return wr.target;
}
// 从略...
}
WeakValueMap
Talk is cheap, show you the codes!
class WeakValueMap<K, V> implements Map<K, V> {
WeakValueMap() : _inner = {};
final Map<K, WeakReference<dynamic>?> _inner;
void purge() => _inner.removeWhere((key, wr) => wr?.target == null);
@override
void clear() => _inner.clear();
Map<K, V> toMap() {
// remove empty entries
purge();
// convert entries
return _inner.map((key, wr) => MapEntry(key, wr?.target));
}
@override
String toString() => toMap().toString();
@override
bool containsValue(Object? value) => toMap().containsValue(value);
@override
bool containsKey(Object? key) => _inner[key]?.target != null;
@override
V? operator [](Object? key) => _inner[key]?.target;
@override
void operator []=(K key, V value) =>
_inner[key] = value == null ? null : WeakReference(value);
@override
Iterable<MapEntry<K, V>> get entries => toMap().entries;
@override
void addAll(Map<K, V> other) => other.forEach((key, value) {
this[key] = value;
});
@override
V? remove(Object? key) => _inner.remove(key)?.target;
@override
void forEach(void Function(K key, V value) action) => _inner.forEach((key, wr) {
V val = wr?.target;
if (val != null) {
action(key, val);
}
});
@override
Iterable<K> get keys => toMap().keys;
@override
Iterable<V> get values => toMap().values;
@override
int get length => toMap().length;
@override
bool get isEmpty => toMap().isEmpty;
@override
bool get isNotEmpty => toMap().isNotEmpty;
// 从略...
}
课余练习
- WeakKeyMap 如何实现?
提示:可以先实现一个用于包裹 key 的类,以 key 对应的对象为参数初始化(内部用一个弱引用指向它),同时将 key 对象的 hash 值等信息保存起来,以便后面 key 对象消失时仍然可以进行相应的运算。
应用示例
以上类的使用很简单,根普通的 Set、List、Map 几乎没有差别:
// WeakSet
Set<Observer> listenerSet = WeakSet();
listenerSet.add(obs1);
listenerSet.add(obs2);
// WeakList
List<Observer> listenerList = WeakList();
listenerList.add(obs1);
listenerList.add(obs2);
// WeakMap
Map<String, Observer> listenerMap = WeakValueMap();
listenerMap['Albert'] = obs1;
listenerMap['Ben'] = obs2;
代码引用
由于我已将这部分代码提交到了 pub.dev,所以在实际应用中,你只需要在项目工程文件 ```pubspec.yaml``` 中添加:
dependencies:
object_key: ^0.1.1
然后在需要使用的 dart 文件头引入即可:
import 'package:object_key/object_key.dart';
全部源码
GitHub 地址:
https://github.com/moky/ObjectKey/tree/main/object_key/lib/src
结语
弱引用可以有效避免对象循环引用等原因导致的内存泄漏问题。
在某些实践中,还可以实现类似“自动退出”的效果,比如前面介绍的观察者模式的实战中,合理使用基于弱引用的集合,可以达到当观察者销毁时即自动注销的效果,十分方便!
如有其他问题,可以下载登录 Tarsier 与我交流(默认通讯录里找 Albert Moky / 章北海)