Scripts/kvo-debugging

kvo-debugging.py

#!/usr/bin/env python
"""
Experimental code, attempting to work around Apple's KVO hacks.
"""

import objc
from Foundation import (
    NSObject,
    NSKeyValueObservingOptionOld,
    NSKeyValueObservingOptionNew,
)


_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_("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, "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_("1w00t")
print(foo.bar())
foo.setBar_("2sw00t")
print(foo.bar())
foo.setBar_("3shw00t")
print(foo.bar())

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

print(foo.__class__)