When you assign an object to a variable, you store a reference to that variable. The existence of that reference prevents the object from being collected by the garbage collector. If your object is merely an observer, if it only wants to periodically query a object should it still exist, storing a reference to that object will keep that object in memory as long as that reference exists, even though the object that originally created that object may have long ago deleted any references to the object. The solution to this problem is a weak reference, implemented by the WeakRef class.
ObjectSpace and Object IDs
Every Ruby object has an ID. This ID is used internally and, in the MRI implementation, is a pointer to the C structure that describes the object. This ID could be something different on each implementation, but it's guaranteed that this number is unique for every object. You can access any object's ID by calling some_object.object_id.
Ruby provides a reflection interface called ObjectSpace. Through this object you can iterate over all objects Ruby has created, all objects of a certain type, etc. You can also get a reference to any object given an object ID by calling ObjectSpace._id2ref(some_id).
WeakRef works by storing the ID of the object in question, instead of a reference to the object itself. The WeakRef class itself is a Delegator object, a class that simply passes all method calls on to another object. This is how WeakRef works: storing the object's ID, retrieving the reference using ObjectSpace and passing method calls on to the object as a Delegator. If the object has been garbage collected and no longer exists, an exception of the type WeakRef::RefError will be raised.
The WeakRef.new method takes a single argument, the object you want to take a weak reference to. Any methods you call on the WeakRef object will be passed to the referenced object. The only method that WeakRef adds into the mix directly is the weakref_alive?, which returns true if the referenced object hasn't been garbage collected.
In this example, there are two threads. The first thread simulates a worker thread. It's really just sleeping for 3 seconds and, when it's finished it deletes the obj object. The second thread is an observer thread. It has a weak reference to the obj object. Since it doesn't want to disturb the state of the program by artificially keeping the obj object alive, it's using a weak reference. As long as the reference is alive, it will wait for the first thread.
#!/usr/bin/env ruby require 'weakref' obj = "Testing string" a = Thread.new do sleep 3 obj = nil GC.start end b = Thread.new do ref = WeakRef.new(obj) print "Job is running" loop do print "." break if ref.weakref_alive? sleep 1 end end a.join b.join
While this might be useful in a pinch, it might not be a long term solution. Since Object IDs are usually just memory locations, the allocator can reuse memory locations. Imagine the following scenario: you create a new object and make a weak reference to it. That object is deleted and sometime in the future a new object with the same ID is created. If you check the WeakRef object, it will imply the original object is still alive. Without knowing more about the object, it's very difficult to tell if the original object is still alive.
So this is not a class you'll see very often. It's not often you see code that depends on the existence of another object in Ruby. It's not even a very common use case. It is, however, a class that can bewilder anyone encountering it in the wild. It's not immediately clear how it works, especially if you don't know about object IDs or ObjectSpace. This is another "everyone should know about it, but you may never end up using it" class.