Bob Ippolito avatar Bob Ippolito committed edd0c0c

Fix for double-observing?

Comments (0)

Files changed (3)

Modules/objc/objc-class.h

  * @field hasPythonImpl True if the class is implemented in Python
  * @field generation   The value of PyObjC_MappingCount at the last time
  *                     the method-list was updated.
+ * @field useKVO    should the class implement automatic KVO notifications?
+ * @field keysetoffset Offset of the NSMutableSet used to keep track of
+ *                     notifications on Panther
  *
  * @discussion
  *      This struct is the type-object for on Objective-C class. It stores
 	int hasPythonImpl;
 	int generation;
 	int useKVO;
+	int keysetoffset;
 } PyObjCClassObject;
 
 extern PyObject* PyObjCClass_DefaultModule;
 int ObjC_RegisterClassProxy(Class cls, PyObject* classProxy);
 void PyObjCClass_CheckMethodList(PyObject* cls, int recursive);
 int PyObjCClass_DictOffset(PyObject* cls);
+int PyObjCClass_KeySetOffset(PyObject* cls);
 PyObject* PyObjCClass_GetDelMethod(PyObject* cls);
 void PyObjCClass_SetDelMethod(PyObject* cls, PyObject* newval);
 int  PyObjCClass_HasPythonImplementation(PyObject* cls);

Modules/objc/objc-class.m

 	info->method_magic = objc_methodlist_magic(objc_class);
 	info->dictoffset = 0;
 	info->useKVO = 0;
+    info->keysetoffset = 0;
 	info->delmethod = delmethod;
 	info->hasPythonImpl = 1;
 
 		info->useKVO = PyObject_IsTrue(useKVOObj);
 	}
 
+	var = class_getInstanceVariable(objc_class, "__pyobjc_kvo_stack__");
+	if (var != NULL) {
+		info->keysetoffset = var->ivar_offset;
+	}
+
 	Py_INCREF(res);
 	return res;
 }
 }
 
 int
+PyObjCClass_KeySetOffset(PyObject* cls)
+{
+	return ((PyObjCClassObject*)cls)->keysetoffset;
+}
+
+
+int
 PyObjCClass_DictOffset(PyObject* cls)
 {
 	return ((PyObjCClassObject*)cls)->dictoffset;

Modules/objc/objc-object.m

  *      
  */     
 static BOOL
-_UseKVO(void)
+_UseKVO(PyObject *tp, NSObject *self, NSString *key, int isSet)
 {           
 	static int _checkedKVO = 0;
 	if (_checkedKVO == 0) {
-		if ([NSObject respondsToSelector:@selector(willChangeValueForKey:)] &&
-			[NSObject respondsToSelector:@selector(didChangeValueForKey:)]) {
+		if ([NSObject instancesRespondToSelector:@selector(willChangeValueForKey:)] &&
+			[NSObject instancesRespondToSelector:@selector(didChangeValueForKey:)]) {
 			_checkedKVO = 1; 
+			if ([NSObject instancesRespondToSelector:@selector(willChangeValueForKey:withSetMutation:usingObjects:)]) {
+				_checkedKVO = 2;
+			}
 		} else {
 			_checkedKVO = -1;
 		}
 	}           
-	return (_checkedKVO == 1);
+	if (_checkedKVO == -1 || [key characterAtIndex:0] == (unichar)'_') {
+		return NO;
+	} else if (_checkedKVO == 2) {
+		return YES;
+	}
+	intptr_t setofs = (intptr_t)PyObjCClass_KeySetOffset(tp);
+	if (setofs == 0) {
+		return YES;
+	}
+	// Hacks for Panther so that you don't get nested observations
+	NSMutableSet **setPtr = (NSMutableSet **)(((char *)self) + setofs);
+	NSMutableSet *kvoSet = *setPtr;
+	if (!kvoSet) {
+		kvoSet = *setPtr = [[NSMutableSet alloc] initWithCapacity:0];
+	}
+	if (isSet) {
+		if ([kvoSet containsObject:key]) {
+			return NO;
+		}
+		[kvoSet addObject:key];
+	} else {
+		if (![kvoSet containsObject:key]) {
+			return NO;
+		}
+		[kvoSet removeObject:key];
+	}
+	return YES;
 }           
 			
-#define WILL_CHANGE(self, key) \
+#define WILL_CHANGE(tp, self, key) \
 	do { \
-		if (_UseKVO()) [(NSObject*)(self) willChangeValueForKey:(key)]; \
+		if (_UseKVO((PyObject *)tp, (NSObject *)self, (NSString *)key, 1)) { \
+			[(NSObject*)(self) willChangeValueForKey:(key)]; \
+		} \
 	} while (0)
 
-#define DID_CHANGE(self, key) \
+#define DID_CHANGE(tp, self, key) \
 	do { \
-		if (_UseKVO()) [(NSObject*)(self) didChangeValueForKey:(key)]; \
+		if (_UseKVO((PyObject *)tp, (NSObject *)self, (NSString *)key, 0)) { \
+			[(NSObject*)(self) didChangeValueForKey:(key)]; \
+		} \
 	} while (0) 
 
 static PyObject*
 	if (((PyObjCClassObject*)tp)->useKVO) {
 		if ((PyObjCObject_GetFlags(obj) & PyObjCObject_kUNINITIALIZED) == 0) {
 			obj_name = [NSString stringWithCString:PyString_AS_STRING(name)];
-			WILL_CHANGE(obj_inst, obj_name);
+			WILL_CHANGE(tp, obj_inst, obj_name);
 		}
 	}
 	descr = _type_lookup(tp, name);
 		     tp->tp_name, PyString_AS_STRING(name));
   done:
 	if (obj_inst && obj_name) {
-		DID_CHANGE(obj_inst, obj_name);
+		DID_CHANGE(tp, obj_inst, obj_name);
 	}
 	Py_DECREF(name);
 	return res;
      { 0, 0, 0, 0 },			/* as_buffer */
      0,					/* name */
      0,					/* slots */
-   }, 0, 0, 0, 0, 0, 0, 0, 0
+   }, 0, 0, 0, 0, 0, 0, 0, 0, 0
 };
 
 /*
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.