mirror of
https://github.com/gnustep/libs-base.git
synced 2025-06-04 10:30:47 +00:00
rewrite code for overriding setters
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@25735 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
69fce99d3e
commit
95daef46c6
2 changed files with 230 additions and 196 deletions
|
@ -1,3 +1,9 @@
|
||||||
|
2007-12-14 Richard Frith-Macdonald <rfm@gnu.org>
|
||||||
|
|
||||||
|
* Source/NSKeyValueObserving.m: Rewrite code for the subclass which
|
||||||
|
handles overriding of setters. Should only override the setters for
|
||||||
|
the keys which have been observed. Compiles but untested!
|
||||||
|
|
||||||
2007-12-11 Richard Frith-Macdonald <rfm@gnu.org>
|
2007-12-11 Richard Frith-Macdonald <rfm@gnu.org>
|
||||||
|
|
||||||
* Source/NSUserDefaults.m: catch any exception if som eone breaks our
|
* Source/NSUserDefaults.m: catch any exception if som eone breaks our
|
||||||
|
|
|
@ -78,6 +78,20 @@ static NSMapTable *infoTable = 0;
|
||||||
static NSMapTable *dependentKeyTable;
|
static NSMapTable *dependentKeyTable;
|
||||||
static Class baseClass;
|
static Class baseClass;
|
||||||
|
|
||||||
|
static inline void setup()
|
||||||
|
{
|
||||||
|
if (kvoLock == nil)
|
||||||
|
{
|
||||||
|
kvoLock = [GSLazyRecursiveLock new];
|
||||||
|
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
|
* This is the template class whose methods are added to KVO classes to
|
||||||
* override the originals and make the swizzled class look like the
|
* override the originals and make the swizzled class look like the
|
||||||
|
@ -86,6 +100,21 @@ static Class baseClass;
|
||||||
@interface GSKVOBase : NSObject
|
@interface GSKVOBase : NSObject
|
||||||
@end
|
@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
|
* This is a placeholder class which has the abstract setter method used
|
||||||
* to replace all setter methods in the original.
|
* to replace all setter methods in the original.
|
||||||
|
@ -308,6 +337,187 @@ static NSString *newKey(SEL _cmd)
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static GSKVOReplacement *
|
||||||
|
replacementForInstance(id o)
|
||||||
|
{
|
||||||
|
Class c = GSObjCClass(o);
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
m = GSAllocMethodList(2);
|
||||||
|
|
||||||
|
suffix = [aKey capitalizedString];
|
||||||
|
a[0] = [NSString stringWithFormat: @"set%@:", suffix];
|
||||||
|
a[1] = [NSString stringWithFormat: @"_set%@:", 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)
|
||||||
|
* 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);
|
||||||
|
found = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found == YES)
|
||||||
|
{
|
||||||
|
GSAddMethodList(replacement, m, YES);
|
||||||
|
GSFlushMethodCacheForClass(replacement);
|
||||||
|
[keys addObject: aKey];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[NSException raise: NSInvalidArgumentException
|
||||||
|
format: @"class not KVC complient for %@", aKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (Class) replacement
|
||||||
|
{
|
||||||
|
return replacement;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This class
|
* This class
|
||||||
*/
|
*/
|
||||||
|
@ -764,7 +974,7 @@ static NSString *newKey(SEL _cmd)
|
||||||
{
|
{
|
||||||
if (anObject == observedObjectForUpdate)
|
if (anObject == observedObjectForUpdate)
|
||||||
{
|
{
|
||||||
[self keyPathChanged:nil];
|
[self keyPathChanged: nil];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -824,201 +1034,17 @@ static NSString *newKey(SEL _cmd)
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
static inline void setup()
|
|
||||||
{
|
|
||||||
if (kvoLock == nil)
|
|
||||||
{
|
|
||||||
kvoLock = [GSLazyRecursiveLock new];
|
|
||||||
classTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
|
|
||||||
NSNonOwnedPointerMapValueCallBacks, 128);
|
|
||||||
infoTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
|
|
||||||
NSNonOwnedPointerMapValueCallBacks, 1024);
|
|
||||||
dependentKeyTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
|
|
||||||
NSOwnedPointerMapValueCallBacks, 128);
|
|
||||||
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)
|
@implementation NSObject (NSKeyValueObserving)
|
||||||
|
|
||||||
/**
|
|
||||||
* NOT IMPLEMENTED
|
|
||||||
*/
|
|
||||||
- (void) observeValueForKeyPath: (NSString*)aPath
|
- (void) observeValueForKeyPath: (NSString*)aPath
|
||||||
ofObject: (id)anObject
|
ofObject: (id)anObject
|
||||||
change: (NSDictionary*)aChange
|
change: (NSDictionary*)aChange
|
||||||
context: (void*)aContext
|
context: (void*)aContext
|
||||||
{
|
{
|
||||||
|
[NSException raise: NSInvalidArgumentException
|
||||||
|
format: @"-%@ cannot be sent to %@ ..."
|
||||||
|
@" create an instance overriding this",
|
||||||
|
NSStringFromSelector(_cmd), NSStringFromClass([self class])];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1031,14 +1057,16 @@ static Class classForInstance(id o)
|
||||||
options: (NSKeyValueObservingOptions)options
|
options: (NSKeyValueObservingOptions)options
|
||||||
context: (void*)aContext
|
context: (void*)aContext
|
||||||
{
|
{
|
||||||
GSKVOInfo *info;
|
GSKVOInfo *info;
|
||||||
Class c;
|
GSKVOReplacement *r;
|
||||||
NSKeyValueObservationForwarder * forwarder;
|
NSKeyValueObservationForwarder *forwarder;
|
||||||
NSRange dot;
|
NSRange dot;
|
||||||
|
|
||||||
setup();
|
setup();
|
||||||
[kvoLock lock];
|
[kvoLock lock];
|
||||||
|
|
||||||
|
r = replacementForInstance(self);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the existing observation information, creating it (and changing
|
* Get the existing observation information, creating it (and changing
|
||||||
* the receiver to start key-value-observing by switching its class)
|
* the receiver to start key-value-observing by switching its class)
|
||||||
|
@ -1047,10 +1075,9 @@ static Class classForInstance(id o)
|
||||||
info = (GSKVOInfo*)[self observationInfo];
|
info = (GSKVOInfo*)[self observationInfo];
|
||||||
if (info == nil)
|
if (info == nil)
|
||||||
{
|
{
|
||||||
c = classForInstance(self);
|
|
||||||
info = [[GSKVOInfo alloc] initWithInstance: self];
|
info = [[GSKVOInfo alloc] initWithInstance: self];
|
||||||
[self setObservationInfo: info];
|
[self setObservationInfo: info];
|
||||||
isa = c;
|
isa = [r replacement];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1071,6 +1098,7 @@ static Class classForInstance(id o)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
[r overrideSetterFor: aPath];
|
||||||
[info addObserver: anObserver
|
[info addObserver: anObserver
|
||||||
forKeyPath: aPath
|
forKeyPath: aPath
|
||||||
options: options
|
options: options
|
||||||
|
@ -1328,7 +1356,7 @@ static Class classForInstance(id o)
|
||||||
oldSet = [change valueForKey: @"oldSet"];
|
oldSet = [change valueForKey: @"oldSet"];
|
||||||
set = [self valueForKey: aKey];
|
set = [self valueForKey: aKey];
|
||||||
|
|
||||||
[change setValue:nil forKey:@"oldSet"];
|
[change setValue: nil forKey: @"oldSet"];
|
||||||
|
|
||||||
if (mutationKind == NSKeyValueUnionSetMutation)
|
if (mutationKind == NSKeyValueUnionSetMutation)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue