Added partial implementation os KVO to match the accidentally comitted makefile changes.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@21395 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
CaS 2005-07-01 21:26:46 +00:00
parent efb94a8834
commit 6ae0ba5ca5
3 changed files with 1206 additions and 0 deletions

View file

@ -30,6 +30,12 @@
Tools/pldes.1, Tools/plist-0_9.dtd, Tools/sfparse.1,
Tools/xmlparse.1: Add/fix copyright/licenses.
2005-07-01 Richard Frith-Macdonald <rfm@gnu.org>
* Bad day ... accidentally updated makefiles to build KVO stuff
in last change ... so I might as well commit the partial
implementation of NSKeyValueObserving.[hm]
2005-07-01 Richard Frith-Macdonald <rfm@gnu.org>
* The last change passed regression tests etc, but one program

View file

@ -0,0 +1,212 @@
/* Interface for NSKeyValueObserving for GNUStep
Copyright (C) 2005 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <rfm@gnu.org>
Date: 2005
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 Library 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 Library 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.
*/
#ifndef __NSKeyValueObserving_h_GNUSTEP_BASE_INCLUDE
#define __NSKeyValueObserving_h_GNUSTEP_BASE_INCLUDE
#include <Foundation/NSObject.h>
#if OS_API_VERSION(100300,GS_API_LATEST) && GS_API_VERSION(010200,GS_API_LATEST)
@class NSArray;
@class NSIndexSet;
@class NSSet;
@class NSString;
typedef enum {
NSKeyValueObservingOptionNew = 1,
NSKeyValueObservingOptionOld = 2
} NSKeyValueObservingOptions;
typedef enum {
NSKeyValueChangeSetting = 1,
NSKeyValueChangeInsertion = 2,
NSKeyValueChangeRemoval = 3,
NSKeyValueChangeReplacement = 4
} NSKeyValueChange;
typedef enum {
NSKeyValueUnionSetMutation = 1,
NSKeyValueMinusSetMutation = 2,
NSKeyValueIntersectSetMutation = 3,
NSKeyValueSetSetMutation = 4
} NSKeyValueSetMutationKind;
GS_EXPORT NSString *const NSKeyValueChangeIndexesKey;
GS_EXPORT NSString *const NSKeyValueChangeKindKey;
GS_EXPORT NSString *const NSKeyValueChangeNewKey;
GS_EXPORT NSString *const NSKeyValueChangeOldKey;
/* Given that the receiver has been registered as an observer
* of the value at a key path relative to an object,
* be notified that the value has changed.
* The change dictionary always contains an NSKeyValueChangeKindKey entry
* whose value is an NSNumber wrapping an NSKeyValueChange
* (use [NSNumber-intValue]). The meaning of NSKeyValueChange
* depends on what sort of property is identified by the key path:
*
* For any sort of property (attribute, to-one relationship,
* or ordered or unordered to-many relationship) NSKeyValueChangeSetting
* indicates that the observed object has received a -setValue:forKey:
* message, or that the key-value coding-compliant set method for the
* key has been invoked, or that a -willChangeValueForKey: or
* -didChangeValueForKey: pair has otherwise been invoked.
*
* For an _ordered_ to-many relationship, NSKeyValueChangeInsertion,
* NSKeyValueChangeRemoval, and NSKeyValueChangeReplacement indicate
* that a mutating message has been sent to the array returned by
* a -mutableArrayValueForKey: message sent to the object, or that
* one of the key-value coding-compliant array mutation methods for
* the key has been invoked, or that a -willChange:valuesAtIndexes:forKey:
* or -didChange:valuesAtIndexes:forKey: pair has otherwise been invoked.
*
* For an _unordered_ to-many relationship (introduced in Mac OS 10.4),
* NSKeyValueChangeInsertion, NSKeyValueChangeRemoval,
* and NSKeyValueChangeReplacement indicate that a mutating
* message has been sent to the set returned by a -mutableSetValueForKey:
* message sent to the object, or that one of the key-value
* coding-compliant set mutation methods for the key has been invoked,
* or that a -willChangeValueForKey:withSetMutation:usingObjects:
* or -didChangeValueForKey:withSetMutation:usingObjects: pair has
* otherwise been invoked.
*
* For any sort of property, the change dictionary always contains
* an NSKeyValueChangeNewKey entry if NSKeyValueObservingOptionNew
* was specified at observer-registration time, likewise for
* NSKeyValueChangeOldKey if NSKeyValueObservingOptionOld was specified.
* See the comments for the NSKeyValueObserverNotification informal
* protocol methods for what the values of those entries will be.
* For an _ordered_ to-many relationship, the change dictionary
* always contains an NSKeyValueChangeIndexesKey entry whose value
* is an NSIndexSet containing the indexes of the inserted, removed,
* or replaced objects, unless the change is an NSKeyValueChangeSetting.
* context is always the same pointer that was passed in at
* observer-registration time.
*/
@interface NSObject (NSKeyValueObserving)
- (void) observeValueForKeyPath: (NSString*)aPath
ofObject: (id)anObject
change: (NSDictionary*)aChange
context: (void*)aContext;
@end
@interface NSObject (NSKeyValueObserverRegistration)
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext;
- (void) removeObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath;
@end
@interface NSArray (NSKeyValueObserverRegistration)
- (void) addObserver: (NSObject*)anObserver
toObjectsAtIndexes: (NSIndexSet*)indexes
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext;
- (void) removeObserver: (NSObject*)anObserver
fromObjectsAtIndexes: (NSIndexSet*)indexes
forKeyPath: (NSString*)aPath;
@end
/**
* These methods are sent to the receiver when observing it active
* for a key and the key is about to be (or has just been) changed.
*/
@interface NSObject (NSKeyValueObserverNotification)
- (void) didChangeValueForKey: (NSString*)aKey;
- (void) didChange: (NSKeyValueChange)changeKind
valuesAtIndexes: (NSIndexSet*)indexes
forKey: (NSString*)aKey;
- (void) willChangeValueForKey: (NSString*)aKey;
- (void) willChange: (NSKeyValueChange)changeKind
valuesAtIndexes: (NSIndexSet*)indexes
forKey: (NSString*)aKey;
#if OS_API_VERSION(100400,GS_API_LATEST)
- (void) didChangeValueForKey: (NSString*)aKey
withSetMutation: (NSKeyValueSetMutationKind)mutationKind
usingObjects: (NSSet*)objects;
- (void) willChangeValueForKey: (NSString*)aKey
withSetMutation: (NSKeyValueSetMutationKind)mutationKind
usingObjects: (NSSet*)objects;
#endif
@end
/**
* These methods permit modifications to the observing system.
*/
@interface NSObject(NSKeyValueObservingCustomization)
/**
* Specifies whether the class should send the notification methods of
* the NSKeyValueObserverNotification protocol when instances of the
* class receive messages to change the value for the key.<br />
* The default implementation returns YES.
*/
+ (BOOL) automaticallyNotifiesObserversForKey: (NSString*)aKey;
/**
* Tells the observing system that when NSKeyValueObserverNotification
* protocol messages are sent for any key in the keys array, they should
* also be sent for dependentKey.
*/
+ (void) setKeys: (NSArray*)keys
triggerChangeNotificationsForDependentKey: (NSString*)dependentKey;
/**
* Returns a reference to the observation information for the receiver
* as stored using the -setObservationInfo: method.<br />
* The default implementation returns information from a global table.
*/
- (void*) observationInfo;
/**
* Stores observation information for the receiver. By default this is
* done in a global table, but classes may implement storage in an instance
* variable or some other scheme (for improved performance).
*/
- (void) setObservationInfo: (void*)observationInfo;
@end
#endif
#endif /* __NSKeyValueObserving_h_GNUSTEP_BASE_INCLUDE */

