mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-22 16:33:29 +00:00
Merge branch 'kvo_winobjc' into kvo-kvc-test
This commit is contained in:
commit
8a5c2b4c1a
15 changed files with 5513 additions and 29 deletions
|
@ -255,7 +255,6 @@ NSJSONSerialization.m \
|
|||
NSKeyedArchiver.m \
|
||||
NSKeyedUnarchiver.m \
|
||||
NSKeyValueCoding.m \
|
||||
NSKeyValueObserving.m \
|
||||
NSLengthFormatter.m \
|
||||
NSLinguisticTagger.m \
|
||||
NSLocale.m \
|
||||
|
@ -359,6 +358,19 @@ NSZone.m \
|
|||
externs.m \
|
||||
objc-load.m
|
||||
|
||||
# We have two implementations for Key Value Observing.
|
||||
# One highly-optimised one that depends on libobjc2
|
||||
# and the original implementation.
|
||||
ifeq ($(OBJC_RUNTIME_LIB), ng)
|
||||
BASE_MFILES += \
|
||||
NSKVOSupport.m \
|
||||
NSKVOSwizzling.m
|
||||
else
|
||||
BASE_MFILES += \
|
||||
NSKeyValueObserving.m
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(GNUSTEP_TARGET_OS), mingw32)
|
||||
ifneq ($(GNUSTEP_TARGET_OS), mingw64)
|
||||
ifneq ($(GNUSTEP_TARGET_OS), windows)
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
* Use native C11 atomic operations. _Atomic() should be defined by the
|
||||
* compiler.
|
||||
*/
|
||||
#define atomic_load_explicit(object, order) \
|
||||
#define gs_atomic_load_explicit(object, order) \
|
||||
__c11_atomic_load(object, order)
|
||||
#define atomic_store_explicit(object, desired, order) \
|
||||
#define gs_atomic_store_explicit(object, desired, order) \
|
||||
__c11_atomic_store(object, desired, order)
|
||||
|
||||
#else
|
||||
|
@ -33,7 +33,7 @@
|
|||
#define _Atomic(T) struct { T volatile __val; }
|
||||
#if __has_builtin(__sync_swap)
|
||||
/* Clang provides a full-barrier atomic exchange - use it if available. */
|
||||
#define atomic_exchange_explicit(object, desired, order) \
|
||||
#define gs_atomic_exchange_explicit(object, desired, order) \
|
||||
((void)(order), __sync_swap(&(object)->__val, desired))
|
||||
#else
|
||||
/*
|
||||
|
@ -41,7 +41,7 @@
|
|||
* practice it is usually a full barrier) so we need an explicit barrier before
|
||||
* it.
|
||||
*/
|
||||
#define atomic_exchange_explicit(object, desired, order) \
|
||||
#define gs_atomic_exchange_explicit(object, desired, order) \
|
||||
__extension__ ({ \
|
||||
__typeof__(object) __o = (object); \
|
||||
__typeof__(desired) __d = (desired); \
|
||||
|
@ -50,10 +50,10 @@ __extension__ ({ \
|
|||
__sync_lock_test_and_set(&(__o)->__val, __d); \
|
||||
})
|
||||
#endif
|
||||
#define atomic_load_explicit(object, order) \
|
||||
#define gs_atomic_load_explicit(object, order) \
|
||||
((void)(order), __sync_fetch_and_add(&(object)->__val, 0))
|
||||
#define atomic_store_explicit(object, desired, order) \
|
||||
((void)atomic_exchange_explicit(object, desired, order))
|
||||
#define gs_atomic_store_explicit(object, desired, order) \
|
||||
((void)gs_atomic_exchange_explicit(object, desired, order))
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -64,9 +64,9 @@ __extension__ ({ \
|
|||
/*
|
||||
* Convenience functions.
|
||||
*/
|
||||
#define atomic_load(object) \
|
||||
atomic_load_explicit(object, __ATOMIC_SEQ_CST)
|
||||
#define atomic_store(object, desired) \
|
||||
atomic_store_explicit(object, desired, __ATOMIC_SEQ_CST)
|
||||
#define gs_atomic_load(object) \
|
||||
gs_atomic_load_explicit(object, __ATOMIC_SEQ_CST)
|
||||
#define gs_atomic_store(object, desired) \
|
||||
gs_atomic_store_explicit(object, desired, __ATOMIC_SEQ_CST)
|
||||
|
||||
#endif // _GSAtomic_h_
|
||||
|
|
|
@ -62,12 +62,18 @@ typedef CONDITION_VARIABLE gs_cond_t;
|
|||
#define GS_COND_BROADCAST(cond) WakeAllConditionVariable(&(cond))
|
||||
|
||||
/* Pthread-like locking primitives defined in NSLock.m */
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void gs_mutex_init(gs_mutex_t *l, gs_mutex_attr_t attr);
|
||||
int gs_mutex_lock(gs_mutex_t *l);
|
||||
int gs_mutex_trylock(gs_mutex_t *l);
|
||||
int gs_mutex_unlock(gs_mutex_t *l);
|
||||
int gs_cond_wait(gs_cond_t *cond, gs_mutex_t *mutex);
|
||||
int gs_cond_timedwait(gs_cond_t *cond, gs_mutex_t *mutex, DWORD millisecs);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Threading primitives.
|
||||
|
|
116
Source/NSKVOInternal.h
Normal file
116
Source/NSKVOInternal.h
Normal file
|
@ -0,0 +1,116 @@
|
|||
/**
|
||||
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 <Foundation/Foundation.h>
|
||||
#import "GSPThread.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath) \
|
||||
do \
|
||||
{ \
|
||||
[NSException \
|
||||
raise:NSInvalidArgumentException \
|
||||
format:@"-[%s %s] is not supported. Key path: %@", \
|
||||
object_getClassName(self), sel_getName(_cmd), keyPath]; \
|
||||
} while (false)
|
||||
|
||||
@class _NSKVOKeypathObserver;
|
||||
|
||||
@interface _NSKVOKeyObserver : NSObject
|
||||
- (instancetype)initWithObject:(id)object
|
||||
keypathObserver:(_NSKVOKeypathObserver *)keypathObserver
|
||||
key:(NSString *)key
|
||||
restOfKeypath:(NSString *)restOfKeypath
|
||||
affectedObservers:(NSArray *)affectedObservers;
|
||||
@property (nonatomic, retain) _NSKVOKeypathObserver *keypathObserver;
|
||||
@property (nonatomic, retain) _NSKVOKeyObserver *restOfKeypathObserver;
|
||||
@property (nonatomic, retain) NSArray *dependentObservers;
|
||||
@property (nonatomic, assign) id object;
|
||||
@property (nonatomic, copy) NSString *key;
|
||||
@property (nonatomic, copy) NSString *restOfKeypath;
|
||||
@property (nonatomic, retain) NSArray *affectedObservers;
|
||||
@property (nonatomic, assign) BOOL root;
|
||||
@property (nonatomic, readonly) BOOL isRemoved;
|
||||
@end
|
||||
|
||||
@interface _NSKVOKeypathObserver : NSObject
|
||||
- (instancetype)initWithObject:(id)object
|
||||
observer:(id)observer
|
||||
keyPath:(NSString *)keypath
|
||||
options:(NSKeyValueObservingOptions)options
|
||||
context:(void *)context;
|
||||
@property (nonatomic, assign) id object;
|
||||
@property (nonatomic, assign) id observer;
|
||||
@property (nonatomic, copy) NSString *keypath;
|
||||
@property (nonatomic, assign) NSKeyValueObservingOptions options;
|
||||
@property (nonatomic, assign) void *context;
|
||||
|
||||
@property (atomic, retain) NSMutableDictionary *pendingChange;
|
||||
@end
|
||||
|
||||
@interface _NSKVOObservationInfo : NSObject
|
||||
{
|
||||
NSMutableDictionary<NSString *, NSMutableArray<_NSKVOKeyObserver *> *>
|
||||
*_keyObserverMap;
|
||||
NSInteger _dependencyDepth;
|
||||
NSMutableSet<NSString *> *_existingDependentKeys;
|
||||
gs_mutex_t _lock;
|
||||
}
|
||||
- (instancetype)init;
|
||||
- (NSArray *)observersForKey:(NSString *)key;
|
||||
- (void)addObserver:(_NSKVOKeyObserver *)observer;
|
||||
@end
|
||||
|
||||
// From NSKVOSwizzling
|
||||
void
|
||||
_NSKVOEnsureKeyWillNotify(id object, NSString *key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
1171
Source/NSKVOSupport.m
Normal file
1171
Source/NSKVOSupport.m
Normal file
File diff suppressed because it is too large
Load diff
662
Source/NSKVOSwizzling.m
Normal file
662
Source/NSKVOSwizzling.m
Normal file
|
@ -0,0 +1,662 @@
|
|||
/**
|
||||
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"
|
||||
|
||||
#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
|
||||
= (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
|
||||
= (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)
|
||||
{
|
||||
SEL sel = KVCSetterForPropertyName(object, rawKey);
|
||||
|
||||
Method originalMethod = class_getInstanceMethod(object_getClass(object), sel);
|
||||
const char *types = method_getTypeEncoding(originalMethod);
|
||||
if (!types)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:types];
|
||||
|
||||
const char *valueType = [sig getArgumentTypeAtIndex:2];
|
||||
IMP newImpl = NULL;
|
||||
|
||||
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, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
SEL sel
|
||||
= NSSelectorFromString([[[NSString alloc] initWithFormat:format
|
||||
arguments:ap] 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)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
char *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);
|
||||
}
|
90
Source/NSKVOTypeEncodingCases.h
Normal file
90
Source/NSKVOTypeEncodingCases.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
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 */
|
||||
|
||||
#pragma once
|
||||
|
||||
// Each OBJC_APPLY_*_TYPE_ENCODINGS macro expects a single argument: the name
|
||||
// of a macro to apply for every type encoding. That macro should take the form
|
||||
// #define name(ctype, objectiveCName, CapitalizedObjectiveCName,
|
||||
// typeEncodingCharacter)
|
||||
|
||||
#if defined(_Bool)
|
||||
#define OBJC_APPLY_BOOL_TYPE_ENCODING(_APPLY_TYPE_MACRO) \
|
||||
_APPLY_TYPE_MACRO(_Bool, bool, Bool, 'B')
|
||||
#else
|
||||
#define OBJC_APPLY_BOOL_TYPE_ENCODING(_APPLY_TYPE_MACRO) // Nothing
|
||||
#endif
|
||||
|
||||
#define OBJC_APPLY_NUMERIC_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \
|
||||
_APPLY_TYPE_MACRO(double, double, Double, 'd') \
|
||||
_APPLY_TYPE_MACRO(float, float, Float, 'f') \
|
||||
_APPLY_TYPE_MACRO(signed char, char, Char, 'c') \
|
||||
_APPLY_TYPE_MACRO(int, int, Int, 'i') \
|
||||
_APPLY_TYPE_MACRO(short, short, Short, 's') \
|
||||
_APPLY_TYPE_MACRO(long, long, Long, 'l') \
|
||||
_APPLY_TYPE_MACRO(long long, longLong, LongLong, 'q') \
|
||||
_APPLY_TYPE_MACRO(unsigned char, unsignedChar, UnsignedChar, 'C') \
|
||||
_APPLY_TYPE_MACRO(unsigned short, unsignedShort, UnsignedShort, 'S') \
|
||||
_APPLY_TYPE_MACRO(unsigned int, unsignedInt, UnsignedInt, 'I') \
|
||||
_APPLY_TYPE_MACRO(unsigned long, unsignedLong, UnsignedLong, 'L') \
|
||||
_APPLY_TYPE_MACRO(unsigned long long, unsignedLongLong, UnsignedLongLong, \
|
||||
'Q') \
|
||||
OBJC_APPLY_BOOL_TYPE_ENCODING(_APPLY_TYPE_MACRO)
|
||||
|
||||
//APPLY_TYPE(__int128, int128, Int128, 't') \
|
||||
//APPLY_TYPE(unsigned __int128, unsignedInt128, UnsignedInt128, 'T')
|
||||
|
||||
#define OBJC_APPLY_OBJECTIVEC_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \
|
||||
_APPLY_TYPE_MACRO(id, object, Object, '@') \
|
||||
_APPLY_TYPE_MACRO(Class, class, Pointer, '#') \
|
||||
_APPLY_TYPE_MACRO(SEL, selector, Pointer, ':')
|
||||
#define OBJC_APPLY_POINTER_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \
|
||||
_APPLY_TYPE_MACRO(char *, cString, Pointer, '*')
|
||||
|
||||
#define OBJC_APPLY_ALL_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \
|
||||
OBJC_APPLY_NUMERIC_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \
|
||||
OBJC_APPLY_OBJECTIVEC_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \
|
||||
OBJC_APPLY_POINTER_TYPE_ENCODINGS(_APPLY_TYPE_MACRO)
|
|
@ -141,6 +141,7 @@ SetValueForKey(NSObject *self, id anObject, const char *key, unsigned size)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
GSObjCSetVal(self, key, anObject, sel, type, size, off);
|
||||
}
|
||||
|
||||
|
@ -346,6 +347,8 @@ static id ValueForKey(NSObject *self, const char *key, unsigned size)
|
|||
{
|
||||
unsigned size = [aKey length] * 8;
|
||||
char key[size + 1];
|
||||
BOOL shouldNotify = [[self class] automaticallyNotifiesObserversForKey:aKey];
|
||||
|
||||
#ifdef WANT_DEPRECATED_KVC_COMPAT
|
||||
IMP o = [self methodForSelector: @selector(takeValue:forKey:)];
|
||||
|
||||
|
@ -361,7 +364,18 @@ static id ValueForKey(NSObject *self, const char *key, unsigned size)
|
|||
maxLength: size + 1
|
||||
encoding: NSUTF8StringEncoding];
|
||||
size = strlen(key);
|
||||
|
||||
if (shouldNotify)
|
||||
{
|
||||
[self willChangeValueForKey: aKey];
|
||||
}
|
||||
|
||||
SetValueForKey(self, anObject, key, size);
|
||||
|
||||
if (shouldNotify)
|
||||
{
|
||||
[self didChangeValueForKey: aKey];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -104,6 +104,8 @@
|
|||
}
|
||||
|
||||
|
||||
/* Ordering as specified in "Accessor Search Patterns" from Key-Value Coding
|
||||
* Programming Guide */
|
||||
proxy = [NSKeyValueFastMutableSet setForKey: aKey
|
||||
ofObject: anObject
|
||||
withCapitalizedKey: keybuf];
|
||||
|
@ -718,14 +720,14 @@
|
|||
{
|
||||
[object willChangeValueForKey: key
|
||||
withSetMutation: NSKeyValueUnionSetMutation
|
||||
usingObjects: [NSSet setWithObject: anObject]];
|
||||
usingObjects: anObject];
|
||||
}
|
||||
[set unionSet: anObject];
|
||||
if (notifiesObservers && !changeInProgress)
|
||||
{
|
||||
[object didChangeValueForKey: key
|
||||
withSetMutation: NSKeyValueUnionSetMutation
|
||||
usingObjects: [NSSet setWithObject:anObject]];
|
||||
usingObjects: anObject];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -735,14 +737,14 @@
|
|||
{
|
||||
[object willChangeValueForKey: key
|
||||
withSetMutation: NSKeyValueMinusSetMutation
|
||||
usingObjects: [NSSet setWithObject: anObject]];
|
||||
usingObjects: anObject];
|
||||
}
|
||||
[set minusSet: anObject];
|
||||
if (notifiesObservers && !changeInProgress)
|
||||
{
|
||||
[object didChangeValueForKey: key
|
||||
withSetMutation: NSKeyValueMinusSetMutation
|
||||
usingObjects: [NSSet setWithObject: anObject]];
|
||||
usingObjects: anObject];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -752,14 +754,14 @@
|
|||
{
|
||||
[object willChangeValueForKey: key
|
||||
withSetMutation: NSKeyValueIntersectSetMutation
|
||||
usingObjects: [NSSet setWithObject: anObject]];
|
||||
usingObjects: anObject];
|
||||
}
|
||||
[set intersectSet: anObject];
|
||||
if (notifiesObservers && !changeInProgress)
|
||||
{
|
||||
[object didChangeValueForKey: key
|
||||
withSetMutation: NSKeyValueIntersectSetMutation
|
||||
usingObjects: [NSSet setWithObject: anObject]];
|
||||
usingObjects: anObject];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -769,14 +771,14 @@
|
|||
{
|
||||
[object willChangeValueForKey: key
|
||||
withSetMutation: NSKeyValueSetSetMutation
|
||||
usingObjects: [NSSet setWithObject: anObject]];
|
||||
usingObjects: anObject];
|
||||
}
|
||||
[set setSet: anObject];
|
||||
if (notifiesObservers && !changeInProgress)
|
||||
{
|
||||
[object didChangeValueForKey: key
|
||||
withSetMutation: NSKeyValueSetSetMutation
|
||||
usingObjects: [NSSet setWithObject: anObject]];
|
||||
usingObjects: anObject];
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
|
|
@ -952,12 +952,12 @@ gs_mutex_lock(gs_mutex_t *mutex)
|
|||
{
|
||||
assert(mutex->depth == 0);
|
||||
mutex->depth = 1;
|
||||
atomic_store(&mutex->owner, thisThread);
|
||||
gs_atomic_store(&mutex->owner, thisThread);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// needs to be atomic because another thread can concurrently set it
|
||||
ownerThread = atomic_load(&mutex->owner);
|
||||
ownerThread = gs_atomic_load(&mutex->owner);
|
||||
if (ownerThread == thisThread)
|
||||
{
|
||||
// this thread already owns this lock
|
||||
|
@ -986,7 +986,7 @@ gs_mutex_lock(gs_mutex_t *mutex)
|
|||
AcquireSRWLockExclusive(&mutex->lock);
|
||||
assert(mutex->depth == 0);
|
||||
mutex->depth = 1;
|
||||
atomic_store(&mutex->owner, thisThread);
|
||||
gs_atomic_store(&mutex->owner, thisThread);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1000,12 +1000,12 @@ gs_mutex_trylock(gs_mutex_t *mutex)
|
|||
{
|
||||
assert(mutex->depth == 0);
|
||||
mutex->depth = 1;
|
||||
atomic_store(&mutex->owner, thisThread);
|
||||
gs_atomic_store(&mutex->owner, thisThread);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// needs to be atomic because another thread can concurrently set it
|
||||
ownerThread = atomic_load(&mutex->owner);
|
||||
ownerThread = gs_atomic_load(&mutex->owner);
|
||||
if (ownerThread == thisThread && mutex->attr == gs_mutex_attr_recursive)
|
||||
{
|
||||
// this thread already owns this lock and it's recursive
|
||||
|
@ -1029,7 +1029,7 @@ gs_mutex_unlock(gs_mutex_t *mutex)
|
|||
case gs_mutex_attr_recursive: {
|
||||
// return error if lock is not held by this thread
|
||||
DWORD thisThread = GetCurrentThreadId();
|
||||
DWORD ownerThread = atomic_load(&mutex->owner);
|
||||
DWORD ownerThread = gs_atomic_load(&mutex->owner);
|
||||
if (ownerThread != thisThread) {
|
||||
return EPERM;
|
||||
}
|
||||
|
@ -1047,7 +1047,7 @@ gs_mutex_unlock(gs_mutex_t *mutex)
|
|||
{
|
||||
assert(mutex->depth == 1);
|
||||
mutex->depth = 0;
|
||||
atomic_store(&mutex->owner, 0);
|
||||
gs_atomic_store(&mutex->owner, 0);
|
||||
ReleaseSRWLockExclusive(&mutex->lock);
|
||||
return 0;
|
||||
}
|
||||
|
@ -1061,7 +1061,7 @@ gs_cond_timedwait(gs_cond_t *cond, gs_mutex_t *mutex, DWORD millisecs)
|
|||
|
||||
assert(mutex->depth == 1);
|
||||
mutex->depth = 0;
|
||||
atomic_store(&mutex->owner, 0);
|
||||
gs_atomic_store(&mutex->owner, 0);
|
||||
|
||||
if (!SleepConditionVariableSRW(cond, &mutex->lock, millisecs, 0))
|
||||
{
|
||||
|
@ -1075,7 +1075,7 @@ gs_cond_timedwait(gs_cond_t *cond, gs_mutex_t *mutex, DWORD millisecs)
|
|||
|
||||
assert(mutex->depth == 0);
|
||||
mutex->depth = 1;
|
||||
atomic_store(&mutex->owner, GetCurrentThreadId());
|
||||
gs_atomic_store(&mutex->owner, GetCurrentThreadId());
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
|
0
Tests/base/NSKVOSupport/TestInfo
Normal file
0
Tests/base/NSKVOSupport/TestInfo
Normal file
87
Tests/base/NSKVOSupport/basic.m
Normal file
87
Tests/base/NSKVOSupport/basic.m
Normal file
|
@ -0,0 +1,87 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import "ObjectTesting.h"
|
||||
|
||||
#if defined(__OBJC2__)
|
||||
|
||||
@interface Foo : NSObject
|
||||
@property (assign) BOOL a;
|
||||
@property (assign) NSInteger b;
|
||||
@property (nonatomic, strong) NSString *c;
|
||||
@property (nonatomic, strong) NSArray *d;
|
||||
@end
|
||||
|
||||
@implementation Foo
|
||||
@end
|
||||
|
||||
@interface Observer : NSObject
|
||||
@property (assign) Foo *object;
|
||||
@property (assign) NSString *expectedKeyPath;
|
||||
@property (assign) NSInteger receivedCalls;
|
||||
|
||||
@end
|
||||
|
||||
@implementation Observer
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
self.receivedCalls = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
static char observerContext;
|
||||
|
||||
- (void)startObserving:(Foo *)target
|
||||
{
|
||||
self.object = target;
|
||||
[target addObserver:self forKeyPath:@"a" options:0 context:&observerContext];
|
||||
[target addObserver:self forKeyPath:@"b" options:0 context:&observerContext];
|
||||
[target addObserver:self forKeyPath:@"c" options:0 context:&observerContext];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary<NSString *, id> *)change
|
||||
context:(void *)context
|
||||
{
|
||||
PASS(context == &observerContext, "context");
|
||||
PASS(object == self.object, "object");
|
||||
PASS([keyPath isEqualToString:self.expectedKeyPath], "key path");
|
||||
self.receivedCalls++;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
[NSAutoreleasePool new];
|
||||
|
||||
Foo *foo = [Foo new];
|
||||
Observer *obs = [Observer new];
|
||||
[obs startObserving:foo];
|
||||
|
||||
obs.expectedKeyPath = @"a";
|
||||
foo.a = YES;
|
||||
PASS(obs.receivedCalls == 1, "received calls")
|
||||
|
||||
obs.expectedKeyPath = @"b";
|
||||
foo.b = 1;
|
||||
PASS(obs.receivedCalls == 2, "received calls")
|
||||
|
||||
obs.expectedKeyPath = @"c";
|
||||
foo.c = @"henlo";
|
||||
PASS(obs.receivedCalls == 3, "received calls")
|
||||
}
|
||||
|
||||
#else
|
||||
int
|
||||
main(int argc, const char *argv[])
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
1917
Tests/base/NSKVOSupport/general.m
Normal file
1917
Tests/base/NSKVOSupport/general.m
Normal file
File diff suppressed because it is too large
Load diff
1259
Tests/base/NSKVOSupport/kvoToMany.m
Normal file
1259
Tests/base/NSKVOSupport/kvoToMany.m
Normal file
File diff suppressed because it is too large
Load diff
148
Tests/base/NSKVOSupport/newoldvalues.m
Normal file
148
Tests/base/NSKVOSupport/newoldvalues.m
Normal file
|
@ -0,0 +1,148 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import "ObjectTesting.h"
|
||||
|
||||
#if defined(__OBJC2__)
|
||||
|
||||
@class Bar;
|
||||
|
||||
@interface Foo : NSObject
|
||||
@property (assign) Bar *globalBar;
|
||||
@property (assign) NSInteger a;
|
||||
@property (readonly) NSInteger b;
|
||||
@end
|
||||
|
||||
@interface Bar : NSObject
|
||||
@property (assign) NSInteger x;
|
||||
@property (strong, nonatomic) Foo *firstFoo;
|
||||
@property (strong, nonatomic) Foo *secondFoo;
|
||||
@end
|
||||
|
||||
@implementation Foo
|
||||
|
||||
+ (NSSet<NSString *> *)keyPathsForValuesAffectingB
|
||||
{
|
||||
return [NSSet setWithArray:@[ @"a", @"globalBar.x" ]];
|
||||
}
|
||||
|
||||
- (NSInteger)b
|
||||
{
|
||||
return self.a + self.globalBar.x;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation Bar
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
self.firstFoo = [Foo new];
|
||||
self.firstFoo.globalBar = self;
|
||||
self.secondFoo = [Foo new];
|
||||
self.secondFoo.globalBar = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface Observer : NSObject
|
||||
@property (assign) Foo *object;
|
||||
@property (assign) NSInteger expectedOldValue;
|
||||
@property (assign) NSInteger expectedNewValue;
|
||||
@property (assign) NSInteger receivedCalls;
|
||||
@end
|
||||
|
||||
@implementation Observer
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
self.receivedCalls = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
static char observerContext;
|
||||
|
||||
- (void)startObserving:(Foo *)target
|
||||
{
|
||||
self.object = target;
|
||||
[target
|
||||
addObserver:self
|
||||
forKeyPath:@"b"
|
||||
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
|
||||
context:&observerContext];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary<NSString *, id> *)change
|
||||
context:(void *)context
|
||||
{
|
||||
PASS(context == &observerContext, "context is correct");
|
||||
PASS(object == self.object, "object is correct");
|
||||
|
||||
id newValue = change[NSKeyValueChangeNewKey];
|
||||
id oldValue = change[NSKeyValueChangeOldKey];
|
||||
|
||||
PASS([oldValue integerValue] == self.expectedOldValue,
|
||||
"new value in change dict");
|
||||
PASS([newValue integerValue] == self.expectedNewValue,
|
||||
"old value in change dict");
|
||||
self.receivedCalls++;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
||||
|
||||
Bar *bar = [Bar new];
|
||||
bar.x = 0;
|
||||
bar.firstFoo.a = 1;
|
||||
bar.secondFoo.a = 2;
|
||||
|
||||
Observer *obs1 = [Observer new];
|
||||
Observer *obs2 = [Observer new];
|
||||
[obs1 startObserving:bar.firstFoo];
|
||||
[obs2 startObserving:bar.secondFoo];
|
||||
|
||||
obs1.expectedOldValue = 1;
|
||||
obs1.expectedNewValue = 2;
|
||||
obs2.expectedOldValue = 2;
|
||||
obs2.expectedNewValue = 3;
|
||||
bar.x = 1;
|
||||
PASS(obs1.receivedCalls == 1, "num observe calls");
|
||||
PASS(obs2.receivedCalls == 1, "num observe calls");
|
||||
|
||||
obs1.expectedOldValue = 2;
|
||||
obs1.expectedNewValue = 2;
|
||||
obs2.expectedOldValue = 3;
|
||||
obs2.expectedNewValue = 3;
|
||||
bar.x = 1;
|
||||
PASS(obs1.receivedCalls == 2, "num observe calls");
|
||||
PASS(obs2.receivedCalls == 2, "num observe calls");
|
||||
|
||||
obs1.expectedOldValue = 2;
|
||||
obs1.expectedNewValue = 3;
|
||||
bar.firstFoo.a = 2;
|
||||
PASS(obs1.receivedCalls == 3, "num observe calls");
|
||||
PASS(obs2.receivedCalls == 2, "num observe calls");
|
||||
|
||||
DESTROY(arp);
|
||||
}
|
||||
|
||||
#else
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue