Joshua B. wrote:
that isa-swizzling is used to replace the class of the property being
observed with a thin pseudo-class that notifies any registered observers
when a change is made, then passes the change to the original class.
Is there a good reason not to do things this way? to do things this way?
The only KVO implementation that I’ve ever used is Cocoa’s, so I’m just
curious about how/why it’s done in Ruby. If I’m making a big fuss over
nothing please feel free to say so.
It will be difficult to get automatic notification of a change in the
length of a string, since there are so many ways that can happen, and it
happens in core code. (For example, the C function rb_str_append() gets
called from other C functions, so there is no way to hook in a pure ruby
observer.)
However, there are some cases where a library class has an attr_writer
(or other method) and all you want is notification of changes that go
through that method. And, of course if you are developing the library
yourself you can force all changes to go through a method.
In these cases, you might want to take a look at my observable library
(really, it should be called observable-attr or something):
http://raa.ruby-lang.org/project/observable/
http://redshift.sourceforge.net/observable/
Unlike the standard Observer library, which operates at the level of
entire objects, this library operates on individual methods. Also,
notification is automatic in the sense that the observed object doesn’t
have to call notify_observers. Further, each observer can register more
than one “when” clause that is called just when the new value matches
(in the sense of #===) some pattern, class, or other matching object.
I’ve found this useful primarily in fxruby (the foxtails library uses it
to make data targets more friendly and responsive).
Here’s an example:
require ‘observable’
A prexisting class with a method that doesn’t expect to be
observed.
class Base
attr_writer :foo
end
class Speaker < Base
extend Observable
# make the inherited method :foo be observable and define an
# additional observable attribute, :bar.
observable :foo, :bar
def run
self.foo = "1"
self.bar = [4,5,6]
self.foo = "2"
self.bar << 7 # Caution: no notification here!
self.bar += [8] # notification
self.foo = "3"
@foo = "4" # No notification here, since writer wasn't called
end
end
class Listener
def initialize(speaker)
speaker.when_foo /2/ do |v|
puts “#{self} saw foo change to have a 2 in #{v.inspect}”
end
speaker.when_foo /\d/ do |v|
puts "#{self} saw foo change to have a digit in #{v.inspect}"
end
# This would override the first when_foo clause
#speaker.when_foo /2/ do |v|
# puts "#{self} saw foo change to have a 2 in #{v.inspect}
[overridden]"
#end
# listen for _any_ changes (note that #=== is used to match
value,
# so Object matches everything, including the initial nil)
speaker.when_bar Object do |v, old_v|
puts “#{self} saw bar change from #{old_v.inspect} to
#{v.inspect}”
end
end
end
sp = Speaker.new
Listener.new(sp)
sp.run
END
Output:
#Listener:0xb7a56628 saw bar change from nil to nil
#Listener:0xb7a56628 saw foo change to have a digit in “1”
#Listener:0xb7a56628 saw bar change from nil to [4, 5, 6]
#Listener:0xb7a56628 saw foo change to have a 2 in “2”
#Listener:0xb7a56628 saw foo change to have a digit in “2”
#Listener:0xb7a56628 saw bar change from [4, 5, 6, 7] to [4, 5, 6, 7,
8]
#Listener:0xb7a56628 saw foo change to have a digit in “3”