Bob Ippolito avatar Bob Ippolito committed b28b13f

Fix Panther KVO, hopefully not breaking Tiger :)

Comments (0)

Files changed (6)

Lib/objc/_convenience.py

     set(['__cmp__'])
 
 """
-from _objc import setClassExtender, selector, lookUpClass, currentBundle, repythonify, splitSignature, ivar
+from _objc import setClassExtender, selector, lookUpClass, currentBundle, repythonify, splitSignature
 from itertools import imap
 
 __all__ = ['CONVENIENCE_METHODS', 'CLASS_METHODS']
 NSObject = lookUpClass('NSObject')
 
 HAS_KVO = NSObject.instancesRespondToSelector_('willChangeValueForKey:')
-# This is a crappy way to test, but we need to check.
-# Panther's implementation sends multiple observations for the following:
-#
-#  [self willChangeValueForKey:key];
-#  [self willChangeValueForKey:key];
-#  [self didChangeValueForKey:key];
-#  [self didChangeValueForKey:key];
-#
-HAS_PANTHER_KVO = not NSObject.instancesRespondToSelector_('willChangeValueForKey:withSetMutation:usingObjects:')
+
+def isNative(sel):
+    return not hasattr(sel, 'callable')
 
 def add_convenience_methods(super_class, name, type_dict):
     try:
             def bundleForClass(cls):
                 return cb
             type_dict['bundleForClass'] = selector(bundleForClass, isClassMethod=True)
-            if HAS_KVO and '__useKVO__' not in type_dict:
-                if not (
-                        'willChangeValueForKey_' in type_dict
-                        or 'didChangeValueForKey_' in type_dict):
-                    useKVO = issubclass(super_class, NSObject)
-                    type_dict['__useKVO__'] = useKVO
-                    if useKVO and HAS_PANTHER_KVO:
-                        type_dict['__pyobjc_kvo_stack__'] = ivar('__pyobjc_kvo_stack__')
+            if (HAS_KVO and 
+                    '__useKVO__' not in type_dict and
+                    isNative(type_dict.get('willChangeValueForKey_')) and
+                    isNative(type_dict.get('didChangeValueForKey_'))):
+                useKVO = issubclass(super_class, NSObject)
+                type_dict['__useKVO__'] = useKVO
         if '__bundle_hack__' in type_dict:
             import warnings
             warnings.warn(

Modules/objc/OC_NSBundleHack.m

 #import "OC_NSBundleHack.h"
+#import "objc_util.h"
 
 static id (*bundleForClassIMP)(id, SEL, Class);
 
 }
 @end
 
-static void
-nsmaptable_objc_retain(NSMapTable *table __attribute__((__unused__)), const void *datum) {
-	[(id)datum retain];
-}
-
-static void
-nsmaptable_objc_release(NSMapTable *table __attribute__((__unused__)), void *datum) {
-	[(id)datum release];
-}
-
-static
-NSMapTableKeyCallBacks PyObjC_ClassToNSBundleTable_KeyCallBacks = {
-	NULL, // use pointer value for hash
-	NULL, // use pointer value for equality
-	NULL, // no need to retain classes
-	NULL, // no need to release classes
-	NULL, // generic description
-	NULL  // not a key
-};
-
-static
-NSMapTableValueCallBacks PyObjC_ClassToNSBundle_ValueCallBacks = {
-	&nsmaptable_objc_retain,
-	&nsmaptable_objc_release,
-	NULL  // generic description
-};
-
 @implementation OC_NSBundleHack
 +(NSBundle*)bundleForClass:(Class)aClass
 {
 		mainBundle = [[NSBundle mainBundle] retain];
 	}
 	if (!bundleCache) {
-		bundleCache = NSCreateMapTable(PyObjC_ClassToNSBundleTable_KeyCallBacks, PyObjC_ClassToNSBundle_ValueCallBacks, PYOBJC_EXPECTED_CLASS_COUNT);
+		bundleCache = NSCreateMapTable(
+			PyObjCUtil_PointerKeyCallBacks,
+			PyObjCUtil_ObjCValueCallBacks,
+			PYOBJC_EXPECTED_CLASS_COUNT);
 	}
 	if (!aClass) {
 		return mainBundle;

Modules/objc/class-builder.m

 	} else if (_checkedKVO == 2) {
 		return YES;
 	}
+	static NSMapTable* kvo_stack = nil;
+	if (kvo_stack == nil) {
+		kvo_stack = NSCreateMapTable(
+			PyObjCUtil_ObjCIdentityKeyCallBacks,
+			PyObjCUtil_ObjCValueCallBacks,
+			0);
+	}
 	// Hacks for Panther so that you don't get nested observations
-	PyObjCRT_Ivar_t var;
-	var = class_getInstanceVariable([self class], "__pyobjc_kvo_stack__");
-	if (!var) {
-		return YES;
-	}
-	intptr_t setofs = (intptr_t)var->ivar_offset;
-	NSMutableDictionary **dictPtr = (NSMutableDictionary **)(((char *)self) + setofs);
-	NSMutableDictionary *kvoDict = *dictPtr;
+	NSMutableDictionary *kvoDict = (NSMutableDictionary *)NSMapGet(kvo_stack, (const void *)self);
 	if (!kvoDict) {
-		kvoDict = *dictPtr = [[NSMutableDictionary alloc] initWithCapacity:0];
+		kvoDict = [[NSMutableDictionary alloc] initWithCapacity:0];
+		NSMapInsert(kvo_stack, (const void *)self, (const void *)kvoDict);
+		[kvoDict release];
 	}   
 	if (isSet) {
 		int setCount = [(NSNumber *)[kvoDict objectForKey:key] intValue] + 1;
 		}
 	} else {
 		int setCount = [(NSNumber *)[kvoDict objectForKey:key] intValue] - 1;
-		n = [[NSNumber alloc] initWithInt:setCount];
-		[kvoDict setValue:n forKey:key];
-		[n release];
 		if (setCount != 0) {
+			n = [[NSNumber alloc] initWithInt:setCount];
+			[kvoDict setValue:n forKey:key];
+			[n release];
 			return NO;
+		} else {
+			[kvoDict removeObjectForKey:key];
+			if (![kvoDict count]) {
+				NSMapRemove(kvo_stack, (const void *)self);
+			}
 		}
 	}
 	return YES;
 		protocol_list->count = cur_protocol;
 	}
 	
-
-	key_list = PyDict_Keys(class_dict);
-	if (key_list == NULL) {
-		goto error_cleanup;
-	}
-
-	key_count = PyList_Size(key_list);
-	if (PyErr_Occurred()) {
-		Py_DECREF(key_list);
-		goto error_cleanup;
-	}
-
 	if (!PyObjCClass_HasPythonImplementation(py_superclass)) {
 		/* 
 		 * This class has a super_class that is pure objective-C
 		ivar_count        += 0;
 		meta_method_count += 0; 
 		method_count      += 10;
+		
+		if (_KVOHackLevel() == 1) {
+			method_count      += 2;
+		}
+	}
+
+
+	key_list = PyDict_Keys(class_dict);
+	if (key_list == NULL) {
+		goto error_cleanup;
+	}
+
+	key_count = PyList_Size(key_list);
+	if (PyErr_Occurred()) {
+		Py_DECREF(key_list);
+		goto error_cleanup;
 	}
 
 	/* Allocate the class as soon as possible, for new selector objects */
 }
 
 
-static void object_method_willOrDidChangeValueForKey_(
+static void
+object_method_willOrDidChangeValueForKey_(
 		ffi_cif* cif __attribute__((__unused__)),
 		void* retval __attribute__((__unused__)),
 		void** args,
 	id value = *(id*)args[2];
 	NSString* key = *(NSString**)args[3];
 
-	// First check super
+	// Set up a KVO stack so you only get one notification from this
 	NS_DURING
+		if (_KVOHackLevel() == 1) {
+			[self willChangeValueForKey:key];
+		}
+	NS_HANDLER
+	NS_ENDHANDLER
+
+	NS_DURING
+		// First check super
 		super.class = (Class)userdata;
 		RECEIVER(super) = self;
 		(void)objc_msgSendSuper(&super, _meth, value, key);
 			if (val == NULL) {
 				PyErr_Clear();
 				PyGILState_Release(state);
+				// Pop the KVO stack
+                if (_KVOHackLevel() == 1) {
+                    [self didChangeValueForKey:key];
+                }
 				[localException raise];
 			}
 			PyObject* res = NULL;
 			if (r == -1) {
 				PyErr_Clear();
 				PyGILState_Release(state);
+				// Pop the KVO stack
+                if (_KVOHackLevel() == 1) {
+                    [self didChangeValueForKey:key];
+                }
 				[localException raise];
 			}
 			PyGILState_Release(state);
 		} else {
+			// Pop the KVO stack
+            if (_KVOHackLevel() == 1) {
+                [self didChangeValueForKey:key];
+            }
 			[localException raise];
 		}
 	NS_ENDHANDLER
+	// Pop the KVO stack
+    if (_KVOHackLevel() == 1) {
+        [self didChangeValueForKey:key];
+    }
 }

Modules/objc/objc-object.m

 	return _checkedKVO;
 }
 
-static BOOL
-_UseKVO(NSString *key)
+static void
+_UseKVO(NSObject *self, NSString *key, int willChange)
 {           
-	int _checkedKVO = _KVOHackLevel();
-	if (_checkedKVO == -1 || [key characterAtIndex:0] == (unichar)'_') {
-		return NO;
-	}
-    return YES;
+    PyObjC_DURING
+        int _checkedKVO = _KVOHackLevel();
+        if (_checkedKVO == -1 || [key characterAtIndex:0] == (unichar)'_') {
+        } else if (willChange) {
+            [self willChangeValueForKey:key];
+        } else {
+            [self didChangeValueForKey:key];
+        }
+    PyObjC_HANDLER
+    PyObjC_ENDHANDLER
 }           
 			
-#define WILL_CHANGE(tp, self, key) \
-	do { \
-		if (_UseKVO(key)) { \
-			[(NSObject*)(self) willChangeValueForKey:(key)]; \
-		} \
-	} while (0)
-
-#define DID_CHANGE(tp, self, key) \
-	do { \
-		if (_UseKVO(key)) { \
-			[(NSObject*)(self) didChangeValueForKey:(key)]; \
-		} \
-	} while (0) 
-
 static PyObject*
 object_new(
 	PyTypeObject*  type __attribute__((__unused__)),
 	PyObject *descr;
 	descrsetfunc f;
 	PyObject** dictptr;
-	int res = -1;
+	int res;
 	id obj_inst;
-	NSString *obj_name = nil;
+	NSString *obj_name;
 	
 	if (!PyString_Check(name)) {
 #ifdef Py_USING_UNICODE
 		     "Cannot set '%s.400s' on NIL '%.50s' object",
 		     PyString_AS_STRING(name),
 		     tp->tp_name);
-		goto done;
+		Py_DECREF(name);
+		return -1;
 	}
 
+	obj_name = nil;
 	if (((PyObjCClassObject*)tp)->useKVO) {
 		if ((PyObjCObject_GetFlags(obj) & PyObjCObject_kUNINITIALIZED) == 0) {
 			obj_name = [NSString stringWithCString:PyString_AS_STRING(name)];
-			WILL_CHANGE(tp, obj_inst, obj_name);
+            _UseKVO((NSObject *)obj_inst, obj_name, 1);
 		}
 	}
 	descr = _type_lookup(tp, name);
 		
 		if (dict == NULL && value != NULL) {
 			dict = PyDict_New();
-			if (dict == NULL)
+			if (dict == NULL) {
+				res = -1;
 				goto done;
+			}
 			
 			*dictptr = dict;
 		}
 		PyErr_Format(PyExc_AttributeError,
 			     "'%.50s' object has no attribute '%.400s'",
 			     tp->tp_name, PyString_AS_STRING(name));
+		res = -1;
 		goto done;
 	}
 
 	PyErr_Format(PyExc_AttributeError,
 		     "'%.50s' object attribute '%.400s' is read-only",
 		     tp->tp_name, PyString_AS_STRING(name));
+	res = -1;
   done:
 	if (obj_inst && obj_name) {
-		DID_CHANGE(tp, obj_inst, obj_name);
+        _UseKVO((NSObject *)obj_inst, obj_name, 0);
 	}
 	Py_DECREF(name);
 	return res;

Modules/objc/objc_util.h

 extern NSMapTableKeyCallBacks PyObjCUtil_PointerKeyCallBacks;
 extern NSMapTableValueCallBacks PyObjCUtil_PointerValueCallBacks;
 
+extern NSMapTableKeyCallBacks PyObjCUtil_ObjCIdentityKeyCallBacks;
+extern NSMapTableValueCallBacks PyObjCUtil_ObjCValueCallBacks;
+
 void    PyObjC_FreeCArray(int, void*);
 int     PyObjC_PythonToCArray(const char*, PyObject*, PyObject*, void**, int*);
 PyObject* PyObjC_CArrayToPython(const char*, void*, int);

Modules/objc/objc_util.m

 PyObject* PyObjCExc_InternalError;
 PyObject* PyObjCExc_UnInitDeallocWarning;
 
+
 int 
 PyObjCUtil_Init(PyObject* module)
 {
 	NULL,
 };
 
+static void
+nsmaptable_objc_retain(NSMapTable *table __attribute__((__unused__)), const void *datum) {
+	[(id)datum retain];
+}
+
+static void
+nsmaptable_objc_release(NSMapTable *table __attribute__((__unused__)), void *datum) {
+	[(id)datum release];
+}
+
+NSMapTableKeyCallBacks PyObjCUtil_ObjCIdentityKeyCallBacks = {
+	NULL,
+	NULL,
+	&nsmaptable_objc_retain,
+	&nsmaptable_objc_release,
+	NULL,
+	NULL,
+};
+
+NSMapTableValueCallBacks PyObjCUtil_ObjCValueCallBacks = {
+	&nsmaptable_objc_retain,
+	&nsmaptable_objc_release,
+	NULL  // generic description
+};
+
+
 #define SHOULD_FREE 0
 #define SHOULD_IGNORE 1
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.