Scripts/kvo-debugging

...

kvo-debugging.py

#!/usr/bin/env python
"""
Experimental code, attempting to work around Apple's KVO hacks.
"""
from __future__ import print_function
from Foundation import *
import objc
import sys

_kvoclassed = {}

def dumpClass(cls):
    print("DUMP", cls)
    print(cls.__bases__)
    #for k, v in cls.__dict__.items():
    #    print k, v
    print("-"*40)


def toKVOClass(orig, new):
    if new in _kvoclassed:
        return new
    origdct = dict(orig.__dict__)
    origset = set(origdct)
    newset = set(new.__dict__)
    # Merge in non-methods from the dict
    for key in (origset - newset):
        value = origdct[key]
        if isinstance(value, objc.selector):
            continue
        setattr(new, key, value)
    # Remember the original class,
    # KVO wants to go back to NSObject!
    _kvoclassed[new] = orig
    return new

def fromKVOClass(new):
    return _kvoclassed[new]

class FooClass(NSObject):
    _kvc_bar = None
    outlet = objc.IBOutlet('outlet')

    def XXaddObserver_forKeyPath_options_context_(self, observer, keyPath, options, context):
        print('addObserver_forKeyPath_options_context_', observer, keyPath, options, context)
        orig = FooClass #type(self)
        super(orig, self).addObserver_forKeyPath_options_context_(observer, keyPath, options, context)
        new = self.class__()
        print(orig, type(self), new)
        if orig is not new:
            print("class changed!!")
            self.__class__ = toKVOClass(orig, new)

    def XXremoveObserver_forKeyPath_(self, observer, keyPath):
        print('removeObserver_forKeyPath_', observer, keyPath)
        orig = type(self)
        super(orig, self).removeObserver_forKeyPath_(observer, keyPath)
        new = self.class__()
        print(orig, type(self), new)
        if orig is not new:
            print("class changed!!")
            self.__class__ = fromKVOClass(orig)

    def setBar_(self, bar):
        print('setBar_ ->', bar)
        print(self, type(self), self.class__())
        #print('->', bar)
        self._kvc_bar = bar
    setBar_ = objc.accessor(setBar_)

    def bar(self):
        print('bar', self._kvc_bar)
        #print(self, type(self), self.class__())
        return self._kvc_bar
    bar = objc.accessor(bar)

class FooObserver(NSObject):
    def observeValueForKeyPath_ofObject_change_context_(self, keyPath, obj, change, context):
        print('[[[[[]]]]] observeValueForKeyPath_ofObject_change_context_', keyPath, obj, change, context)
        dumpClass(type(obj))

    def willChangeValueForKey_(self, key):
        print('[[[[[]]]]] willChangeValueForKey_', key)

foo = FooClass.alloc().init()
fooObserver = FooObserver.alloc().init()

print()
print()
print("***** unobserved set")
print(foo.bar())
foo.setBar_(u'0shw00t')
print(foo.bar())

print()
print()
print("***** observing, setting three times")
print("foo's ISA:", foo.pyobjc_ISA)
print("Adding an observer")
foo.addObserver_forKeyPath_options_context_(fooObserver, u'bar',  (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld), 0)
print("foo's ISA:", foo.pyobjc_ISA)
#foo.removeObserver_forKeyPath_(fooObserver, u'bar');sys.exit(1)
print(foo.bar())
foo.setBar_(u'1w00t')
print(foo.bar())
foo.setBar_(u'2sw00t')
print(foo.bar())
foo.setBar_(u'3shw00t')
print(foo.bar())

print()
print()
print("***** removing the observer and setting twice (unobserved)")
foo.removeObserver_forKeyPath_(fooObserver, u'bar')
print(foo.bar())
foo.setBar_(u'4sw00t')
print(foo.bar())
foo.setBar_(u'5w00t')
print(foo.bar())

print(foo.__class__)