-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
weak.dart
316 lines (304 loc) · 12.1 KB
/
weak.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of dart.core;
// Examples can assume:
// class DBConnection {
// DBConnection._();
// factory DBConnection.connect() => DBConnection._();
// void close() {}
// }
/// An [Expando] allows adding new properties to objects.
///
/// Does not work on numbers, strings, booleans, records, `null`,
/// `dart:ffi` pointers, `dart:ffi` structs, or `dart:ffi` unions.
///
/// An `Expando` does not hold on to the added property value after an object
/// becomes inaccessible.
///
/// Since you can always create a new number that is identical to an existing
/// number, it means that an expando property on a number could never be
/// released. To avoid this, expando properties cannot be added to numbers.
/// The same argument applies to strings, booleans and `null`, which also have
/// literals that evaluate to identical values when they occur more than once.
/// In addition, expando properties can not be added to records because
/// records do not have a well-defined persistent identity.
///
/// There is no restriction on other classes, even for compile time constant
/// objects. Be careful if adding expando properties to compile time constants,
/// since they will stay alive forever.
final class Expando<T extends Object> {
/// The name of the this [Expando] as passed to the constructor.
///
/// If no name was passed to the constructor, the value is the `null` value.
final String? name;
/// Creates a new [Expando]. The optional name is only used for
/// debugging purposes and creating two different [Expando]s with the
/// same name yields two [Expando]s that work on different properties
/// of the objects they are used on.
external Expando([String? name]);
/// Expando toString method override.
String toString() => "Expando:$name";
/// Gets the value of this [Expando]'s property on the given object.
///
/// If the object hasn't been expanded, the result is the `null` value.
///
/// The object must not be a number, a string, a boolean, a record, `null`,
/// a `dart:ffi` pointer, a `dart:ffi` struct, or a `dart:ffi` union.
external T? operator [](Object object);
/// Sets this [Expando]'s property value on the given object to [value].
///
/// Properties can effectively be removed again
/// by setting their value to `null`.
///
/// The object must not be a number, a string, a boolean, a record, `null`,
/// a `dart:ffi` pointer, a `dart:ffi` struct, or a `dart:ffi` union.
external void operator []=(Object object, T? value);
}
/// A weak reference to a Dart object.
///
/// A _weak_ reference to the [target] object which may be cleared
/// (set to reference `null` instead) at any time
/// when there is no other way for the program to access the target object.
///
/// _Being the target of a weak reference does not keep an object
/// from being garbage collected._
///
/// There are no guarantees that a weak reference will ever be cleared
/// even if all references to its target are weak references.
///
/// Not all objects are supported as targets for weak references.
/// The [WeakReference] constructor will reject any object that is not
/// supported as an [Expando] key.
///
/// Use-cases like caching can benefit from using weak references. Example:
///
/// ```dart
/// /// [CachedComputation] caches the computation result, weakly holding
/// /// on to the cache.
/// ///
/// /// If nothing else in the program is holding on the result, and the
/// /// garbage collector runs, the cache is purged, freeing the memory.
/// ///
/// /// Until the cache is purged, the computation will not run again on
/// /// a subsequent request.
/// ///
/// /// Example use:
/// /// ```
/// /// final cached = CachedComputation(
/// /// () => jsonDecode(someJsonSource) as Object);
/// /// print(cached.result); // Executes computation.
/// /// print(cached.result); // Most likely uses cache.
/// /// ```
/// class CachedComputation<R extends Object> {
/// final R Function() computation;
///
/// WeakReference<R>? _cache;
///
/// CachedComputation(this.computation);
///
/// R get result {
/// final cachedResult = _cache?.target;
/// if (cachedResult != null) {
/// return cachedResult;
/// }
///
/// final result = computation();
///
/// // WeakReferences do not support nulls, bools, numbers, and strings.
/// if (result is! bool && result is! num && result is! String) {
/// _cache = WeakReference(result);
/// }
///
/// return result;
/// }
/// }
/// ```
@Since("2.17")
abstract final class WeakReference<T extends Object> {
/// Creates a [WeakReference] pointing to the given [target].
///
/// The [target] must be an object supported as an [Expando] key,
/// which means [target] cannot be a number, a string, a boolean, a record,
/// the `null` value, or certain other types of special objects.
external factory WeakReference(T target);
/// The current object weakly referenced by this [WeakReference], if any.
///
/// The value is either the object supplied in the constructor,
/// or `null` if the weak reference has been cleared.
T? get target;
}
/// A finalizer which can be attached to Dart objects.
///
/// A finalizer can create attachments between
/// the finalizer and any number of Dart values,
/// by calling [attach] with the value, along with a
/// _finalization token_ and an optional _attach key_,
/// which are part of the attachment.
///
/// When a Dart value becomes inaccessible to the program,
/// any finalizer that currently has an attachment to
/// the value *may* have its callback function called
/// with the attachment's finalization token.
///
/// Example:
/// ```dart
/// class Database {
/// // Keeps the finalizer itself reachable, otherwise it might be disposed
/// // before the finalizer callback gets a chance to run.
/// static final Finalizer<DBConnection> _finalizer =
/// Finalizer((connection) => connection.close());
///
/// final DBConnection _connection;
///
/// Database._fromConnection(this._connection);
///
/// factory Database.connect() {
/// // Wraps the connection in a nice user API,
/// // *and* closes connection if the user forgets to.
/// final connection = DBConnection.connect();
/// final wrapper = Database._fromConnection(connection);
/// // Calls finalizer callback when `wrapper` is no longer reachable.
/// _finalizer.attach(wrapper, connection, detach: wrapper);
/// return wrapper;
/// }
///
/// void close() {
/// // User requested close.
/// _connection.close();
/// // Detach from finalizer, no longer needed.
/// _finalizer.detach(this);
/// }
///
/// // Some useful methods.
/// }
/// ```
/// This example has an example of an external resource that needs clean-up.
/// The finalizer is used to clean up an external connection when the
/// user of the API no longer has access to that connection.
/// The example uses the same object as attached object and detach key,
/// which is a useful approach when each attached object can be detached
/// individually. Being a detachment key doesn't keep an object alive.
///
/// No promises are made that the callback will ever be called.
/// The only thing that is guaranteed is that if a finalizer's callback
/// is called with a specific finalization token as argument,
/// then at least one value with an attachment to the finalizer
/// that has that finalization token,
/// is no longer accessible to the program.
///
/// If the finalizer *itself* becomes unreachable,
/// it's allowed to be garbage collected
/// and then it won't trigger any further callbacks.
/// Always make sure to keep the finalizer itself reachable while it's needed.
///
/// If multiple finalizers are attached to a single object,
/// or the same finalizer is attached multiple times to an object,
/// and that object becomes inaccessible to the program,
/// then any number (including zero) of those attachments may trigger
/// their associated finalizer's callback.
/// It will not necessarily be all or none of them.
///
/// Finalization callbacks will happen as *events*.
/// They will not happen during execution of other code,
/// and not as a microtask,
/// but as high-level events similar to timer events.
///
/// Finalization callbacks must not throw.
///
/// When running on the Dart native runtime, and the callback is a native
/// function rather than a Dart function, use `dart:ffi`'s [NativeFinalizer]
/// instead.
@Since("2.17")
abstract final class Finalizer<T> {
/// Creates a finalizer with the given finalization callback.
///
/// The [callback] is bound to the current zone
/// when the [Finalizer] is created, and will run in that zone when called.
external factory Finalizer(void Function(T) callback);
/// Attaches this finalizer to [value].
///
/// When [value] is no longer accessible to the program,
/// while still having an attachment to this finalizer,
/// the callback of this finalizer *may* be called
/// with [finalizationToken] as argument.
/// The callback may be called at most once per active attachment,
/// ones which have not been detached by calling [Finalizer.detach].
///
/// The [detach] value is only used by the finalizer to identify the current
/// attachment. If a non-`null` [detach] value is provided, the same
/// (identical) value can be passed to [Finalizer.detach] to remove the
/// attachment. If no (non-`null`) value is provided, the attachment cannot
/// be removed again.
///
/// The [value] and [detach] arguments do not count towards those
/// objects being accessible to the program.
/// Both must be objects supported as an [Expando] key.
/// They may be the *same* object.
///
/// Example:
/// ```dart
/// class Database {
/// // Keeps the finalizer itself reachable, otherwise it might be disposed
/// // before the finalizer callback gets a chance to run.
/// static final Finalizer<DBConnection> _finalizer =
/// Finalizer((connection) => connection.close());
///
/// factory Database.connect() {
/// // Wraps the connection in a nice user API,
/// // *and* closes connection if the user forgets to.
/// final connection = DBConnection.connect();
/// final wrapper = Database._fromConnection();
/// // Calls finalizer callback when `wrapper` is no longer reachable.
/// _finalizer.attach(wrapper, connection, detach: wrapper);
/// return wrapper;
/// }
///
/// Database._fromConnection();
///
/// // Some useful methods.
/// }
/// ```
///
/// Multiple objects may be attached using the same finalization token,
/// and the finalizer can be attached multiple times to the same object
/// with different, or the same, finalization token.
void attach(Object value, T finalizationToken, {Object? detach});
/// Detaches this finalizer from values attached with [detach].
///
/// Each attachment between this finalizer and a value,
/// which was created by calling [attach] with the [detach] object as
/// `detach` argument, is removed.
///
/// If the finalizer was attached multiple times to the same value
/// with different detachment keys,
/// only those attachments which used [detach] are removed.
///
/// After detaching, an attachment won't cause any callbacks to happen
/// if the object become inaccessible.
///
/// Example:
/// ```dart
/// class Database {
/// // Keeps the finalizer itself reachable, otherwise it might be disposed
/// // before the finalizer callback gets a chance to run.
/// static final Finalizer<DBConnection> _finalizer =
/// Finalizer((connection) => connection.close());
///
/// final DBConnection _connection;
///
/// Database._fromConnection(this._connection);
///
/// void close() {
/// // User requested close.
/// _connection.close();
/// // Detach from finalizer, no longer needed.
/// // Was attached using this object as `detach` token.
/// _finalizer.detach(this);
/// }
///
/// // Some useful methods.
/// }
/// ```
void detach(Object detach);
}