motion/core/kvo.rb in bubble-wrap-1.8.0 vs motion/core/kvo.rb in bubble-wrap-1.9.0

- old
+ new

@@ -16,95 +16,158 @@ # end # # @see https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/10000177i module BubbleWrap module KVO - COLLECTION_OPERATIONS = [ NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, NSKeyValueChangeReplacement ] - DEFAULT_OPTIONS = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld + class Registry + COLLECTION_OPERATIONS = [ NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, NSKeyValueChangeReplacement ] + OPTION_MAP = { + new: NSKeyValueChangeNewKey, + old: NSKeyValueChangeOldKey + } - def observe(*arguments, &block) - unless [1,2].include?(arguments.length) - raise ArgumentError, "wrong number of arguments (#{arguments.length} for 1 or 2)" + attr_reader :callbacks, :keys + + def initialize(value_keys = [:old, :new]) + @keys = value_keys.inject([]) do |acc, key| + value_change_key = OPTION_MAP[key] + + if value_change_key.nil? + raise RuntimeError, "Unknown value change key #{key}. Possible keys: #{OPTION_MAP.keys}" + end + + acc << value_change_key + end + + @callbacks = Hash.new do |hash, key| + hash[key] = Hash.new do |subhash, subkey| + subhash[subkey] = Array.new + end + end end - key_path = arguments.pop - target = arguments.pop || self + def add(target, key_path, &block) + return if target.nil? || key_path.nil? || block.nil? - target.addObserver(self, forKeyPath:key_path, options:DEFAULT_OPTIONS, context:nil) unless registered?(target, key_path) - add_observer_block(target, key_path, &block) - end + block.weak! if BubbleWrap.use_weak_callbacks? - def unobserve(*arguments) - unless [1,2].include?(arguments.length) - raise ArgumentError, "wrong number of arguments (#{arguments.length} for 1 or 2)" + callbacks[target][key_path.to_s] << block end - key_path = arguments.pop - target = arguments.pop || self + def remove(target, key_path) + return if target.nil? || key_path.nil? - return unless registered?(target, key_path) + key_path = key_path.to_s - target.removeObserver(self, forKeyPath:key_path) - remove_observer_block(target, key_path) - end + callbacks[target].delete(key_path) - def unobserve_all - return if @targets.nil? + # If there no key_paths left for target, remove the target key + if callbacks[target].empty? + callbacks.delete(target) + end + end - @targets.each do |target, key_paths| - key_paths.each_key do |key_path| - target.removeObserver(self, forKeyPath:key_path) + def registered?(target, key_path) + callbacks[target].has_key? key_path.to_s + end + + def remove_all + callbacks.clear + end + + def each_key_path + callbacks.each do |target, key_paths| + key_paths.each_key do |key_path| + yield target, key_path + end end end - remove_all_observer_blocks - end - # Observer blocks + private - private - def registered?(target, key_path) - !@targets.nil? && !@targets[target].nil? && @targets[target].has_key?(key_path.to_s) + def observeValueForKeyPath(key_path, ofObject: target, change: change, context: context) + key_paths = callbacks[target] || {} + blocks = key_paths[key_path] || [] + + args = change.values_at(*keys) + args << key_path + + blocks.each do |block| + block.call(*args) + end + end end - def add_observer_block(target, key_path, &block) - return if target.nil? || key_path.nil? || block.nil? + DEFAULT_OPTIONS = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld + IMMIDEATE_OPTIONS = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial - block.weak! if BubbleWrap.use_weak_callbacks? + def observe(target = self, key_paths, &block) + key_paths = [key_paths].flatten - @targets ||= {} - @targets[target] ||= {} - @targets[target][key_path.to_s] ||= [] - @targets[target][key_path.to_s] << block + key_paths.each do |key_path| + if not observers_registry.registered?(target, key_path) + target.addObserver(observers_registry, forKeyPath:key_path, options:DEFAULT_OPTIONS, context:nil) + end + + # Add block even if observer is registed, so multiplie blocks can be invoked. + observers_registry.add(target, key_path, &block) + end end - def remove_observer_block(target, key_path) - return if @targets.nil? || target.nil? || key_path.nil? + def observe!(target = self, key_paths, &block) + key_paths = [key_paths].flatten - key_paths = @targets[target] - key_paths.delete(key_path.to_s) if !key_paths.nil? - if key_paths.nil? || key_paths.length == 0 - @targets.delete(target) + key_paths.each do |key_path| + registered = immediate_observers_registry.registered?(target, key_path) + + immediate_observers_registry.add(target, key_path, &block) + + # We need to first register the block, and then call addObserver, because + # observeValueForKeyPath will fire immedeately. + if not registered + target.addObserver(immediate_observers_registry, forKeyPath:key_path, options: IMMIDEATE_OPTIONS, context:nil) + end end end - def remove_all_observer_blocks - @targets.clear unless @targets.nil? + def unobserve(target = self, key_paths) + remove_from_registry_if_exists(target, key_paths, observers_registry) end - # NSKeyValueObserving Protocol + def unobserve!(target = self, key_paths) + remove_from_registry_if_exists(target, key_paths, immediate_observers_registry) + end - def observeValueForKeyPath(key_path, ofObject: target, change: change, context: context) - key_paths = @targets[target] || {} - blocks = key_paths[key_path] || [] - blocks.each do |block| - args = [change[NSKeyValueChangeOldKey], change[NSKeyValueChangeNewKey]] - args << change[NSKeyValueChangeIndexesKey] if collection?(change) - block.call(*args) + def unobserve_all + observers_registry.each_key_path do |target, key_path| + target.removeObserver(observers_registry, forKeyPath:key_path) end + + immediate_observers_registry.each_key_path do |target, key_path| + target.removeObserver(immediate_observers_registry, forKeyPath:key_path) + end + + observers_registry.remove_all + immediate_observers_registry.remove_all end - def collection?(change) - COLLECTION_OPERATIONS.include?(change[NSKeyValueChangeKindKey]) + private + def observers_registry + @observers_registry ||= Registry.new end + def immediate_observers_registry + @immediate_observers_registry ||= Registry.new([:new]) + end + + def remove_from_registry_if_exists(target, key_paths, registry) + key_paths = [key_paths].flatten + + key_paths.each do |key_path| + if registry.registered?(target, key_path) + target.removeObserver(registry, forKeyPath:key_path) + registry.remove(target, key_path) + end + end + end end end