libs-base/Source/NSKeyValueObserving.m
Richard Frith-MacDonald 7e09c86e68 make library buid without referring to installed headers
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/branches/reorg@29535 72102866-910b-0410-8b05-ffd578937521
2010-02-10 17:15:09 +00:00

1991 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$
*/
#include "GNUstepBase/preface.h"
#import "Foundation/NSCharacterSet.h"
#import "Foundation/NSDebug.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/NSObject.h"
#import "Foundation/NSSet.h"
#import "Foundation/NSString.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 isa 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 isa 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 (kvoLock == nil)
{
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");
}
}
/*
* 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.
NSLock *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) forwarderWithKeyPath: (NSString *)keyPath
ofObject: (id)object
withTarget: (id)aTarget
context: (void *)context;
- (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];
isa = [self class];
[self dealloc];
GSNOSUPERDEALLOC;
}
- (Class) class
{
return GSObjCSuper(GSObjCClass(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 GSObjCSuper(GSObjCSuper(GSObjCClass(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 = GSNameFromSelector(_cmd);
unsigned len = strlen(name);
NSString *key;
unsigned i;
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];
RELEASE(tmp);
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)
{
GSMethodList m;
NSMethodSignature *sig;
SEL sel;
IMP imp;
const char *type;
NSString *suffix;
NSString *a[2];
unsigned i;
BOOL found = NO;
NSString *tmp;
unichar u;
m = GSAllocMethodList(2);
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];
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)
{
GSAppendMethodToList(m, sel, [sig methodType], imp, YES);
found = YES;
}
}
if (found == YES)
{
GSAddMethodList(replacement, m, YES);
GSFlushMethodCacheForClass(replacement);
[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 complient 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;
}
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];
}
@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) forwarderWithKeyPath: (NSString *)keyPath
ofObject: (id)object
withTarget: (id)aTarget
context: (void *)context
{
return [[self alloc] initWithKeyPath: keyPath
ofObject: object
withTarget: aTarget
context: context];
}
- (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
forwarderWithKeyPath: 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];
}
[self release];
}
- (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];
isa = [r replacement];
}
/*
* Now add the observer.
*/
dot = [aPath rangeOfString:@"."];
if (dot.location != NSNotFound)
{
forwarder = [NSKeyValueObservationForwarder
forwarderWithKeyPath: 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.
*/
isa = [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