libs-base/Source/NSKeyValueObserving.m
theraven ceba92a265 Lots of little fixes to make -base compile with -Werror (now builds without warnings).
Richard: I'm unsure about three of these, which were fixes in memset() calls in:
- NSConcreteMapTable.m
- NSConcreteHashTable.m
- Additions/NSData+GNUstepBase.m

Please can you check them?  I think they are intended to zero the entire object
(rather than the first word), but the lack of comments makes me unsure.

Most changes were just tweaks to variable types.  I've also removed some dead code from NSInvocation.  This was small group of things that were marked for internal use only, but not actually referenced in the code anywhere.

Other improvements:

- NSArray / NSDictionary fixed up to use the 10.7 (ARC-friendly) prototypes.
- getObjects:andKeys: implemented for NSDictionary (10.5 method)
- NSPointerArray and NSHashTable now properly support weak objects.
- Tests for weak objects in collections.



git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@33621 72102866-910b-0410-8b05-ffd578937521
2011-07-24 13:09:22 +00:00

1986 lines
53 KiB
Objective-C

/** Implementation of GNUSTEP key value observing
Copyright (C) 2005 Free Software Foundation, Inc.
Written by Richard Frith-Macdonald <richard@brainstorm.co.uk>
Date: 2005-2008
This file is part of the GNUstep Base Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02111 USA.
$Date$ $Revision$
*/
#import "common.h"
#import "Foundation/NSCharacterSet.h"
#import "Foundation/NSDictionary.h"
#import "Foundation/NSEnumerator.h"
#import "Foundation/NSException.h"
#import "Foundation/NSHashTable.h"
#import "Foundation/NSKeyValueCoding.h"
#import "Foundation/NSKeyValueObserving.h"
#import "Foundation/NSLock.h"
#import "Foundation/NSMapTable.h"
#import "Foundation/NSMethodSignature.h"
#import "Foundation/NSNull.h"
#import "Foundation/NSSet.h"
#import "Foundation/NSValue.h"
#import "GNUstepBase/GSObjCRuntime.h"
#import "GNUstepBase/Unicode.h"
#import "GNUstepBase/GSLock.h"
#import "GNUstepBase/NSObject+GNUstepBase.h"
#import "GSInvocation.h"
/*
* IMPLEMENTATION NOTES
*
* Originally, I wanted to do KVO via a proxy, with class pointer swizzling
* to turn the original instance into an instance of the proxy class.
* However, I couldn't figure a way to get decent performance out of
* this model, as every message to the instance would have to be
* forwarded through the proxy class methods to the original class
* methods.
*
* So, instead I arrived at the mechanism of creating a subclass of
* each class being observed, with a few subclass methods overriding
* those of the original, but most remaining the same.
* The same class pointer swizzling technique was used to convert between the
* original class and the superclass.
* This subclass basically overrides several standard methods with
* those from a template class, and then overrides any setter methods
* with a another generic setter.
*/
NSString *const NSKeyValueChangeIndexesKey = @"indexes";
NSString *const NSKeyValueChangeKindKey = @"kind";
NSString *const NSKeyValueChangeNewKey = @"new";
NSString *const NSKeyValueChangeOldKey = @"old";
NSString *const NSKeyValueChangeNotificationIsPriorKey = @"notificationIsPrior";
static NSRecursiveLock *kvoLock = nil;
static NSMapTable *classTable = 0;
static NSMapTable *infoTable = 0;
static NSMapTable *dependentKeyTable;
static Class baseClass;
static id null;
static inline void
setup()
{
if (nil == kvoLock)
{
[gnustep_global_lock lock];
if (nil == kvoLock)
{
kvoLock = [GSLazyRecursiveLock new];
null = [[NSNull null] retain];
classTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
NSNonOwnedPointerMapValueCallBacks, 128);
infoTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
NSNonOwnedPointerMapValueCallBacks, 1024);
dependentKeyTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
NSOwnedPointerMapValueCallBacks, 128);
baseClass = NSClassFromString(@"GSKVOBase");
}
[gnustep_global_lock unlock];
}
}
/*
* This is the template class whose methods are added to KVO classes to
* override the originals and make the swizzled class look like the
* original class.
*/
@interface GSKVOBase : NSObject
@end
/*
* This holds information about a subclass replacing a class which is
* being observed.
*/
@interface GSKVOReplacement : NSObject
{
Class original; /* The original class */
Class replacement; /* The replacement class */
NSMutableSet *keys; /* The observed setter keys */
}
- (id) initWithClass: (Class)aClass;
- (void) overrideSetterFor: (NSString*)aKey;
- (Class) replacement;
@end
/*
* This is a placeholder class which has the abstract setter method used
* to replace all setter methods in the original.
*/
@interface GSKVOSetter : NSObject
- (void) setter: (void*)val;
- (void) setterChar: (unsigned char)val;
- (void) setterDouble: (double)val;
- (void) setterFloat: (float)val;
- (void) setterInt: (unsigned int)val;
- (void) setterLong: (unsigned long)val;
#ifdef _C_LNG_LNG
- (void) setterLongLong: (unsigned long long)val;
#endif
- (void) setterShort: (unsigned short)val;
- (void) setterRange: (NSRange)val;
- (void) setterPoint: (NSPoint)val;
- (void) setterSize: (NSSize)val;
- (void) setterRect: (NSRect)rect;
@end
/* An instance of this records all the information for a single observation.
*/
@interface GSKVOObservation : NSObject
{
@public
NSObject *observer; // Not retained (zeroing weak pointer)
void *context;
int options;
}
@end
/* An instance of thsi records the observations for a key path and the
* recursion state of the process of sending notifications.
*/
@interface GSKVOPathInfo : NSObject
{
@public
unsigned recursion;
unsigned allOptions;
NSMutableArray *observations;
NSMutableDictionary *change;
}
- (void) notifyForKey: (NSString *)aKey ofInstance: (id)instance prior: (BOOL)f;
@end
/*
* Instances of this class are created to hold information about the
* observers monitoring a particular object which is being observed.
*/
@interface GSKVOInfo : NSObject
{
NSObject *instance; // Not retained.
GSLazyRecursiveLock *iLock;
NSMapTable *paths;
}
- (GSKVOPathInfo *) lockReturningPathInfoForKey: (NSString *)key;
- (void*) contextForObserver: (NSObject*)anObserver ofKeyPath: (NSString*)aPath;
- (id) initWithInstance: (NSObject*)i;
- (NSObject*) instance;
- (BOOL) isUnobserved;
- (void) unlock;
@end
@interface NSKeyValueObservationForwarder : NSObject
{
id target;
NSKeyValueObservationForwarder *child;
void *contextToForward;
id observedObjectForUpdate;
NSString *keyForUpdate;
id observedObjectForForwarding;
NSString *keyForForwarding;
NSString *keyPathToForward;
}
- (id) initWithKeyPath: (NSString *)keyPath
ofObject: (id)object
withTarget: (id)aTarget
context: (void *)context;
- (void) keyPathChanged: (id)objectToObserve;
@end
@implementation GSKVOBase
- (void) dealloc
{
// Turn off KVO for self ... then call the real dealloc implementation.
[self setObservationInfo: nil];
object_setClass(self, [self class]);
[self dealloc];
GSNOSUPERDEALLOC;
}
- (Class) class
{
return class_getSuperclass(object_getClass(self));
}
- (void) setValue: (id)anObject forKey: (NSString*)aKey
{
Class c = [self class];
void (*imp)(id,SEL,id,id);
imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
imp(self,_cmd,anObject,aKey);
[self didChangeValueForKey: aKey];
}
else
{
imp(self,_cmd,anObject,aKey);
}
}
- (void) takeStoredValue: (id)anObject forKey: (NSString*)aKey
{
Class c = [self class];
void (*imp)(id,SEL,id,id);
imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
imp(self,_cmd,anObject,aKey);
[self didChangeValueForKey: aKey];
}
else
{
imp(self,_cmd,anObject,aKey);
}
}
- (void) takeValue: (id)anObject forKey: (NSString*)aKey
{
Class c = [self class];
void (*imp)(id,SEL,id,id);
imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
imp(self,_cmd,anObject,aKey);
[self didChangeValueForKey: aKey];
}
else
{
imp(self,_cmd,anObject,aKey);
}
}
- (void) takeValue: (id)anObject forKeyPath: (NSString*)aKey
{
Class c = [self class];
void (*imp)(id,SEL,id,id);
imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
imp(self,_cmd,anObject,aKey);
[self didChangeValueForKey: aKey];
}
else
{
imp(self,_cmd,anObject,aKey);
}
}
- (Class) superclass
{
return class_getSuperclass(class_getSuperclass(object_getClass(self)));
}
@end
/*
* Get a key name from a selector (setKey: or _setKey:) by
* taking the key part and making the first letter lowercase.
*/
static NSString *newKey(SEL _cmd)
{
const char *name = sel_getName(_cmd);
unsigned len = strlen(name);
NSString *key;
unsigned i;
NSCAssert(len > 0, @"Invalid selector name!");
if (*name == '_')
{
name++;
len--;
}
name += 3; // Step past 'set'
len -= 4; // allow for 'set' and trailing ':'
for (i = 0; i < len; i++)
{
if (name[i] < 0)
{
break;
}
}
if (i == len)
{
char buf[len];
/* Efficient key creation for ascii keys
*/
for (i = 0; i < len; i++) buf[i] = name[i];
if (isupper(buf[0]))
{
buf[0] = tolower(buf[0]);
}
key = [[NSString alloc] initWithBytes: buf
length: len
encoding: NSASCIIStringEncoding];
}
else
{
unichar u;
NSMutableString *m;
NSString *tmp;
/*
* Key creation for unicode strings.
*/
m = [[NSMutableString alloc] initWithBytes: name
length: len
encoding: NSUTF8StringEncoding];
u = [m characterAtIndex: 0];
u = uni_tolower(u);
tmp = [[NSString alloc] initWithCharacters: &u length: 1];
[m replaceCharactersInRange: NSMakeRange(0, 1) withString: tmp];
[tmp release];
key = m;
}
return key;
}
static GSKVOReplacement *
replacementForClass(Class c)
{
GSKVOReplacement *r;
setup();
[kvoLock lock];
r = (GSKVOReplacement*)NSMapGet(classTable, (void*)c);
if (r == nil)
{
r = [[GSKVOReplacement alloc] initWithClass: c];
NSMapInsert(classTable, (void*)c, (void*)r);
}
[kvoLock unlock];
return r;
}
@implementation GSKVOReplacement
- (void) dealloc
{
DESTROY(keys);
[super dealloc];
}
- (id) initWithClass: (Class)aClass
{
NSValue *template;
NSString *superName;
NSString *name;
if ([aClass instanceMethodForSelector: @selector(takeValue:forKey:)]
!= [NSObject instanceMethodForSelector: @selector(takeValue:forKey:)])
{
NSLog(@"WARNING The class '%@' (or one of its superclasses) overrides"
@" the deprecated takeValue:forKey: method. Using KVO to observe"
@" this class may interfere with this method. Please change the"
@" class to override -setValue:forKey: instead.",
NSStringFromClass(aClass));
}
if ([aClass instanceMethodForSelector: @selector(takeValue:forKeyPath:)]
!= [NSObject instanceMethodForSelector: @selector(takeValue:forKeyPath:)])
{
NSLog(@"WARNING The class '%@' (or one of its superclasses) overrides"
@" the deprecated takeValue:forKeyPath: method. Using KVO to observe"
@" this class may interfere with this method. Please change the"
@" class to override -setValue:forKeyPath: instead.",
NSStringFromClass(aClass));
}
original = aClass;
/*
* Create subclass of the original, and override some methods
* with implementations from our abstract base class.
*/
superName = NSStringFromClass(original);
name = [@"GSKVO" stringByAppendingString: superName];
template = GSObjCMakeClass(name, superName, nil);
GSObjCAddClasses([NSArray arrayWithObject: template]);
replacement = NSClassFromString(name);
GSObjCAddClassBehavior(replacement, baseClass);
/* Create the set of setter methods overridden.
*/
keys = [NSMutableSet new];
return self;
}
- (void) overrideSetterFor: (NSString*)aKey
{
if ([keys member: aKey] == nil)
{
NSMethodSignature *sig;
SEL sel;
IMP imp;
const char *type;
NSString *suffix;
NSString *a[2];
unsigned i;
BOOL found = NO;
NSString *tmp;
unichar u;
suffix = [aKey substringFromIndex: 1];
u = uni_toupper([aKey characterAtIndex: 0]);
tmp = [[NSString alloc] initWithCharacters: &u length: 1];
a[0] = [NSString stringWithFormat: @"set%@%@:", tmp, suffix];
a[1] = [NSString stringWithFormat: @"_set%@%@:", tmp, suffix];
[tmp release];
for (i = 0; i < 2; i++)
{
/*
* Replace original setter with our own version which does KVO
* notifications.
*/
sel = NSSelectorFromString(a[i]);
if (sel == 0)
{
continue;
}
sig = [original instanceMethodSignatureForSelector: sel];
if (sig == 0)
{
continue;
}
/*
* A setter must take three arguments (self, _cmd, value).
* The return value (if any) is ignored.
*/
if ([sig numberOfArguments] != 3)
{
continue; // Not a valid setter method.
}
/*
* Since the compiler passes different argument types
* differently, we must use a different setter method
* for each argument type.
* FIXME ... support structures
* Unsupported types are quietly ignored ... is that right?
*/
type = [sig getArgumentTypeAtIndex: 2];
switch (*type)
{
case _C_CHR:
case _C_UCHR:
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setterChar:)];
break;
case _C_SHT:
case _C_USHT:
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setterShort:)];
break;
case _C_INT:
case _C_UINT:
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setterInt:)];
break;
case _C_LNG:
case _C_ULNG:
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setterLong:)];
break;
#ifdef _C_LNG_LNG
case _C_LNG_LNG:
case _C_ULNG_LNG:
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setterLongLong:)];
break;
#endif
case _C_FLT:
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setterFloat:)];
break;
case _C_DBL:
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setterDouble:)];
break;
case _C_ID:
case _C_CLASS:
case _C_PTR:
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setter:)];
break;
case _C_STRUCT_B:
if (strcmp(@encode(NSRange), type) == 0)
{
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setterRange:)];
}
else if (strcmp(@encode(NSPoint), type) == 0)
{
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setterPoint:)];
}
else if (strcmp(@encode(NSSize), type) == 0)
{
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setterSize:)];
}
else if (strcmp(@encode(NSRect), type) == 0)
{
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setterRect:)];
}
else
{
imp = 0;
}
break;
default:
imp = 0;
break;
}
if (imp != 0)
{
if (class_addMethod(replacement, sel, imp, [sig methodType]))
{
found = YES;
}
else
{
NSLog(@"Failed to add setter method for %s to %@",
sel_getName(sel), class_getName(original));
}
}
}
if (found == YES)
{
[keys addObject: aKey];
}
else
{
NSMapTable *depKeys = NSMapGet(dependentKeyTable, original);
if (depKeys)
{
NSMapEnumerator enumerator = NSEnumerateMapTable(depKeys);
NSString *mainKey;
NSHashTable *dependents;
while (NSNextMapEnumeratorPair(&enumerator, (void **)(&mainKey),
(void**)&dependents))
{
NSHashEnumerator dependentKeyEnum;
NSString *dependentKey;
if (!dependents) continue;
dependentKeyEnum = NSEnumerateHashTable(dependents);
while ((dependentKey
= NSNextHashEnumeratorItem(&dependentKeyEnum)))
{
if ([dependentKey isEqual: aKey])
{
[self overrideSetterFor: mainKey];
// Mark the key as used
[keys addObject: aKey];
found = YES;
}
}
NSEndHashTableEnumeration(&dependentKeyEnum);
}
NSEndMapTableEnumeration(&enumerator);
}
if (!found)
{
NSDebugLLog(@"KVC", @"class %@ not KVC compliant for %@",
original, aKey);
/*
[NSException raise: NSInvalidArgumentException
format: @"class not KVC complient for %@", aKey];
*/
}
}
}
}
- (Class) replacement
{
return replacement;
}
@end
/*
* This class
*/
@implementation GSKVOSetter
- (void) setter: (void*)val
{
NSString *key;
Class c = [self class];
void (*imp)(id,SEL,void*);
imp = (void (*)(id,SEL,void*))[c instanceMethodForSelector: _cmd];
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES)
{
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
}
else
{
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
- (void) setterChar: (unsigned char)val
{
NSString *key;
Class c = [self class];
void (*imp)(id,SEL,unsigned char);
imp = (void (*)(id,SEL,unsigned char))[c instanceMethodForSelector: _cmd];
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES)
{
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
}
else
{
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
- (void) setterDouble: (double)val
{
NSString *key;
Class c = [self class];
void (*imp)(id,SEL,double);
imp = (void (*)(id,SEL,double))[c instanceMethodForSelector: _cmd];
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES)
{
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
}
else
{
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
- (void) setterFloat: (float)val
{
NSString *key;
Class c = [self class];
void (*imp)(id,SEL,float);
imp = (void (*)(id,SEL,float))[c instanceMethodForSelector: _cmd];
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES)
{
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
}
else
{
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
- (void) setterInt: (unsigned int)val
{
NSString *key;
Class c = [self class];
void (*imp)(id,SEL,unsigned int);
imp = (void (*)(id,SEL,unsigned int))[c instanceMethodForSelector: _cmd];
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES)
{
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
}
else
{
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
- (void) setterLong: (unsigned long)val
{
NSString *key;
Class c = [self class];
void (*imp)(id,SEL,unsigned long);
imp = (void (*)(id,SEL,unsigned long))[c instanceMethodForSelector: _cmd];
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES)
{
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
}
else
{
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
#ifdef _C_LNG_LNG
- (void) setterLongLong: (unsigned long long)val
{
NSString *key;
Class c = [self class];
void (*imp)(id,SEL,unsigned long long);
imp = (void (*)(id,SEL,unsigned long long))
[c instanceMethodForSelector: _cmd];
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES)
{
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
}
else
{
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
#endif
- (void) setterShort: (unsigned short)val
{
NSString *key;
Class c = [self class];
void (*imp)(id,SEL,unsigned short);
imp = (void (*)(id,SEL,unsigned short))[c instanceMethodForSelector: _cmd];
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES)
{
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
}
else
{
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
- (void) setterRange: (NSRange)val
{
NSString *key;
Class c = [self class];
void (*imp)(id,SEL,NSRange);
imp = (void (*)(id,SEL,NSRange))[c instanceMethodForSelector: _cmd];
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES)
{
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
}
else
{
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
- (void) setterPoint: (NSPoint)val
{
NSString *key;
Class c = [self class];
void (*imp)(id,SEL,NSPoint);
imp = (void (*)(id,SEL,NSPoint))[c instanceMethodForSelector: _cmd];
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES)
{
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
}
else
{
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
- (void) setterSize: (NSSize)val
{
NSString *key;
Class c = [self class];
void (*imp)(id,SEL,NSSize);
imp = (void (*)(id,SEL,NSSize))[c instanceMethodForSelector: _cmd];
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES)
{
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
}
else
{
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
- (void) setterRect: (NSRect)val
{
NSString *key;
Class c = [self class];
void (*imp)(id,SEL,NSRect);
imp = (void (*)(id,SEL,NSRect))[c instanceMethodForSelector: _cmd];
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES)
{
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
}
else
{
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
@end
@implementation GSKVOObservation
#if GS_WITH_GC
+ (void) initialize
{
GSMakeWeakPointer(self, "observer");
}
- (void) finalize
{
GSAssignZeroingWeakPointer((void**)&observer, nil);
}
#endif
@end
@implementation GSKVOPathInfo
- (void) dealloc
{
[change release];
[observations release];
[super dealloc];
}
- (id) init
{
change = [NSMutableDictionary new];
observations = [NSMutableArray new];
return self;
}
- (void) notifyForKey: (NSString *)aKey ofInstance: (id)instance prior: (BOOL)f
{
unsigned count;
id oldValue;
id newValue;
if (f == YES)
{
if ((allOptions & NSKeyValueObservingOptionPrior) == 0)
{
return; // Nothing to do.
}
[change setObject: [NSNumber numberWithBool: YES]
forKey: NSKeyValueChangeNotificationIsPriorKey];
}
else
{
[change removeObjectForKey: NSKeyValueChangeNotificationIsPriorKey];
}
oldValue = [[change objectForKey: NSKeyValueChangeOldKey] retain];
if (oldValue == nil)
{
oldValue = null;
}
newValue = [[change objectForKey: NSKeyValueChangeNewKey] retain];
if (newValue == nil)
{
newValue = null;
}
/* Retain self so that we won't be deallocated during the
* notification process.
*/
[self retain];
count = [observations count];
while (count-- > 0)
{
GSKVOObservation *o = [observations objectAtIndex: count];
if (f == YES)
{
if ((o->options & NSKeyValueObservingOptionPrior) == 0)
{
continue;
}
}
else
{
if (o->options & NSKeyValueObservingOptionNew)
{
[change setObject: newValue
forKey: NSKeyValueChangeNewKey];
}
}
if (o->options & NSKeyValueObservingOptionOld)
{
[change setObject: oldValue
forKey: NSKeyValueChangeOldKey];
}
[o->observer observeValueForKeyPath: aKey
ofObject: instance
change: change
context: o->context];
}
[change setObject: oldValue forKey: NSKeyValueChangeOldKey];
[oldValue release];
[change setObject: newValue forKey: NSKeyValueChangeNewKey];
[newValue release];
[self release];
}
@end
@implementation GSKVOInfo
- (NSObject*) instance
{
return instance;
}
/* Locks receiver and returns path info on success, otherwise
* leaves receiver munlocked and returns nil.
*/
- (GSKVOPathInfo*) lockReturningPathInfoForKey: (NSString*)key
{
GSKVOPathInfo *pathInfo;
[iLock lock];
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)key);
if (pathInfo == nil)
{
[iLock unlock];
}
return pathInfo;
}
- (void) unlock
{
[iLock unlock];
}
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
GSKVOPathInfo *pathInfo;
GSKVOObservation *observation;
unsigned count;
if ([anObserver respondsToSelector:
@selector(observeValueForKeyPath:ofObject:change:context:)] == NO)
{
return;
}
[iLock lock];
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
if (pathInfo == nil)
{
pathInfo = [GSKVOPathInfo new];
// use immutable object for map key
aPath = [aPath copy];
NSMapInsert(paths, (void*)aPath, (void*)pathInfo);
[pathInfo release];
[aPath release];
}
observation = nil;
pathInfo->allOptions = 0;
count = [pathInfo->observations count];
while (count-- > 0)
{
GSKVOObservation *o;
o = [pathInfo->observations objectAtIndex: count];
if (o->observer == anObserver)
{
o->context = aContext;
o->options = options;
observation = o;
}
#if GS_WITH_GC
else if (o->observer == nil)
{
/* The observer for thsi observation must have been collected.
*/
[pathInfo->observations removeObjectAtIndex: count];
continue;
}
#endif
pathInfo->allOptions |= o->options;
}
if (observation == nil)
{
observation = [GSKVOObservation new];
GSAssignZeroingWeakPointer((void**)&observation->observer,
(void*)anObserver);
observation->context = aContext;
observation->options = options;
[pathInfo->observations addObject: observation];
[observation release];
pathInfo->allOptions |= options;
}
if (options & NSKeyValueObservingOptionInitial)
{
/* If the NSKeyValueObservingOptionInitial option is set,
* we must send an immediate notification containing the
* existing value in the NSKeyValueChangeNewKey
*/
[pathInfo->change setObject: [NSNumber numberWithInt: 1]
forKey: NSKeyValueChangeKindKey];
if (options & NSKeyValueObservingOptionNew)
{
id value;
value = [instance valueForKey: aPath];
if (value == nil)
{
value = null;
}
[pathInfo->change setObject: value
forKey: NSKeyValueChangeNewKey];
}
[anObserver observeValueForKeyPath: aPath
ofObject: instance
change: pathInfo->change
context: aContext];
}
[iLock unlock];
}
- (void) dealloc
{
if (paths != 0) NSFreeMapTable(paths);
RELEASE(iLock);
[super dealloc];
}
- (id) initWithInstance: (NSObject*)i
{
instance = i;
paths = NSCreateMapTable(NSObjectMapKeyCallBacks,
NSObjectMapValueCallBacks, 8);
iLock = [GSLazyRecursiveLock new];
return self;
}
- (BOOL) isUnobserved
{
BOOL result = NO;
[iLock lock];
if (NSCountMapTable(paths) == 0)
{
result = YES;
}
[iLock unlock];
return result;
}
/*
* removes the observer
*/
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
GSKVOPathInfo *pathInfo;
[iLock lock];
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
if (pathInfo != nil)
{
unsigned count = [pathInfo->observations count];
pathInfo->allOptions = 0;
while (count-- > 0)
{
GSKVOObservation *o;
o = [pathInfo->observations objectAtIndex: count];
if (o->observer == anObserver || o->observer == nil)
{
[pathInfo->observations removeObjectAtIndex: count];
if ([pathInfo->observations count] == 0)
{
NSMapRemove(paths, (void*)aPath);
}
}
else
{
pathInfo->allOptions |= o->options;
}
}
}
[iLock unlock];
}
- (void*) contextForObserver: (NSObject*)anObserver ofKeyPath: (NSString*)aPath
{
GSKVOPathInfo *pathInfo;
void *context = 0;
[iLock lock];
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
if (pathInfo != nil)
{
unsigned count = [pathInfo->observations count];
while (count-- > 0)
{
GSKVOObservation *o;
o = [pathInfo->observations objectAtIndex: count];
if (o->observer == anObserver)
{
context = o->context;
break;
}
#if GS_WITH_GC
else if (o->observer == nil)
{
/* The observer for thsi observation must have been collected.
*/
[pathInfo->observations removeObjectAtIndex: count];
}
#endif
}
}
[iLock unlock];
return context;
}
@end
@implementation NSKeyValueObservationForwarder
- (id) initWithKeyPath: (NSString *)keyPath
ofObject: (id)object
withTarget: (id)aTarget
context: (void *)context
{
NSString * remainingKeyPath;
NSRange dot;
target = aTarget;
keyPathToForward = [keyPath copy];
contextToForward = context;
dot = [keyPath rangeOfString: @"."];
if (dot.location == NSNotFound)
{
[NSException raise: NSInvalidArgumentException
format: @"NSKeyValueObservationForwarder was not given a key path"];
}
keyForUpdate = [[keyPath substringToIndex: dot.location] copy];
remainingKeyPath = [keyPath substringFromIndex: dot.location + 1];
observedObjectForUpdate = object;
[object addObserver: self
forKeyPath: keyForUpdate
options: NSKeyValueObservingOptionNew
| NSKeyValueObservingOptionOld
context: target];
dot = [remainingKeyPath rangeOfString: @"."];
if (dot.location != NSNotFound)
{
child = [[NSKeyValueObservationForwarder alloc]
initWithKeyPath: remainingKeyPath
ofObject: [object valueForKey: keyForUpdate]
withTarget: self
context: NULL];
observedObjectForForwarding = nil;
}
else
{
keyForForwarding = [remainingKeyPath copy];
observedObjectForForwarding = [object valueForKey: keyForUpdate];
[observedObjectForForwarding addObserver: self
forKeyPath: keyForForwarding
options: NSKeyValueObservingOptionNew
| NSKeyValueObservingOptionOld
context: target];
child = nil;
}
return self;
}
- (void) finalize
{
if (child)
{
[child finalize];
}
if (observedObjectForUpdate)
{
[observedObjectForUpdate removeObserver: self forKeyPath: keyForUpdate];
}
if (observedObjectForForwarding)
{
[observedObjectForForwarding removeObserver: self forKeyPath:
keyForForwarding];
}
DESTROY(self);
}
- (void) dealloc
{
[keyForUpdate release];
[keyForForwarding release];
[keyPathToForward release];
[super dealloc];
}
- (void) observeValueForKeyPath: (NSString *)keyPath
ofObject: (id)anObject
change: (NSDictionary *)change
context: (void *)context
{
if (anObject == observedObjectForUpdate)
{
[self keyPathChanged: nil];
}
else
{
[target observeValueForKeyPath: keyPathToForward
ofObject: observedObjectForUpdate
change: change
context: contextToForward];
}
}
- (void) keyPathChanged: (id)objectToObserve
{
if (objectToObserve != nil)
{
[observedObjectForUpdate removeObserver: self forKeyPath: keyForUpdate];
observedObjectForUpdate = objectToObserve;
[objectToObserve addObserver: self
forKeyPath: keyForUpdate
options: NSKeyValueObservingOptionNew
| NSKeyValueObservingOptionOld
context: target];
}
if (child != nil)
{
[child keyPathChanged:
[observedObjectForUpdate valueForKey: keyForUpdate]];
}
else
{
NSMutableDictionary *change;
change = [NSMutableDictionary dictionaryWithObject:
[NSNumber numberWithInt: 1]
forKey: NSKeyValueChangeKindKey];
if (observedObjectForForwarding != nil)
{
id oldValue;
oldValue
= [observedObjectForForwarding valueForKey: keyForForwarding];
[observedObjectForForwarding removeObserver: self forKeyPath:
keyForForwarding];
if (oldValue)
{
[change setObject: oldValue
forKey: NSKeyValueChangeOldKey];
}
}
observedObjectForForwarding = [observedObjectForUpdate
valueForKey:keyForUpdate];
if (observedObjectForForwarding != nil)
{
id newValue;
[observedObjectForForwarding addObserver: self
forKeyPath: keyForForwarding
options: NSKeyValueObservingOptionNew
| NSKeyValueObservingOptionOld
context: target];
//prepare change notification
newValue
= [observedObjectForForwarding valueForKey: keyForForwarding];
if (newValue)
{
[change setObject: newValue forKey: NSKeyValueChangeNewKey];
}
}
[target observeValueForKeyPath: keyPathToForward
ofObject: observedObjectForUpdate
change: change
context: contextToForward];
}
}
@end
@implementation NSObject (NSKeyValueObserving)
- (void) observeValueForKeyPath: (NSString*)aPath
ofObject: (id)anObject
change: (NSDictionary*)aChange
context: (void*)aContext
{
[NSException raise: NSInvalidArgumentException
format: @"-%@ cannot be sent to %@ ..."
@" create an instance overriding this",
NSStringFromSelector(_cmd), NSStringFromClass([self class])];
return;
}
@end
@implementation NSObject (NSKeyValueObserverRegistration)
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
GSKVOInfo *info;
GSKVOReplacement *r;
NSKeyValueObservationForwarder *forwarder;
NSRange dot;
setup();
[kvoLock lock];
// Use the original class
r = replacementForClass([self class]);
/*
* Get the existing observation information, creating it (and changing
* the receiver to start key-value-observing by switching its class)
* if necessary.
*/
info = (GSKVOInfo*)[self observationInfo];
if (info == nil)
{
info = [[GSKVOInfo alloc] initWithInstance: self];
[self setObservationInfo: info];
object_setClass(self, [r replacement]);
}
/*
* Now add the observer.
*/
dot = [aPath rangeOfString:@"."];
if (dot.location != NSNotFound)
{
forwarder = [[NSKeyValueObservationForwarder alloc]
initWithKeyPath: aPath
ofObject: self
withTarget: anObserver
context: aContext];
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: forwarder];
}
else
{
[r overrideSetterFor: aPath];
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: aContext];
}
[kvoLock unlock];
}
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
GSKVOInfo *info;
id forwarder;
setup();
[kvoLock lock];
/*
* Get the observation information and remove this observation.
*/
info = (GSKVOInfo*)[self observationInfo];
forwarder = [info contextForObserver: anObserver ofKeyPath: aPath];
[info removeObserver: anObserver forKeyPath: aPath];
if ([info isUnobserved] == YES)
{
/*
* The instance is no longer being observed ... so we can
* turn off key-value-observing for it.
*/
object_setClass(self, [self class]);
IF_NO_GC(AUTORELEASE(info);)
[self setObservationInfo: nil];
}
[kvoLock unlock];
if ([aPath rangeOfString:@"."].location != NSNotFound)
[forwarder finalize];
}
@end
/**
* NSArray objects are not observable, so the registration methods
* raise an exception.
*/
@implementation NSArray (NSKeyValueObserverRegistration)
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
[NSException raise: NSGenericException
format: @"[%@-%@]: This class is not observable",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
- (void) addObserver: (NSObject*)anObserver
toObjectsAtIndexes: (NSIndexSet*)indexes
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
[self notImplemented: _cmd];
}
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
[NSException raise: NSGenericException
format: @"[%@-%@]: This class is not observable",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
- (void) removeObserver: (NSObject*)anObserver
fromObjectsAtIndexes: (NSIndexSet*)indexes
forKeyPath: (NSString*)aPath
{
[self notImplemented: _cmd];
}
@end
/**
* NSSet objects are not observable, so the registration methods
* raise an exception.
*/
@implementation NSSet (NSKeyValueObserverRegistration)
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
[NSException raise: NSGenericException
format: @"[%@-%@]: This class is not observable",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
[NSException raise: NSGenericException
format: @"[%@-%@]: This class is not observable",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
@end
@implementation NSObject (NSKeyValueObserverNotification)
- (void) willChangeValueForDependentsOfKey: (NSString *)aKey
{
NSMapTable *keys = NSMapGet(dependentKeyTable, [self class]);
if (keys != nil)
{
NSHashTable *dependents = NSMapGet(keys, aKey);
if (dependents != 0)
{
NSString *dependentKey;
NSHashEnumerator dependentKeyEnum;
dependentKeyEnum = NSEnumerateHashTable(dependents);
while ((dependentKey = NSNextHashEnumeratorItem(&dependentKeyEnum)))
{
[self willChangeValueForKey: dependentKey];
}
NSEndHashTableEnumeration(&dependentKeyEnum);
}
}
}
- (void) didChangeValueForDependentsOfKey: (NSString *)aKey
{
NSMapTable *keys = NSMapGet(dependentKeyTable, [self class]);
if (keys != nil)
{
NSHashTable *dependents = NSMapGet(keys, aKey);
if (dependents != nil)
{
NSString *dependentKey;
NSHashEnumerator dependentKeyEnum;
dependentKeyEnum = NSEnumerateHashTable(dependents);
while ((dependentKey = NSNextHashEnumeratorItem(&dependentKeyEnum)))
{
[self didChangeValueForKey: dependentKey];
}
NSEndHashTableEnumeration(&dependentKeyEnum);
}
}
}
- (void) willChangeValueForKey: (NSString*)aKey
{
GSKVOPathInfo *pathInfo;
GSKVOInfo *info;
info = (GSKVOInfo *)[self observationInfo];
if (info == nil)
{
return;
}
pathInfo = [info lockReturningPathInfoForKey: aKey];
if (pathInfo != nil)
{
if (pathInfo->recursion++ == 0)
{
id old = [pathInfo->change objectForKey: NSKeyValueChangeNewKey];
if (old != nil)
{
/* We have set a value for this key already, so the value
* we set must now be the old value and we don't need to
* refetch it.
*/
[pathInfo->change setObject: old
forKey: NSKeyValueChangeOldKey];
[pathInfo->change removeObjectForKey: NSKeyValueChangeNewKey];
}
else if (pathInfo->allOptions & NSKeyValueObservingOptionOld)
{
/* We don't have an old value set, so we must fetch the
* existing value because at least one observation wants it.
*/
old = [self valueForKey: aKey];
if (old == nil)
{
old = null;
}
[pathInfo->change setObject: old
forKey: NSKeyValueChangeOldKey];
}
[pathInfo->change setValue:
[NSNumber numberWithInt: NSKeyValueChangeSetting]
forKey: NSKeyValueChangeKindKey];
[pathInfo notifyForKey: aKey ofInstance: [info instance] prior: YES];
}
[info unlock];
}
[self willChangeValueForDependentsOfKey: aKey];
}
- (void) didChangeValueForKey: (NSString*)aKey
{
GSKVOPathInfo *pathInfo;
GSKVOInfo *info;
info = (GSKVOInfo *)[self observationInfo];
if (info == nil)
{
return;
}
pathInfo = [info lockReturningPathInfoForKey: aKey];
if (pathInfo != nil)
{
if (pathInfo->recursion == 1)
{
id value = [self valueForKey: aKey];
if (value == nil)
{
value = null;
}
[pathInfo->change setValue: value
forKey: NSKeyValueChangeNewKey];
[pathInfo->change setValue:
[NSNumber numberWithInt: NSKeyValueChangeSetting]
forKey: NSKeyValueChangeKindKey];
[pathInfo notifyForKey: aKey ofInstance: [info instance] prior: NO];
}
if (pathInfo->recursion > 0)
{
pathInfo->recursion--;
}
[info unlock];
}
[self didChangeValueForDependentsOfKey: aKey];
}
- (void) didChange: (NSKeyValueChange)changeKind
valuesAtIndexes: (NSIndexSet*)indexes
forKey: (NSString*)aKey
{
GSKVOPathInfo *pathInfo;
GSKVOInfo *info;
info = [self observationInfo];
if (info == nil)
{
return;
}
pathInfo = [info lockReturningPathInfoForKey: aKey];
if (pathInfo != nil)
{
if (pathInfo->recursion == 1)
{
NSMutableArray *array;
array = [self valueForKey: aKey];
[pathInfo->change setValue: [NSNumber numberWithInt: changeKind]
forKey: NSKeyValueChangeKindKey];
[pathInfo->change setValue: indexes
forKey: NSKeyValueChangeIndexesKey];
if (changeKind == NSKeyValueChangeInsertion
|| changeKind == NSKeyValueChangeReplacement)
{
[pathInfo->change setValue: [array objectsAtIndexes: indexes]
forKey: NSKeyValueChangeNewKey];
}
[pathInfo notifyForKey: aKey ofInstance: [info instance] prior: NO];
}
if (pathInfo->recursion > 0)
{
pathInfo->recursion--;
}
[info unlock];
}
[self didChangeValueForDependentsOfKey: aKey];
}
- (void) willChange: (NSKeyValueChange)changeKind
valuesAtIndexes: (NSIndexSet*)indexes
forKey: (NSString*)aKey
{
GSKVOPathInfo *pathInfo;
GSKVOInfo *info;
info = [self observationInfo];
if (info == nil)
{
return;
}
pathInfo = [info lockReturningPathInfoForKey: aKey];
if (pathInfo != nil)
{
if (pathInfo->recursion++ == 0)
{
NSMutableArray *array;
array = [self valueForKey: aKey];
if (changeKind == NSKeyValueChangeRemoval
|| changeKind == NSKeyValueChangeReplacement)
{
[pathInfo->change setValue: [array objectsAtIndexes: indexes]
forKey: NSKeyValueChangeOldKey];
}
[pathInfo->change setValue: [NSNumber numberWithInt: changeKind]
forKey: NSKeyValueChangeKindKey];
[pathInfo notifyForKey: aKey ofInstance: [info instance] prior: YES];
}
[info unlock];
}
[self willChangeValueForDependentsOfKey: aKey];
}
- (void) willChangeValueForKey: (NSString*)aKey
withSetMutation: (NSKeyValueSetMutationKind)mutationKind
usingObjects: (NSSet*)objects
{
GSKVOPathInfo *pathInfo;
GSKVOInfo *info;
info = [self observationInfo];
if (info == nil)
{
return;
}
pathInfo = [info lockReturningPathInfoForKey: aKey];
if (pathInfo != nil)
{
if (pathInfo->recursion++ == 0)
{
NSMutableSet *set;
set = [self valueForKey: aKey];
[pathInfo->change setValue: [set mutableCopy] forKey: @"oldSet"];
[pathInfo notifyForKey: aKey ofInstance: [info instance] prior: YES];
}
[info unlock];
}
[self willChangeValueForDependentsOfKey: aKey];
}
- (void) didChangeValueForKey: (NSString*)aKey
withSetMutation: (NSKeyValueSetMutationKind)mutationKind
usingObjects: (NSSet*)objects
{
GSKVOPathInfo *pathInfo;
GSKVOInfo *info;
info = [self observationInfo];
if (info == nil)
{
return;
}
pathInfo = [info lockReturningPathInfoForKey: aKey];
if (pathInfo != nil)
{
if (pathInfo->recursion == 1)
{
NSMutableSet *oldSet;
NSMutableSet *set;
oldSet = [pathInfo->change valueForKey: @"oldSet"];
set = [self valueForKey: aKey];
[pathInfo->change removeObjectForKey: @"oldSet"];
if (mutationKind == NSKeyValueUnionSetMutation)
{
set = [set mutableCopy];
[set minusSet: oldSet];
[pathInfo->change setValue:
[NSNumber numberWithInt: NSKeyValueChangeInsertion]
forKey: NSKeyValueChangeKindKey];
[pathInfo->change setValue: set
forKey: NSKeyValueChangeNewKey];
}
else if (mutationKind == NSKeyValueMinusSetMutation
|| mutationKind == NSKeyValueIntersectSetMutation)
{
[oldSet minusSet: set];
[pathInfo->change setValue:
[NSNumber numberWithInt: NSKeyValueChangeRemoval]
forKey: NSKeyValueChangeKindKey];
[pathInfo->change setValue: oldSet
forKey: NSKeyValueChangeOldKey];
}
else if (mutationKind == NSKeyValueSetSetMutation)
{
NSMutableSet *old;
NSMutableSet *new;
old = [oldSet mutableCopy];
[old minusSet: set];
new = [set mutableCopy];
[new minusSet: oldSet];
[pathInfo->change setValue:
[NSNumber numberWithInt: NSKeyValueChangeReplacement]
forKey: NSKeyValueChangeKindKey];
[pathInfo->change setValue: old
forKey: NSKeyValueChangeOldKey];
[pathInfo->change setValue: new
forKey: NSKeyValueChangeNewKey];
}
[pathInfo notifyForKey: aKey ofInstance: [info instance] prior: NO];
}
if (pathInfo->recursion > 0)
{
pathInfo->recursion--;
}
[info unlock];
}
[self didChangeValueForDependentsOfKey: aKey];
}
@end
@implementation NSObject (NSKeyValueObservingCustomization)
+ (BOOL) automaticallyNotifiesObserversForKey: (NSString*)aKey
{
return YES;
}
+ (void) setKeys: (NSArray*)triggerKeys
triggerChangeNotificationsForDependentKey: (NSString*)dependentKey
{
NSMapTable *affectingKeys;
NSEnumerator *enumerator;
NSString *affectingKey;
setup();
affectingKeys = NSMapGet(dependentKeyTable, self);
if (!affectingKeys)
{
affectingKeys = NSCreateMapTable(NSObjectMapKeyCallBacks,
NSNonOwnedPointerMapValueCallBacks, 10);
NSMapInsert(dependentKeyTable, self, affectingKeys);
}
enumerator = [triggerKeys objectEnumerator];
while ((affectingKey = [enumerator nextObject]))
{
NSHashTable *dependentKeys = NSMapGet(affectingKeys, affectingKey);
if (!dependentKeys)
{
dependentKeys = NSCreateHashTable(NSObjectHashCallBacks, 10);
NSMapInsert(affectingKeys, affectingKey, dependentKeys);
}
NSHashInsert(dependentKeys, dependentKey);
}
}
- (void*) observationInfo
{
void *info;
setup();
[kvoLock lock];
info = NSMapGet(infoTable, (void*)self);
IF_NO_GC(AUTORELEASE(RETAIN((id)info));)
[kvoLock unlock];
return info;
}
- (void) setObservationInfo: (void*)observationInfo
{
setup();
[kvoLock lock];
if (observationInfo == 0)
{
NSMapRemove(infoTable, (void*)self);
}
else
{
NSMapInsert(infoTable, (void*)self, observationInfo);
}
[kvoLock unlock];
}
@end