Weak References
A weak reference is a reference to an object which does not force it to
remain in memory. Weak references are created using either the
@weak
annotation or directly using the frost.core.Weak
class.
To understand why we need weak references in the first place, consider this class:
class Node {
def parent:Node?
def children := Array<Node>()
}
Node
contains a normal ("strong") reference to its parent and to an Array
of its children. The
Array
will in turn hold strong references to each Node
inserted into it.
If we create two such Node
objects in a parent-child relationship:
def parent := Node()
def child := Node()
parent.children.add(child)
child.parent := parent
We have now created a reference cycle. parent
refers to child
and child
refers to parent
.
Each of them forces the other to remain in memory; even if no other part of the program refers to
either node, all objects involved in the reference cycle will remain in memory.
We can break the reference cycle using a weak reference:
class Node {
@weak
def parent:Node?
def children := Array<Node>()
}
The @weak
annotation on parent turns off reference counting for that particular reference.
Assigning to parent
will not increase the reference count of the object being pointed to. Our
previous parent-child set of objects no longer remains in memory indefinitely: as soon as the last
strong reference to parent
goes away, the weak reference from child
is insufficient to keep it
in memory and parent
is destroyed. As soon as parent
is destroyed, the last reference to child
is also gone, and child
will be cleaned up as well.
Parent-child relationships are by far the most common kind of reference cycle. By convention,
the reference from child to parent is the one that should be tagged @weak
to break the cycle.
frost.core.Weak
In addition to the @weak
annotation, you may explicitly create frost.core.Weak
objects. Weak
objects hold a reference to an object without affecting its reference count. The Node
class above
could also have been written:
class Node {
def parent := Weak<Node?>(null)
def children := Array<Node>()
}
The only difference between this Node
class and the one above is that you will have to manually
wrap objects in Weak
references:
def parent := Node()
def child := Node()
parent.children.add(child)
child.parent := Weak<Node?>(parent)
and extract them using get()
Console.printLine(child.parent.get())
The @weak
annotation simply instructs Frost to perform these steps automatically; there is no
behavioral difference between using frost.core.Weak
explicitly, or implicitly via @weak
.
Using a weak reference after it is destroyed
Because weak references do not force objects to remain in memory, this opens up the possibility of using an object after it is destroyed:
def w := Weak<String>("Hello" * 3)
Console.printLine(w.get())
The only reference to the result of calling "Hello" * 3
is a weak one, which cannot cause it to
remain in memory. The temporary object will be destroyed as soon as the first statement finishes
executing, and is gone by the time we call w.get()
in the second statement.
This is a safety violation . With safety checks enabled, Frost will catch and report this error. With safety checks disabled, undefined behavior results.