mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-22 00:11:04 +00:00
* GSAtomic: Add prefix to macro definitions * NSKVOSupport: Import * NSKVOSupport: Add test cases * NSKVOSwizzling: Ugly C Rewrite * NSKeyValueObserving: Use old implementation as fallback * NSKeyValueObserving: Rename TypeEncodingCases header * NSKVOSupport: Fix new objects not being added to NSKeyValueChangeNew set on set mutation * NSKeyValueMutableSet: Fix will and didChange notifications for set operations * NSKeyValueMutableSet: Document Accessor Search Patterns * NSKVOSupport: Add toMany test * NSKeyValueCoding: Change notifications when changing value via setValue:forKey: * NSKVOSupport: Add more tests * NSKVOSupport: Do not wrap block in try/finally to avoid crash in windows * NSKVOSwizzling: use _alloca on Windows * NSKVOSupport: Do not autorelease newWithObservee: * NSKVOSupport: Do not leak Observee and TestFacade objects * Improve runtime detection in makefile * Add file extension of source file in GNUMakefile * NSKVOSupport: Remove @status comments * NSKVOSupport: Implement private notify method * NSUserDefaults: KVO Support and fix macOS incompatibilities * NSKeyValueObserving: Set old to null if nil * NSKeyValueObserving: Remove cached new value * NSMethodSignature: Add signature cache * NSKVOSupport: Remove ObjC2 features and mark tests failing on GCC as hopeful * Call class method instead of private _keyPathsForValuesAffectingValueForKey * Move _keyPathsForValuesAffectingValueForKey body into class method and statically construct empty NSSet * NSUserDefaults: Change notification should contain old value from other domains aswell * NSUserDefaults: Fetch new value from all domains * NSKVOInternal: Fixup filename in header * NSUserDefaults: Go through search list instead of only one domain in KVO change * Making indentation a bit less worse * Add NSUserDefaults KVO tests * NSKVOSupport: NSUserDefaults test small fixes * Add autoreleasepool * NSUserDefaults: Only emit change notifications if value changed * Avoid compiler warnings and tidy some of the whitespace/formatting --------- Co-authored-by: Frederik Seiffert <frederik@algoriddim.com> Co-authored-by: rfm <richardfrithmacdonald@gmail.com> Co-authored-by: rfm <rfm@gnu.org>
695 lines
24 KiB
Objective-C
695 lines
24 KiB
Objective-C
/**
|
|
NSKVOSwizzling.m
|
|
|
|
Copyright (C) 2024 Free Software Foundation, Inc.
|
|
|
|
Written by: Hugo Melder <hugo@algoriddim.com>
|
|
Date: June 2024
|
|
|
|
Based on WinObjC KVO tests by Microsoft Corporation.
|
|
|
|
This file is part of GNUStep-base
|
|
|
|
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
|
|
Lesser General Public License for more details.
|
|
|
|
If you are interested in a warranty or support for this source code,
|
|
contact Scott Christley <scottc@net-community.com> for more information.
|
|
|
|
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 02110 USA.
|
|
*/
|
|
/**
|
|
Copyright (c) Microsoft. All rights reserved.
|
|
|
|
This code is licensed under the MIT License (MIT).
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
*/
|
|
|
|
/* This Key Value Observing Implementation is tied to libobjc2 */
|
|
|
|
#import "common.h"
|
|
#import "NSKVOInternal.h"
|
|
|
|
#import <objc/encoding.h>
|
|
#import <objc/runtime.h>
|
|
#import "NSKVOTypeEncodingCases.h"
|
|
|
|
#import <Foundation/Foundation.h>
|
|
|
|
#ifdef WIN32
|
|
#define alloca(x) _alloca(x)
|
|
#endif
|
|
|
|
/* These are defined by the ABI and the runtime. */
|
|
#define ABI_SUPER(obj) (((Class **) obj)[0][1])
|
|
#define ABI_ISA(obj) (((Class *) obj)[0])
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-objc-pointer-introspection"
|
|
// Small objects, as defined by libobjc2, are tagged pointers. Instead of being
|
|
// heap-allocated, the object is stored in the sizeof(id) -
|
|
// OBJC_SMALL_OBJECT_BITS bits of the "pointer" itself.
|
|
static inline bool
|
|
isSmallObject_np(id object)
|
|
{
|
|
return ((((uintptr_t) object) & OBJC_SMALL_OBJECT_MASK) != 0);
|
|
}
|
|
#pragma clang diagnostic pop
|
|
|
|
static void
|
|
NSKVO$setObject$forKey$(id self, SEL _cmd, id object, NSString *key);
|
|
static void
|
|
NSKVO$removeObjectForKey$(id self, SEL _cmd, NSString *key);
|
|
static void
|
|
NSKVO$nilIMP(id self, SEL _cmd)
|
|
{}
|
|
|
|
static void
|
|
_NSKVOEnsureObjectIsKVOAware(id object)
|
|
{
|
|
// We have to use ABI_ISA here, because object_getClass will skip the secret
|
|
// hidden subclass.
|
|
if (class_respondsToSelector(ABI_ISA(object), @selector(_isKVOAware)))
|
|
{
|
|
// The presence of _isKVOAware signals that we have already mangled this
|
|
// object.
|
|
return;
|
|
}
|
|
|
|
/*
|
|
A crucial design decision was made here: because object_getClass() skips
|
|
autogenerated subclasses, the likes of which are used for associated objects,
|
|
the KVO machinery (if implemented using manual subclassing) would delete all
|
|
associated objects on an observed instance. That was deemed unacceptable.
|
|
|
|
Likewise, this implementation is not free of issues:
|
|
- The _np methods are nonportable.
|
|
- Anyone using class_getMethodImplementation(object_getClass(...), ...) will
|
|
receive the original IMP for any overridden method.
|
|
- We have to manually load isa to get at the secret subclass (thus the use of
|
|
ABI_ISA/ABI_SUPER.)
|
|
- It is dependent upon a libobjc2 implementation detail:
|
|
object_addMethod_np creates a hidden subclass for the object's class (one
|
|
per object!)
|
|
*/
|
|
|
|
object_addMethod_np(object, @selector(setObject:forKey:),
|
|
(IMP)(NSKVO$setObject$forKey$), "v@:@@");
|
|
object_addMethod_np(object, @selector(removeObjectForKey:),
|
|
(IMP)(NSKVO$removeObjectForKey$), "v@:@");
|
|
object_addMethod_np(object, @selector(_isKVOAware),
|
|
(IMP)(NSKVO$nilIMP), "v@:");
|
|
}
|
|
|
|
#pragma region Method Implementations
|
|
// Selector mappings: the class-level mapping from a selector (setX:) to the KVC
|
|
// key ("x") to which it corresponds. This is necessary because both "X" and "x"
|
|
// map to setX:, but we need to be cognizant of precisely which it was for any
|
|
// given observee. The sole reason we can get away with keeping selector<->key
|
|
// mappings on the class is that, through the lifetime of said class, it can
|
|
// never lose selectors. This mapping will be pertinent to every instance of the
|
|
// class.
|
|
static inline NSMapTable *
|
|
_selectorMappingsForObject(id object)
|
|
{
|
|
static char s_selMapKey;
|
|
Class cls;
|
|
|
|
// here we explicitly want the public
|
|
// (non-hidden) class associated with the object.
|
|
cls = object_getClass(object);
|
|
|
|
@synchronized(cls)
|
|
{
|
|
NSMapTable *selMappings
|
|
= (NSMapTable *) objc_getAssociatedObject(cls, &s_selMapKey);
|
|
if (!selMappings)
|
|
{
|
|
selMappings = [NSMapTable
|
|
mapTableWithKeyOptions: NSPointerFunctionsOpaqueMemory
|
|
| NSPointerFunctionsOpaquePersonality
|
|
valueOptions: NSPointerFunctionsStrongMemory
|
|
| NSPointerFunctionsObjectPersonality];
|
|
objc_setAssociatedObject(cls, &s_selMapKey, (id) selMappings,
|
|
OBJC_ASSOCIATION_RETAIN);
|
|
}
|
|
return selMappings;
|
|
}
|
|
}
|
|
|
|
static inline NSString *
|
|
_keyForSelector(id object, SEL selector)
|
|
{
|
|
return (NSString *) NSMapGet(_selectorMappingsForObject(object),
|
|
sel_getName(selector));
|
|
}
|
|
|
|
static inline void
|
|
_associateSelectorWithKey(id object, SEL selector, NSString *key)
|
|
{
|
|
/* this must be insertIfAbsent. otherwise, when calling a setter that itself
|
|
* causes observers to be added/removed on this key, this would call this
|
|
* method and overwrite the association selector->key with an identical key
|
|
* but another object. unfortunately, this would mean that the
|
|
* notifyingSetImpl below would then have a dead ```key``` pointer once it
|
|
* came time to call didChangeValueForKey (because apparently ARC doesn't take
|
|
* care of this properly)
|
|
*/
|
|
NSMapInsertIfAbsent(_selectorMappingsForObject(object),
|
|
sel_getName(selector), key);
|
|
}
|
|
|
|
static void
|
|
notifyingVariadicSetImpl(id self, SEL _cmd, ...)
|
|
{
|
|
NSString *key = _keyForSelector(self, _cmd);
|
|
|
|
/* [Source: NSInvocation.mm]
|
|
* This attempts to flatten the method's arguments (as determined by its type
|
|
* encoding) from the stack into a buffer. That buffer is then emitted back
|
|
* onto the stack for the imp callthrough. This only works if we assume that
|
|
* our calling convention passes variadics and non-variadics in the same way:
|
|
* on the stack. For our two supported platforms, this seems to hold true.
|
|
*/
|
|
NSMethodSignature *sig = [self methodSignatureForSelector: _cmd];
|
|
size_t argSz = objc_sizeof_type([sig getArgumentTypeAtIndex: 2]);
|
|
size_t nStackArgs = argSz / sizeof(uintptr_t);
|
|
uintptr_t *raw = (uintptr_t *) (calloc(sizeof(uintptr_t), nStackArgs));
|
|
va_list ap;
|
|
|
|
va_start(ap, _cmd);
|
|
for (uintptr_t i = 0; i < nStackArgs; ++i)
|
|
{
|
|
raw[i] = va_arg(ap, uintptr_t);
|
|
}
|
|
va_end(ap);
|
|
|
|
[self willChangeValueForKey: key];
|
|
{
|
|
struct objc_super super = {self, ABI_SUPER(self)};
|
|
IMP imp = (id(*)(id, SEL, ...)) objc_msg_lookup_super(&super, _cmd);
|
|
|
|
// VSO 5955259; NSInvocation informs this implementation and this will need
|
|
// to be cleaned up when NSInvocation is.
|
|
switch (nStackArgs)
|
|
{
|
|
case 1:
|
|
imp(self, _cmd, raw[0]);
|
|
break;
|
|
case 2:
|
|
imp(self, _cmd, raw[0], raw[1]);
|
|
break;
|
|
case 3:
|
|
imp(self, _cmd, raw[0], raw[1], raw[2]);
|
|
break;
|
|
case 4:
|
|
imp(self, _cmd, raw[0], raw[1], raw[2], raw[3]);
|
|
break;
|
|
case 5:
|
|
imp(self, _cmd, raw[0], raw[1], raw[2], raw[3], raw[4]);
|
|
break;
|
|
case 6:
|
|
imp(self, _cmd, raw[0], raw[1], raw[2], raw[3], raw[4], raw[5]);
|
|
break;
|
|
default:
|
|
NSLog(@"Can't override setter with more than 6"
|
|
@" sizeof(long int) stack arguments.");
|
|
return;
|
|
}
|
|
}
|
|
[self didChangeValueForKey: key];
|
|
|
|
free(raw);
|
|
}
|
|
|
|
typedef void (*insertObjectAtIndexIMP)(id, SEL, id, NSUInteger);
|
|
typedef void (*removeObjectAtIndexIMP)(id, SEL, NSUInteger);
|
|
typedef void (*insertAtIndexesIMP)(id, SEL, id, NSIndexSet *);
|
|
typedef void (*removeAtIndexesIMP)(id, SEL, NSIndexSet *);
|
|
typedef void (*replaceAtIndexesIMP)(id, SEL, NSIndexSet *, NSArray *);
|
|
typedef void (*setObjectForKeyIMP)(id, SEL, id, NSString *);
|
|
typedef void (*removeObjectForKeyIMP)(id, SEL, NSString *);
|
|
typedef void (*replaceObjectAtIndexWithObjectIMP)(id, SEL, NSUInteger, id);
|
|
|
|
static void
|
|
NSKVONotifying$insertObject$inXxxAtIndex$(id self, SEL _cmd, id object,
|
|
NSUInteger index)
|
|
{
|
|
NSString *key = _keyForSelector(self, _cmd);
|
|
NSIndexSet *indexes = [NSIndexSet indexSetWithIndex: index];
|
|
|
|
[self willChange: NSKeyValueChangeInsertion
|
|
valuesAtIndexes: indexes
|
|
forKey: key];
|
|
{
|
|
struct objc_super super = {self, ABI_SUPER(self)};
|
|
insertObjectAtIndexIMP imp = (void (*)(id, SEL, id, NSUInteger))
|
|
objc_msg_lookup_super(&super, _cmd);
|
|
imp(self, _cmd, object, index);
|
|
}
|
|
[self didChange: NSKeyValueChangeInsertion
|
|
valuesAtIndexes: indexes
|
|
forKey: key];
|
|
}
|
|
|
|
static void
|
|
NSKVONotifying$insertXxx$atIndexes$(id self, SEL _cmd, id object,
|
|
NSIndexSet *indexes)
|
|
{
|
|
NSString *key = _keyForSelector(self, _cmd);
|
|
|
|
[self willChange: NSKeyValueChangeInsertion
|
|
valuesAtIndexes: indexes
|
|
forKey: key];
|
|
{
|
|
struct objc_super super = {self, ABI_SUPER(self)};
|
|
insertAtIndexesIMP imp = (void (*)(id, SEL, id, NSIndexSet *))
|
|
objc_msg_lookup_super(&super, _cmd);
|
|
imp(self, _cmd, object, indexes);
|
|
}
|
|
[self didChange: NSKeyValueChangeInsertion
|
|
valuesAtIndexes: indexes
|
|
forKey: key];
|
|
}
|
|
|
|
static void
|
|
NSKVONotifying$removeObjectFromXxxAtIndex$(id self, SEL _cmd, NSUInteger index)
|
|
{
|
|
NSString *key = _keyForSelector(self, _cmd);
|
|
NSIndexSet *indexes = [NSIndexSet indexSetWithIndex: index];
|
|
|
|
[self willChange: NSKeyValueChangeRemoval
|
|
valuesAtIndexes: indexes
|
|
forKey: key];
|
|
{
|
|
struct objc_super super = {self, ABI_SUPER(self)};
|
|
removeObjectAtIndexIMP imp = (void (*)(id, SEL, NSUInteger))
|
|
objc_msg_lookup_super(&super, _cmd);
|
|
imp(self, _cmd, index);
|
|
}
|
|
[self didChange: NSKeyValueChangeRemoval
|
|
valuesAtIndexes: indexes
|
|
forKey: key];
|
|
}
|
|
|
|
static void
|
|
NSKVONotifying$removeXxxAtIndexes$(id self, SEL _cmd, NSIndexSet *indexes)
|
|
{
|
|
NSString *key = _keyForSelector(self, _cmd);
|
|
|
|
[self willChange: NSKeyValueChangeRemoval
|
|
valuesAtIndexes: indexes
|
|
forKey: key];
|
|
{
|
|
struct objc_super super = {self, ABI_SUPER(self)};
|
|
removeAtIndexesIMP imp = (void (*)(id, SEL, NSIndexSet *))
|
|
objc_msg_lookup_super(&super, _cmd);
|
|
imp(self, _cmd, indexes);
|
|
}
|
|
[self didChange: NSKeyValueChangeRemoval
|
|
valuesAtIndexes: indexes
|
|
forKey: key];
|
|
}
|
|
|
|
static void
|
|
NSKVONotifying$replaceObjectInXxxAtIndex$withObject$(id self, SEL _cmd,
|
|
NSUInteger index, id object)
|
|
{
|
|
NSString *key = _keyForSelector(self, _cmd);
|
|
NSIndexSet *indexes = [NSIndexSet indexSetWithIndex: index];
|
|
|
|
[self willChange: NSKeyValueChangeReplacement
|
|
valuesAtIndexes: indexes
|
|
forKey: key];
|
|
{
|
|
struct objc_super super = {self, ABI_SUPER(self)};
|
|
replaceObjectAtIndexWithObjectIMP imp = (void (*)(id, SEL, NSUInteger, id))
|
|
objc_msg_lookup_super(&super, _cmd);
|
|
imp(self, _cmd, index, object);
|
|
}
|
|
[self didChange: NSKeyValueChangeReplacement
|
|
valuesAtIndexes: indexes
|
|
forKey: key];
|
|
}
|
|
|
|
static void
|
|
NSKVONotifying$replaceXxxAtIndexes$withXxx$(id self, SEL _cmd,
|
|
NSIndexSet *indexes, NSArray *objects)
|
|
{
|
|
NSString *key = _keyForSelector(self, _cmd);
|
|
|
|
[self willChange: NSKeyValueChangeReplacement
|
|
valuesAtIndexes: indexes
|
|
forKey: key];
|
|
{
|
|
struct objc_super super = {self, ABI_SUPER(self)};
|
|
replaceAtIndexesIMP imp = (void (*)(id, SEL, NSIndexSet *, NSArray *))
|
|
objc_msg_lookup_super(&super, _cmd);
|
|
imp(self, _cmd, indexes, objects);
|
|
}
|
|
[self didChange: NSKeyValueChangeReplacement
|
|
valuesAtIndexes: indexes
|
|
forKey: key];
|
|
}
|
|
|
|
#define GENERATE_NSKVOSetDispatch_IMPL(Kind) \
|
|
static inline void _NSKVOSetDispatch_##Kind(id self, SEL _cmd, NSSet *set) \
|
|
{ \
|
|
NSString *key = _keyForSelector(self, _cmd); \
|
|
[self willChangeValueForKey: key withSetMutation: Kind usingObjects: set]; \
|
|
{ \
|
|
struct objc_super super = {self, ABI_SUPER(self)}; \
|
|
void (*imp)(id, SEL, NSSet *) \
|
|
= (void (*)(id, SEL, NSSet *)) objc_msg_lookup_super(&super, _cmd); \
|
|
imp(self, _cmd, set); \
|
|
} \
|
|
[self didChangeValueForKey: key withSetMutation: Kind usingObjects: set]; \
|
|
}
|
|
|
|
#define GENERATE_NSKVOSetDispatchIndividual_IMPL(Kind) \
|
|
static inline void _NSKVOSetDispatchIndividual_##Kind(id self, SEL _cmd, \
|
|
id obj) \
|
|
{ \
|
|
NSSet *set = [NSSet setWithObject: obj]; \
|
|
NSString *key = _keyForSelector(self, _cmd); \
|
|
[self willChangeValueForKey: key withSetMutation: Kind usingObjects: set]; \
|
|
{ \
|
|
struct objc_super super = {self, ABI_SUPER(self)}; \
|
|
void (*imp)(id, SEL, id) \
|
|
= (void (*)(id, SEL, id)) objc_msg_lookup_super(&super, _cmd); \
|
|
imp(self, _cmd, obj); \
|
|
} \
|
|
[self didChangeValueForKey: key withSetMutation: Kind usingObjects: set]; \
|
|
}
|
|
|
|
GENERATE_NSKVOSetDispatchIndividual_IMPL(NSKeyValueUnionSetMutation);
|
|
GENERATE_NSKVOSetDispatchIndividual_IMPL(NSKeyValueMinusSetMutation);
|
|
//GENERATE_NSKVOSetDispatchIndividual_IMPL(NSKeyValueIntersectSetMutation);
|
|
|
|
GENERATE_NSKVOSetDispatch_IMPL(NSKeyValueUnionSetMutation);
|
|
GENERATE_NSKVOSetDispatch_IMPL(NSKeyValueMinusSetMutation);
|
|
GENERATE_NSKVOSetDispatch_IMPL(NSKeyValueIntersectSetMutation);
|
|
|
|
// - (void)setObject: (id)object forKey: (NSString*)key
|
|
static void
|
|
NSKVO$setObject$forKey$(id self, SEL _cmd, id object, NSString *key)
|
|
{
|
|
[self willChangeValueForKey: key];
|
|
{
|
|
struct objc_super super = {self, ABI_SUPER(self)};
|
|
setObjectForKeyIMP imp;
|
|
|
|
imp = (void (*)(id, SEL, id, NSString *)) objc_msg_lookup_super(&super, _cmd);
|
|
imp(self, _cmd, object, key);
|
|
}
|
|
[self didChangeValueForKey: key];
|
|
}
|
|
|
|
// - (void)removeObjectForKey: (NSString*)key
|
|
static void
|
|
NSKVO$removeObjectForKey$(id self, SEL _cmd, NSString *key)
|
|
{
|
|
[self willChangeValueForKey: key];
|
|
{
|
|
struct objc_super super = {self, ABI_SUPER(self)};
|
|
removeObjectForKeyIMP imp;
|
|
|
|
imp = (void (*)(id, SEL, NSString *)) objc_msg_lookup_super(&super, _cmd);
|
|
imp(self, _cmd, key);
|
|
}
|
|
[self didChangeValueForKey: key];
|
|
}
|
|
#pragma endregion
|
|
|
|
#define GENERATE_NOTIFYING_SET_IMPL(funcName, type) \
|
|
static void funcName(id self, SEL _cmd, type val) \
|
|
{ \
|
|
struct objc_super super = {self, ABI_SUPER(self)}; \
|
|
NSString *key = _keyForSelector(self, _cmd); \
|
|
void (*imp)(id, SEL, type) \
|
|
= (void (*)(id, SEL, type)) objc_msg_lookup_super(&super, _cmd); \
|
|
[self willChangeValueForKey: key]; \
|
|
imp(self, _cmd, val); \
|
|
[self didChangeValueForKey: key]; \
|
|
}
|
|
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplDouble, double);
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplFloat, float);
|
|
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplChar, signed char);
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplInt, int);
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplShort, short);
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplLong, long);
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplLongLong, long long);
|
|
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplUnsignedChar, unsigned char);
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplUnsignedInt, unsigned int);
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplUnsignedShort, unsigned short);
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplUnsignedLong, unsigned long);
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplUnsignedLongLong,
|
|
unsigned long long);
|
|
|
|
// GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplBool, bool);
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplObject, id);
|
|
GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplPointer, void *);
|
|
|
|
#define KVO_SET_IMPL_CASE(type, name, capitalizedName, encodingChar) \
|
|
case encodingChar: { \
|
|
newImpl = (IMP) (¬ifyingSetImpl##capitalizedName); \
|
|
break; \
|
|
}
|
|
|
|
SEL
|
|
KVCSetterForPropertyName(NSObject *self, const char *key)
|
|
{
|
|
SEL sel = nil;
|
|
size_t len = strlen(key);
|
|
// For the key "example", we must construct the following buffer:
|
|
// _ _ _ _ x a m p l e _ \0
|
|
// and fill it with the following characters:
|
|
// s e t E x a m p l e : \0
|
|
char *buf = (char *) alloca(3 + len + 2);
|
|
memcpy(buf + 4, key + 1, len);
|
|
buf[0] = 's';
|
|
buf[1] = 'e';
|
|
buf[2] = 't';
|
|
buf[3] = toupper(key[0]);
|
|
buf[3 + len] = ':';
|
|
buf[3 + len + 1] = '\0';
|
|
sel = sel_getUid(buf);
|
|
if ([self respondsToSelector: sel])
|
|
{
|
|
return sel;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
// invariant: rawKey has already been capitalized
|
|
static inline void
|
|
_NSKVOEnsureSimpleKeyWillNotify(id object, NSString *key, const char *rawKey)
|
|
{
|
|
Method originalMethod;
|
|
const char *valueType;
|
|
const char *types;
|
|
NSMethodSignature *sig;
|
|
SEL sel;
|
|
IMP newImpl = NULL;
|
|
|
|
sel = KVCSetterForPropertyName(object, rawKey);
|
|
originalMethod = class_getInstanceMethod(object_getClass(object), sel);
|
|
types = method_getTypeEncoding(originalMethod);
|
|
if (!types)
|
|
{
|
|
return;
|
|
}
|
|
|
|
sig = [NSMethodSignature signatureWithObjCTypes: types];
|
|
valueType = [sig getArgumentTypeAtIndex: 2];
|
|
|
|
switch (valueType[0])
|
|
{
|
|
OBJC_APPLY_ALL_TYPE_ENCODINGS(KVO_SET_IMPL_CASE);
|
|
case '{':
|
|
case '[': {
|
|
size_t valueSize = objc_sizeof_type(valueType);
|
|
if (valueSize > 6 * sizeof(uintptr_t))
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"Class %s key %@ has a value size of %u bytes"
|
|
@", and cannot currently be KVO compliant.",
|
|
class_getName(object_getClass(object)), key,
|
|
(unsigned int) (valueSize)];
|
|
}
|
|
newImpl = (IMP) (¬ifyingVariadicSetImpl);
|
|
break;
|
|
}
|
|
default:
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"Class %s is not KVO compliant for key %@.",
|
|
class_getName(object_getClass(object)), key];
|
|
return;
|
|
}
|
|
|
|
_associateSelectorWithKey(object, sel, key);
|
|
object_addMethod_np(object, sel, newImpl, types);
|
|
}
|
|
|
|
static void
|
|
replaceAndAssociateWithKey(id object, SEL sel, NSString *key, IMP imp)
|
|
{
|
|
if ([object respondsToSelector: sel])
|
|
{
|
|
const char *selName = sel_getName(sel);
|
|
Method method = class_getInstanceMethod(object_getClass(object), sel);
|
|
if (!method)
|
|
{
|
|
NSWarnLog(@"NSObject (NSKeyValueObservation): Unable to find method "
|
|
@"for %s on class %s; perhaps it is a forward?",
|
|
selName, object_getClassName(object));
|
|
return;
|
|
}
|
|
|
|
_associateSelectorWithKey(object, sel, key);
|
|
object_replaceMethod_np(object, sel, imp, method_getTypeEncoding(method));
|
|
}
|
|
}
|
|
|
|
static SEL
|
|
formatSelector(NSString *format, ...)
|
|
{
|
|
NSString *s;
|
|
SEL sel;
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
s = [[NSString alloc] initWithFormat: format arguments: ap];
|
|
sel = NSSelectorFromString([s autorelease]);
|
|
va_end(ap);
|
|
return sel;
|
|
}
|
|
|
|
// invariant: rawKey has already been capitalized
|
|
static inline void
|
|
_NSKVOEnsureOrderedCollectionWillNotify(id object, NSString *key,
|
|
const char *rawKey)
|
|
{
|
|
SEL insertOne = formatSelector(@"insertObject:in%sAtIndex:", rawKey);
|
|
SEL insertMany = formatSelector(@"insert%s:atIndexes:", rawKey);
|
|
|
|
if ([object respondsToSelector: insertOne]
|
|
|| [object respondsToSelector: insertMany])
|
|
{
|
|
replaceAndAssociateWithKey(object, insertOne, key,
|
|
(IMP)NSKVONotifying$insertObject$inXxxAtIndex$);
|
|
replaceAndAssociateWithKey(object, insertMany, key,
|
|
(IMP)NSKVONotifying$insertXxx$atIndexes$);
|
|
replaceAndAssociateWithKey(
|
|
object, formatSelector(@"removeObjectFrom%sAtIndex:", rawKey), key,
|
|
(IMP)NSKVONotifying$removeObjectFromXxxAtIndex$);
|
|
replaceAndAssociateWithKey(object,
|
|
formatSelector(@"remove%sAtIndexes:", rawKey),
|
|
key, (IMP)NSKVONotifying$removeXxxAtIndexes$);
|
|
replaceAndAssociateWithKey(
|
|
object, formatSelector(@"replaceObjectIn%sAtIndex:withObject:", rawKey),
|
|
key, (IMP)NSKVONotifying$replaceObjectInXxxAtIndex$withObject$);
|
|
replaceAndAssociateWithKey(
|
|
object, formatSelector(@"replace%sAtIndexes:with%s:", rawKey, rawKey),
|
|
key, (IMP)NSKVONotifying$replaceXxxAtIndexes$withXxx$);
|
|
}
|
|
}
|
|
|
|
// invariant: rawKey has already been capitalized
|
|
static inline void
|
|
_NSKVOEnsureUnorderedCollectionWillNotify(id object, NSString *key,
|
|
const char *rawKey)
|
|
{
|
|
SEL addOne = formatSelector(@"add%sObject:", rawKey);
|
|
SEL addMany = formatSelector(@"add%s:", rawKey);
|
|
SEL removeOne = formatSelector(@"remove%sObject:", rawKey);
|
|
SEL removeMany = formatSelector(@"remove%s:", rawKey);
|
|
|
|
if (([object respondsToSelector: addOne]
|
|
|| [object respondsToSelector: addMany])
|
|
&& ([object respondsToSelector: removeOne]
|
|
|| [object respondsToSelector: removeMany]))
|
|
{
|
|
replaceAndAssociateWithKey(
|
|
object, addOne, key,
|
|
(IMP)_NSKVOSetDispatchIndividual_NSKeyValueUnionSetMutation);
|
|
replaceAndAssociateWithKey(
|
|
object, addMany, key,
|
|
(IMP)_NSKVOSetDispatch_NSKeyValueUnionSetMutation);
|
|
replaceAndAssociateWithKey(
|
|
object, removeOne, key,
|
|
(IMP)_NSKVOSetDispatchIndividual_NSKeyValueMinusSetMutation);
|
|
replaceAndAssociateWithKey(
|
|
object, removeMany, key,
|
|
(IMP)_NSKVOSetDispatch_NSKeyValueMinusSetMutation);
|
|
replaceAndAssociateWithKey(
|
|
object, formatSelector(@"intersect%s:", rawKey), key,
|
|
(IMP)_NSKVOSetDispatch_NSKeyValueIntersectSetMutation);
|
|
}
|
|
}
|
|
|
|
char *
|
|
mutableBufferFromString(NSString *string)
|
|
{
|
|
NSUInteger lengthInBytes = [string length] + 1;
|
|
char *rawKey = (char *) malloc(lengthInBytes);
|
|
|
|
[string getCString: rawKey
|
|
maxLength: lengthInBytes
|
|
encoding: NSASCIIStringEncoding];
|
|
return rawKey;
|
|
}
|
|
|
|
// NSKVOEnsureKeyWillNotify is the main entrypoint into the swizzling code.
|
|
void
|
|
_NSKVOEnsureKeyWillNotify(id object, NSString *key)
|
|
{
|
|
char *rawKey;
|
|
|
|
// Since we cannot replace the isa of tagged pointer objects, we can't swizzle
|
|
// them.
|
|
if (isSmallObject_np(object))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// A class is allowed to decline automatic swizzling for any/all of its keys.
|
|
if (![[object class] automaticallyNotifiesObserversForKey: key])
|
|
{
|
|
return;
|
|
}
|
|
|
|
rawKey = mutableBufferFromString(key);
|
|
rawKey[0] = toupper(rawKey[0]);
|
|
|
|
@synchronized(object)
|
|
{
|
|
_NSKVOEnsureObjectIsKVOAware(object);
|
|
_NSKVOEnsureSimpleKeyWillNotify(object, key, rawKey);
|
|
_NSKVOEnsureOrderedCollectionWillNotify(object, key, rawKey);
|
|
_NSKVOEnsureUnorderedCollectionWillNotify(object, key, rawKey);
|
|
}
|
|
|
|
free(rawKey);
|
|
}
|