From: Brian M. <ma...@ex...> - 2008-05-17 19:08:42
|
It seems as if RubyCocoa's key-value observing works differently in some ways than Objective-C's would. Is this intentional? An incomplete implementation? The real question: is it stable behavior an app can depend upon? Here's the difference in the most important case (where you've declared the value with kvc_accessor): - setting the variable directly (var= or setVar) behaves like Objc: observeValueForKeyPath... is called - setting it with setValue_forKey does not: observeValueForKeyPath... is called twice (once, I assume, for willChangeValueForKey and once for didChangeValueForKey) In Objc, it'd be called once. Details: ============= Here's Objective-C: A Watcher object watches an Observed object like this: - (id) initWatching: (id) observed { [observed addObserver: self forKeyPath: @"value" options: 0 context: NULL]; return self; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSLog(@"observed %@\n", keyPath); } Here a third object triggers observation: Observed *observed = [[Observed alloc] init]; Watcher *watcher = [[Watcher alloc] initWatching: observed]; NSLog(@"Setting directly."); observed.value = @"new value"; // The result is "observed value" NSLog(@"Setting kvcly."); [observed setValue: @"newer value" forKey: @"value"]; // The result is "observed value" ============ The Ruby code: require 'osx/cocoa' class Observed < OSX::NSObject if ARGV[0] == 'kvc' kvc_accessor :value else attr_accessor :value end end class Watcher < OSX::NSObject def initWatching(observed) init observed.addObserver_forKeyPath_options_context(self, 'value', 0, nil) self end def observeValueForKeyPath_ofObject_change_context( keyPath, object, change, context) puts "#{object} changed #{keyPath}" end end observed = Observed.alloc.init watcher = Watcher.alloc.initWatching(observed) puts "Setting directly" observed.value = 5 # Nothing if plain accessor; sees change if kvc_accessor. puts "Setting via setValue method" observed.setValue(5) # Nothing if plain accessor; sees change if kvc_accessor. puts "setting kvcly" observed.setValue_forKey(5, 'value') # Gets two messages (presumably willChange and hasChanged) observed.removeObserver_forKeyPath(watcher, 'value') ----- Brian Marick, independent consultant Mostly on agile methods with a testing slant www.exampler.com, www.exampler.com/blog, www.twitter.com/marick |