1require "delegate"
2
3# Weak Reference class that allows a referenced object to be
4# garbage-collected.
5#
6# A WeakRef may be used exactly like the object it references.
7#
8# Usage:
9#
10#   foo = Object.new            # create a new object instance
11#   p foo.to_s                  # original's class
12#   foo = WeakRef.new(foo)      # reassign foo with WeakRef instance
13#   p foo.to_s                  # should be same class
14#   GC.start                    # start the garbage collector
15#   p foo.to_s                  # should raise exception (recycled)
16#
17# == Example
18#
19# With help from WeakRef, we can implement our own redimentary WeakHash class.
20#
21# We will call it WeakHash, since it's really just a Hash except all of it's
22# keys and values can be garbage collected.
23#
24#     require 'weakref'
25#
26#     class WeakHash < Hash
27#       def []= key, obj
28#         super WeakRef.new(key), WeakRef.new(obj)
29#       end
30#     end
31#
32# This is just a simple implementation, we've opened the Hash class and changed
33# Hash#store to create a new WeakRef object with +key+ and +obj+ parameters
34# before passing them as our key-value pair to the hash.
35#
36# With this you will have to limit your self to String key's, otherwise you
37# will get an ArgumentError because WeakRef cannot create a finalizer for a
38# Symbol. Symbols are immutable and cannot be garbage collected.
39#
40# Let's see it in action:
41#
42#   omg = "lol"
43#   c = WeakHash.new
44#   c['foo'] = "bar"
45#   c['baz'] = Object.new
46#   c['qux'] = omg
47#   puts c.inspect
48#   #=> {"foo"=>"bar", "baz"=>#<Object:0x007f4ddfc6cb48>, "qux"=>"lol"}
49#
50#   # Now run the garbage collector
51#   GC.start
52#   c['foo'] #=> nil
53#   c['baz'] #=> nil
54#   c['qux'] #=> nil
55#   omg      #=> "lol"
56#
57#   puts c.inspect
58#   #=> WeakRef::RefError: Invalid Reference - probably recycled
59#
60# You can see the local variable +omg+ stayed, although it's reference in our
61# hash object was garbage collected, along with the rest of the keys and
62# values. Also, when we tried to inspect our hash, we got a WeakRef::RefError,
63# this is because these objects were also garbage collected.
64
65class WeakRef < Delegator
66
67  ##
68  # RefError is raised when a referenced object has been recycled by the
69  # garbage collector
70
71  class RefError < StandardError
72  end
73
74  @@__map = ::ObjectSpace::WeakMap.new
75
76  ##
77  # Creates a weak reference to +orig+
78  #
79  # Raises an ArgumentError if the given +orig+ is immutable, such as Symbol,
80  # Fixnum, or Float.
81
82  def initialize(orig)
83    case orig
84    when true, false, nil
85      @delegate_sd_obj = orig
86    else
87      @@__map[self] = orig
88    end
89    super
90  end
91
92  def __getobj__ # :nodoc:
93    @@__map[self] or defined?(@delegate_sd_obj) ? @delegate_sd_obj :
94      Kernel::raise(RefError, "Invalid Reference - probably recycled", Kernel::caller(2))
95  end
96
97  def __setobj__(obj) # :nodoc:
98  end
99
100  ##
101  # Returns true if the referenced object is still alive.
102
103  def weakref_alive?
104    !!(@@__map[self] or defined?(@delegate_sd_obj))
105  end
106end
107
108if __FILE__ == $0
109#  require 'thread'
110  foo = Object.new
111  p foo.to_s                    # original's class
112  foo = WeakRef.new(foo)
113  p foo.to_s                    # should be same class
114  ObjectSpace.garbage_collect
115  ObjectSpace.garbage_collect
116  p foo.to_s                    # should raise exception (recycled)
117end
118