siuying / NanoStoreInMotion

RubyMotion wrapper for NanoStore, a lightweight schema-less key-value document database based on sqlite.
Other
103 stars 24 forks source link

KVO on attributes in NanoStore::Model #27

Closed jordanmaguire closed 11 years ago

jordanmaguire commented 11 years ago

Hey,

I haven't been able to get KVO working using attributes defined on a NanoStore::Model.

If I replace the attribute with an attr_accessor KVO starts working so I don't think I'm doing anything wrong initializing the KVO. EG:

# attribute :disliked # KVO doesn't register
attr_accessor :disliked # KVO works

Any idea how it is possible to fix this? My code would be much cleaner if I were able to use KVO on my model attributes.

nano-store version is 0.6.0

Let me know if you need any further information.

Thanks

siuying commented 11 years ago

This is because the accessor of nanostore models are actually stored in the 'info' ductionary, not real properties. Perhaps you can try to use KVO on 'info' for dictionary change instead.

jordanmaguire commented 11 years ago

It seems like you can't use KVO on hashes/dictionaries.

ie:

modelInstance.info = {} # invokes kvo
modelInstance.info["ilovekvo"] = "something" # doesn't invoke KVO
modelInstance.info = "beans" # invokes kvo

So I tried manually calling the KVO in lib/nano_store/model.rb like so:

def attribute(name)
  attributes << name

  define_method(name) do |*args, &block|
    self.info[name]
  end

  define_method((name + "=").to_sym) do |*args, &block|
    self.willChangeValueForKey(name) # here doesn't go bang
    self.info[name] = args[0]
    self.didChangeValueForKey(name) # here goes bang
  end
end

but RubyMotion goes bang with:

Method `id' created by attr_reader/writer or define_method cannot be called from Objective-C. Please define manually the method instead.

I can't continue looking tonight so I might have another crack tomorrow.

Here's a quick and dirty setup I've been testing in:

class Radio < NanoStore::Model
  attribute :id
end

class RadioObserver

  def self.test
    ro = RadioObserver.new
    ro.startObserving
    ro.radio.id = "Jordan"
  end

  def radio
    @radio ||= Radio.new
  end

  def startObserving
    radio.addObserver(self, forKeyPath:"info", options: NSKeyValueObservingOptionNew, context: nil)
    radio.addObserver(self, forKeyPath:"id", options: NSKeyValueObservingOptionNew, context: nil)
  end

###
# NSKeyValueObserving
# http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOBasics.html
###

  # This method is invoked by the system when an observed instance is changed
  def observeValueForKeyPath(keyPath, ofObject:object, change:change, context:context)
    puts "keyPath: #{keyPath}"
  end

end
bogardon commented 11 years ago

@jordanmaguire have you solved the Method 'id' created by attr_reader/writer or define_method cannot be called from Objective-C. Please define manually the method instead. problem?

jordanmaguire commented 11 years ago

Yo @bogardon, I didn't. I ended up having to change the implementation anyway so I had no need to use the KVO on the attributes.

siuying commented 11 years ago

Sorry for late reply, I found a way to listen to the change of a key from NSMutableDictionary:

radio.addObserver(self, forKeyPath:"info.id", options: NSKeyValueObservingOptionNew, context: nil)

That should work without modifying model class.

For the Method 'id' created by attr_reader/writer or define_method cannot be called from Objective-C. error, i have no idea... we'll need consult rubymotion team. Meanwhile, I'll closing the issue.

siuying commented 11 years ago

Another workaround:

class Radio < NanoStore::Model
  attribute :id
  def id
    self.info[:id]
  end
end

... yeah that's sucks. :-/