View file

@ -0,0 +1,988 @@
/** Implementation of GNUSTEP key value observing
Copyright (C) 2005 Free Software Foundation, Inc.
Written by Richard Frith-Macdonald <richard@brainstorm.co.uk>
Date: 2005
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 Library 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 Library 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"
#include "Foundation/NSObject.h"
#include "Foundation/NSCharacterSet.h"
#include "Foundation/NSString.h"
#include "Foundation/NSException.h"
#include "Foundation/NSLock.h"
#include "Foundation/NSMapTable.h"
#include "Foundation/NSMethodSignature.h"
#include "Foundation/NSKeyValueCoding.h"
#include "Foundation/NSKeyValueObserving.h"
#include "Foundation/NSSet.h"
#include "GNUstepBase/GSObjCRuntime.h"
#include "GNUstepBase/Unicode.h"
#include "GNUstepBase/GSLock.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
= @"NSKeyValueChangeIndexesKey";
NSString *const NSKeyValueChangeKindKey
= @"NSKeyValueChangeKindKey";
NSString *const NSKeyValueChangeNewKey
= @"NSKeyValueChangeNewKey";
NSString *const NSKeyValueChangeOldKey
= @"NSKeyValueChangeOldKey";
static const char *dummy = "";
static NSRecursiveLock *kvoLock = nil;
static NSMapTable *classTable = 0;
static NSMapTable *infoTable = 0;
static Class baseClass;
/*
* 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 is a placeholder class which has the abstract setter method used
* to replace all setter methods in the original. In fact we need different
* setter methods for different arguments ... but right now we just have
* one for objects.
*/
@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;
@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;
}
- (void) changeForKey: (NSString*)aKey;
- (id) initWithInstance: (NSObject*)i;
- (BOOL) isUnobserved;
@end
@implementation GSKVOBase
- (void) dealloc
{
// Turn off KVO for self ... then call the real dealloc implementation.
[self setObservationInfo: nil];
isa = [self class];
[self dealloc];
}
- (Class) class
{
return GSObjCSuper(GSObjCClass(self));
}
- (void) setValue: (id)anObject forKey: (NSString*)aKey
{
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
[super setValue: anObject forKey: aKey];
[self didChangeValueForKey: aKey];
}
else
{
[super setValue: anObject forKey: aKey];
}
}
- (void) setValue: (id)anObject forKeyPath: (NSString*)aKey
{
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
[super setValue: anObject forKeyPath: aKey];
[self didChangeValueForKey: aKey];
}
else
{
[super setValue: anObject forKeyPath: aKey];
}
}
- (void) takeStoredValue: (id)anObject forKey: (NSString*)aKey
{
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
[super takeStoredValue: anObject forKey: aKey];
[self didChangeValueForKey: aKey];
}
else
{
[super takeStoredValue: anObject forKey: aKey];
}
}
- (void) takeValue: (id)anObject forKey: (NSString*)aKey
{
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
[super takeValue: anObject forKey: aKey];
[self didChangeValueForKey: aKey];
}
else
{
[super takeValue: anObject forKey: aKey];
}
}
- (void) takeValue: (id)anObject forKeyPath: (NSString*)aKey
{
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
[super takeValue: anObject forKeyPath: aKey];
[self didChangeValueForKey: aKey];
}
else
{
[super takeValue: anObject forKeyPath: 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;
}
/*
* 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);
}
@end
@implementation GSKVOInfo
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
NSMapTable *observers;
[iLock lock];
observers = (NSMapTable*)NSMapGet(paths, (void*)aPath);
if (observers == 0)
{
observers = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks,
NSNonOwnedPointerMapValueCallBacks, 8);
// use immutable object for map key
aPath = [aPath copy];
NSMapInsert(paths, (void*)aPath, (void*)observers);
RELEASE(aPath);
}
/*
* FIXME ... should store an object containing context and options.
* For simplicity right now, just store context or a dummy value.
*/
NSMapInsert(observers, (void*)anObserver, aContext == 0 ? dummy : aContext);
[iLock unlock];
}
- (void) dealloc
{
if (paths != 0) NSFreeMapTable(paths);
RELEASE(iLock);
[super dealloc];
}
- (void) changeForKey: (NSString*)aKey
{
NSMapTable *observers;
[iLock lock];
observers = (NSMapTable*)NSMapGet(paths, (void*)aKey);
if (observers != 0)
{
NSMapEnumerator enumerator;
NSObject *observer;
void *context;
enumerator = NSEnumerateMapTable(observers);
while (NSNextMapEnumeratorPair(&enumerator,
(void **)(&observer), &context))
{
if (context == dummy) context = 0;
if ([observer respondsToSelector:
@selector(observeValueForKeyPath:ofObject:change:context:)])
{
[observer observeValueForKeyPath: aKey
ofObject: instance
change: nil
context: context];
}
}
NSEndMapTableEnumeration(&enumerator);
}
[iLock unlock];
}
- (id) initWithInstance: (NSObject*)i
{
instance = i;
paths = NSCreateMapTable(NSObjectMapKeyCallBacks,
NSNonOwnedPointerMapValueCallBacks, 8);
iLock = [GSLazyRecursiveLock new];
return self;
}
- (BOOL) isUnobserved
{
BOOL result = NO;
[iLock lock];
if (NSCountMapTable(paths) == 0)
{
result = YES;
}
[iLock unlock];
return result;
}
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
NSMapTable *observers;
[iLock lock];
observers = (NSMapTable*)NSMapGet(paths, (void*)aPath);
if (observers != 0)
{
void *context = NSMapGet(observers, (void*)anObserver);
if (context != 0)
{
NSMapRemove(observers, (void*)anObserver);
if (NSCountMapTable(observers) == 0)
{
NSMapRemove(paths, (void*)aPath);
}
}
}
[iLock unlock];
}
@end
static inline void setup()
{
if (kvoLock == nil)
{
kvoLock = [GSLazyRecursiveLock new];
classTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
NSNonOwnedPointerMapValueCallBacks, 128);
infoTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
NSNonOwnedPointerMapValueCallBacks, 1024);
baseClass = NSClassFromString(@"GSKVOBase");
}
}
static Class classForInstance(id o)
{
Class c = GSObjCClass(o);
Class p;
setup();
[kvoLock lock];
p = (Class)NSMapGet(classTable, (void*)c);
if (p == 0)
{
NSValue *template;
NSString *superName;
NSString *name;
NSArray *methods;
NSMutableArray *setters;
unsigned count;
NSCharacterSet *lc = [NSCharacterSet lowercaseLetterCharacterSet];
/*
* Create subclass of the original, and override some methods
* with implementations from our abstract base class.
*/
superName = NSStringFromClass(c);
name = [@"GSKVO" stringByAppendingString: superName];
template = GSObjCMakeClass(name, superName, nil);
GSObjCAddClasses([NSArray arrayWithObject: template]);
p = NSClassFromString(name);
GSObjCAddClassBehavior(p, baseClass);
/*
* Get the names of all setter methods set(Key): or _set(Key):
*/
methods = GSObjCMethodNames(o);
count = [methods count];
setters = [NSMutableArray arrayWithCapacity: count];
while (count-- > 0)
{
NSRange r;
int x = 3;
name = [methods objectAtIndex: count];
r = [name rangeOfString: @":"];
if (r.length > 0 && r.location == [name length]-1
&& ([name hasPrefix: @"set"] || [name hasPrefix: @"_set"]))
{
unichar u = [name characterAtIndex: x];
/*
* If the key name part begins with a lowercase letter,
* this is not a setter method.
*/
if ([lc characterIsMember: u] == NO)
{
/*
* Don't override setObservationInfo: ... it's a special
* case.
*/
if ([name isEqualToString: @"setObservationInfo:"] == NO)
{
[setters addObject: name];
}
}
}
}
count = [setters count];
if (count > 0)
{
GSMethodList m;
/*
* The original class contains setter methods ... so we must
* replace them all with our own version which does KVO
* notifications.
*/
m = GSAllocMethodList(count);
while (count-- > 0)
{
NSMethodSignature *sig;
SEL sel;
IMP imp;
const char *type;
name = [setters objectAtIndex: count];
sel = NSSelectorFromString(name);
sig = [o methodSignatureForSelector: sel];
/*
* A setter must take three arguments (self, _cmd, value)
* and return nothing.
*/
if (*[sig methodReturnType] != _C_VOID
|| [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;
default:
imp = 0;
break;
}
if (imp != 0)
{
GSAppendMethodToList(m, sel, [sig methodType], imp, YES);
}
}
GSAddMethodList(p, m, YES);
GSFlushMethodCacheForClass(p);
}
NSMapInsert(classTable, (void*)c, (void*)p);
}
[kvoLock unlock];
return p;
}
@implementation NSObject (NSKeyValueObserving)
- (void) observeValueForKeyPath: (NSString*)aPath
ofObject: (id)anObject
change: (NSDictionary*)aChange
context: (void*)aContext
{
}
@end
@implementation NSObject (NSKeyValueObserverRegistration)
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
GSKVOInfo *info;
Class c;
setup();
[kvoLock lock];
/*
* 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)
{
c = classForInstance(self);
info = [[GSKVOInfo alloc] initWithInstance: self];
[self setObservationInfo: info];
isa = c;
}
/*
* Now add the observer.
*/
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: aContext];
[kvoLock unlock];
}
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
GSKVOInfo *info;
setup();
[kvoLock lock];
/*
* Get the observation information and remove this observation.
*/
info = (GSKVOInfo*)[self observationInfo];
[info removeObserver: anObserver forKeyPath: aPath];
if ([info isUnobserved] == YES)
{
/*
* The instance is no longer bing observed ... so we can
* turn off key-value-observing for it.
*/
isa = [self class];
AUTORELEASE(info);
[self setObservationInfo: nil];
}
[kvoLock unlock];
}
@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) didChangeValueForKey: (NSString*)aKey
{
GSKVOInfo *info = [self observationInfo];
[info changeForKey: aKey];
}
- (void) didChange: (NSKeyValueChange)changeKind
valuesAtIndexes: (NSIndexSet*)indexes
forKey: (NSString*)aKey
{
}
- (void) willChangeValueForKey: (NSString*)aKey
{
}
- (void) willChange: (NSKeyValueChange)changeKind
valuesAtIndexes: (NSIndexSet*)indexes
forKey: (NSString*)aKey
{
}
- (void) didChangeValueForKey: (NSString*)aKey
withSetMutation: (NSKeyValueSetMutationKind)mutationKind
usingObjects: (NSSet*)objects
{
}
- (void) willChangeValueForKey: (NSString*)aKey
withSetMutation: (NSKeyValueSetMutationKind)mutationKind
usingObjects: (NSSet*)objects
{
}
@end
@implementation NSObject (NSKeyValueObservingCustomization)
+ (BOOL) automaticallyNotifiesObserversForKey: (NSString*)aKey
{
return YES;
}
+ (void) setKeys: (NSArray*)keys
triggerChangeNotificationsForDependentKey: (NSString*)dependentKey
{
[self notImplemented: _cmd];
}
- (void*) observationInfo
{
void *info;
setup();
[kvoLock lock];
info = NSMapGet(infoTable, (void*)self);
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