// 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 { /// 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 { /// final R Function() computation; /// /// WeakReference? _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 { /// 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 _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 { /// 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 _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 _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); }