mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-22 08:26:27 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/branches/reorg@29535 72102866-910b-0410-8b05-ffd578937521
1991 lines
53 KiB
Objective-C
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
|
|
|