Weak References

From Xojo Documentation

Revision as of 22:21, 16 March 2021 by Gperlman (talk | contribs) (See Also)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

You create an object by using the New command. This creates a reference to the object and increases its reference count. You can then pass this reference around to other methods or assign it to other variables and properties. Each time a new reference to the object is added, the reference count for the object increases. There may be times when you want to get a reference to an object, but you do not want to increase the reference count. You do this using the WeakRef class.

To get a weak reference to an object:

// object is an existing reference to an object created with New
Var ref As WeakRef
ref = New WeakRef(object)

The Value property of the WeakRef returns Nil if the object is no longer available. If the object is still available, it returns the object (which you will likely want to cast to the appropriate type) with increasing the reference count. You are now responsible for managing the memory used by this Weak Reference.

For example, this code declares an instance of a FolderItem that will go out of scope and have its reference counter decreased. A weak reference is assigned to the FolderItem instance. While the instance is available, its name is displayed. When the instance is removed from memory, the weak reference value become Nil, so you now know there are no more references to it:

Var ref As WeakRef
If True Then
Var f As New FolderItem
f.Name = "TestFile.txt"
ref = New WeakRef(f)
If ref.Value <> Nil Then
// This displays
MessageBox("f is Nil.")
End If
End If

If ref.Value <> Nil Then
MessageBox("f is Nil.") // This displays
End If

Explaining Weak References

When you create an object, say a Dictionary, divorce in your mind the object from the variable or property that holds it. The object is out there somewhere and the variable is your bridge to it.

Var goldenGate As New Dictionary

You can assign that object to more than one variable/property, and that creates more bridges, but there is still only one object.

Var goldenGate As New Dictionary
Var brooklyn As Dictionary = goldenGate
Var williamsburg As Dictionary = brooklyn

When there are no more ways to access the object, that is, when all the bridges are gone, the object goes away.

Var goldenGate As New Dictionary // 1 bridge
Var brooklyn As Dictionary = goldenGate // 2 bridges

goldenGate = Nil // Well, one bridge left
brooklyn = Nil // All bridges gone, the object is destroyed

This is known as "reference counting". When a variable refers to an object, there is a reference and the object sticks around. When there are no more references, the object is destroyed.

But there are times when you want to be able to access an object without creating a reference to it. In other words, you want to see if an object still exists, and create a reference to it if it does, without forcing it to stick around. In this example, think of it as a wire to the object. If there are other bridges to the object, the wire will lead you to it too and let you build a bridge. If all the bridges are gone, the wire snaps and leads nowhere.

For these times, you can use a WeakRef, or weak reference, that will let you get to an object without forcing it to stick around in memory if the rest of your code is done with it.

Var d As New Dictionary // the object has one reference
Var w As New WeakRef(d) // still one reference
Var d1 As Dictionary = Dictionary(w.Value) // Now two references
d = Nil // Back to one reference, w.Value <> Nil
d1 = Nil // No more references, w.Value = Nil

Weak Reference Examples

So when might you want a WeakRef instead of merely assigning the object to another variable?

Circular References

The most common use is to prevent a circular reference, that is, an object that holds a second object where the second object also holds the first object. With a circular reference, you can lose access to an object that will never be destroyed, creating a memory leak.

The most common scenario here is the parent/child relationship. Let's say you create a Tree class that can hold many Leaf classes in an array. From any Leaf, you want to be able to get back to its Tree. That is, the Tree is the parent and the many Leafs are its children. Consider this simple example where a Tree has a single Leaf:

Var t As New Tree
Var l As New Leaf

t.Child = l
l.Parent = t

t = Nil
l = Nil

Even though you've set both variables to Nil, both still exist because each refers to the other. To prevent that, you'd have to change the destruction code to something like this instead:

t.Child = Nil
t = Nil // The Tree object sill exists because l.Parent refers to it
l = Nil // No more references to the Leaf, and since it's
        // gone, no more to the Tree either

But there is an easier way. Instead of defining Leaf.Parent as a Tree, define it as a WeakRef instead.

Var t As New Tree
Var l As New Leaf

t.Child = l
l.Parent = New WeakRef(t)

l = Nil // The Leaf still exists because t.Child refers to it
t = Nil // Since t was the only reference, Tree goes away and takes Leaf with it

A Computed Property in Leaf that handles the WeakRef mechanics makes the process painless. But either way, since the only "true" reference is of the Parent to its Child, the problem of the Circular Reference evaporates.


As another example, imagine code that sends objects through queues. While an object is waiting to be processed something might happen that renders it invalid or unnecessary. How would you handle that?

For example, suppose there is a UI where the user can select multiple items where each represents an object that is placed on a queue for processing. After the user has selected multiple objects, they change their mind and unselect a few. How do you remove them from the queue?

You could search the queue for that object and delete it. But what if your code uses multiple queues? What it you might add more in the future? This can all be solved easily with a WeakRef.

The main array holds your objects and each queue holds a WeakRef to that object. When it comes time to process an item in the queue, the code resolves it back to its original object. If it can't, that is, the WeakRef evaluates to Nil because there are no other references to it, the queue processor ignores it and moves on. In this scenario, you can remove the object from every queue at once by simply deleting it from the main array. Instantly there are no more references to the object, so no more object, and the queues simultaneously have empty placeholders in the slots that originally represented that object.

Special thanks to Kem Tekinay for his forum post on WeakRefs.

See Also

WeakRef, Xojo.Core.WeakRef class; Memory Management topic; About WeakRefs forum post