| // 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, `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. |
| /// |
| /// 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. |
| 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, `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, `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 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, |
| /// the `null` value, or certain other types of special objects. |
| external factory WeakReference(T target); |
| |
| /// The current object weakly referenced by [this], 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); |
| /// // Get 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 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 attachement 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]. |
| /// |
| /// If a non-`null` [detach] value is provided, that object can be |
| /// passed to [Finalizer.detach] to remove the attachment 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(); |
| /// // Get 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); |
| } |