Sunday, July 27, 2008

Key Value Observing


Here is an example to explore Key Value Observing. We have a popup whose Content is bound to the variable L in the AppDelegate and its selectedIndex is bound to the variable i. The Text Field is also bound to the variable i in the AppDelegate. There are three buttons connected to actions of the same names, which change the value of i in different ways: (1) call a non-KVO-compliant method doit(), (2) use an accessor set method for the i variable, or (3) call willChangeValueForKey_("i"), call the doit() method, and call self.didChangeValueForKey_("i"). The Observer class is registered to observe the Key Path "i."

2008-07-27 21:54:56.189 PyKVO[2873:10b] go1
2008-07-27 21:54:56.194 PyKVO[2873:10b] observe: 1
2008-07-27 21:54:56.919 PyKVO[2873:10b] go2
2008-07-27 21:54:56.925 PyKVO[2873:10b] observe: 2
2008-07-27 21:54:56.926 PyKVO[2873:10b] observe: 2
2008-07-27 21:54:57.799 PyKVO[2873:10b] go3
2008-07-27 21:54:57.799 PyKVO[2873:10b] observe: 3
2008-07-27 21:54:57.801 PyKVO[2873:10b] observe: 3
2008-07-27 21:54:59.863 PyKVO[2873:10b] observe: 4
2008-07-27 21:54:59.864 PyKVO[2873:10b] observe: 4

The action go1_ (doit()) results in notification to the Observer, as does use of go2_ (setI_()). But go2_ results in duplicate notifications, as does calling go3_, which I thought was the correct way for a non-compliant change. Changing the text field in the window sends duplicate notifications as well. So apparently, use of the setter or editing the textField sends both "willChange" and "didChange" notifications, whil changing the variable directly sends only one, probably "didChange").

Here is the code:

class PyKVOAppDelegate(NSObject):
i = objc.ivar('i')
L = objc.ivar('L')

def init(self):
self.L = list('ABCDE')
self.i = 0
return self

@objc.IBAction
def go1_(self,sender):
NSLog("go1")
self.doit()

def doit(self):
if self.i == len(self.L)-1:
self.i = 0
else: self.i += 1

def setI_(self,value):
self.i = value

@objc.IBAction
def go2_(self,sender):
NSLog("go2")
self.setI_(2)

@objc.IBAction
def go3_(self,sender):
NSLog("go3")
self.willChangeValueForKey_("i")
self.doit()
self.didChangeValueForKey_("i")

class Observer(NSObject):
appDel = objc.IBOutlet()

def awakeFromNib(self):
self.appDel.addObserver_forKeyPath_options_context_(
self,'i',NSKeyValueObservingOptionNew,None)

def observeValueForKeyPath_ofObject_change_context_(
self, k, O, change, context):
v = O.valueForKey_(k)
NSLog("observe: %s" % v)