From b6c52b98aa3cc959e345b689324c4deaf26238e6 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 4 Jun 2024 20:18:20 +0200 Subject: [PATCH 01/46] GSAtomic: Add prefix to macro definitions --- Source/GSAtomic.h | 22 +++++++++++----------- Source/GSPThread.h | 6 ++++++ Source/NSLock.m | 18 +++++++++--------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/Source/GSAtomic.h b/Source/GSAtomic.h index 81687182d..290e55994 100644 --- a/Source/GSAtomic.h +++ b/Source/GSAtomic.h @@ -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_ diff --git a/Source/GSPThread.h b/Source/GSPThread.h index dbec4e213..0850e15e9 100644 --- a/Source/GSPThread.h +++ b/Source/GSPThread.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. diff --git a/Source/NSLock.m b/Source/NSLock.m index df5ae502f..70985c7ed 100644 --- a/Source/NSLock.m +++ b/Source/NSLock.m @@ -952,12 +952,12 @@ + (id) allocWithZone: (NSZone*)z { 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 @@ + (id) allocWithZone: (NSZone*)z 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 @@ + (id) allocWithZone: (NSZone*)z { 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 @@ + (id) allocWithZone: (NSZone*)z 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 @@ + (id) allocWithZone: (NSZone*)z { 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 @@ + (id) allocWithZone: (NSZone*)z 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 @@ + (id) allocWithZone: (NSZone*)z assert(mutex->depth == 0); mutex->depth = 1; - atomic_store(&mutex->owner, GetCurrentThreadId()); + gs_atomic_store(&mutex->owner, GetCurrentThreadId()); return retVal; } From 28c985ad8c697167872f1d9793dc0a89b3aee8ae Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 4 Jun 2024 20:18:49 +0200 Subject: [PATCH 02/46] NSKVOSupport: Import --- Source/GNUmakefile | 7 +- Source/NSKVOSupport.m | 1219 +++++++++++++++++++++++++ Source/NSKVOSwizzling.mm | 614 +++++++++++++ Source/NSKeyValueObserving-Internal.h | 86 ++ Source/type_encoding_cases.h | 58 ++ 5 files changed, 1983 insertions(+), 1 deletion(-) create mode 100644 Source/NSKVOSupport.m create mode 100644 Source/NSKVOSwizzling.mm create mode 100644 Source/NSKeyValueObserving-Internal.h create mode 100644 Source/type_encoding_cases.h diff --git a/Source/GNUmakefile b/Source/GNUmakefile index d16c9a753..a0bda4ba2 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -255,7 +255,6 @@ NSJSONSerialization.m \ NSKeyedArchiver.m \ NSKeyedUnarchiver.m \ NSKeyValueCoding.m \ -NSKeyValueObserving.m \ NSLengthFormatter.m \ NSLinguisticTagger.m \ NSLocale.m \ @@ -357,8 +356,13 @@ NSXMLParser.m \ NSXPCConnection.m \ NSZone.m \ externs.m \ +NSKVOSupport.m \ objc-load.m +BASE_MMFILES = \ +NSKVOSwizzling.mm + + ifneq ($(GNUSTEP_TARGET_OS), mingw32) ifneq ($(GNUSTEP_TARGET_OS), mingw64) ifneq ($(GNUSTEP_TARGET_OS), windows) @@ -604,6 +608,7 @@ endif # The Objective-C source files to be compiled libgnustep-base_OBJC_FILES = $(GNU_MFILES) \ $(BASE_MFILES) +libgnustep-base_OBJCC_FILES = $(BASE_MMFILES) libgnustep-base_C_FILES = $(GNU_CFILES) # Extra DLL exports file diff --git a/Source/NSKVOSupport.m b/Source/NSKVOSupport.m new file mode 100644 index 000000000..ccf6d6031 --- /dev/null +++ b/Source/NSKVOSupport.m @@ -0,0 +1,1219 @@ +//****************************************************************************** +// +// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) 2006-2009 Johannes Fortmann, Cocotron Contributors et al. +// +// 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. +// +//****************************************************************************** + +#import "common.h" +#import "NSKeyValueObserving-Internal.h" +// #import "NSObject_NSKeyValueCoding-Internal.h" +#import +#import + +typedef void (^DispatchChangeBlock)(_NSKVOKeyObserver *); + +NSString * +_NSKVCSplitKeypath(NSString *keyPath, NSString *__autoreleasing *pRemainder) +{ + NSRange result = [keyPath rangeOfString:@"."]; + if (keyPath.length > 0 && result.location != NSNotFound) + { + *pRemainder = [keyPath substringFromIndex:result.location + 1]; + return [keyPath substringToIndex:result.location]; + } + *pRemainder = nil; + return keyPath; +} + +#pragma region Key Observer +@interface +_NSKVOKeyObserver () +{ + _Atomic(BOOL) _isRemoved; +} +@end + +@implementation _NSKVOKeyObserver +- (instancetype)initWithObject:(id)object + keypathObserver:(_NSKVOKeypathObserver *)keypathObserver + key:(NSString *)key + restOfKeypath:(NSString *)restOfKeypath + affectedObservers:(NSArray *)affectedObservers +{ + if (self = [super init]) + { + _object = object; + _keypathObserver = [keypathObserver retain]; + _key = [key copy]; + _restOfKeypath = [restOfKeypath copy]; + _affectedObservers = [affectedObservers copy]; + } + return self; +} + +- (void)dealloc +{ + [_keypathObserver release]; + [_key release]; + [_restOfKeypath release]; + [_dependentObservers release]; + [_restOfKeypathObserver release]; + [_affectedObservers release]; + [super dealloc]; +} + +- (BOOL)isRemoved +{ + return _isRemoved; +} + +- (void)setIsRemoved:(BOOL)removed +{ + _isRemoved = removed; +} +@end +#pragma endregion + +#pragma region Keypath Observer +@interface +_NSKVOKeypathObserver () +{ + _Atomic(int) _changeDepth; +} +@end + +@implementation _NSKVOKeypathObserver +- (instancetype)initWithObject:(id)object + observer:(id)observer + keyPath:(NSString *)keypath + options:(NSKeyValueObservingOptions)options + context:(void *)context +{ + if (self = [super init]) + { + _object = object; + _observer = observer; + _keypath = [keypath copy]; + _options = options; + _context = context; + } + return self; +} + +- (void)dealloc +{ + [_keypath release]; + [_pendingChange release]; + [super dealloc]; +} + +- (id)observer +{ + return _observer; +} + +- (BOOL)pushWillChange +{ + // return std::atomic_fetch_add(&_changeDepth, 1) == 0; + return atomic_fetch_add(&_changeDepth, 1) == 0; +} + +- (BOOL)popDidChange +{ + // return std::atomic_fetch_sub(&_changeDepth, 1) == 1; + return atomic_fetch_sub(&_changeDepth, 1) == 1; +} +@end +#pragma endregion + +#pragma region Object - level Observation Info +@implementation _NSKVOObservationInfo +- (instancetype)init +{ + if (self = [super init]) + { + _keyObserverMap = [[NSMutableDictionary alloc] initWithCapacity:1]; + GS_MUTEX_INIT(_lock); + } + return self; +} + +- (void)dealloc +{ + if (![self isEmpty]) + { + // We only want to flag for root observers: anything we created internally + // is fair game to be destroyed. + for (NSString *key in [_keyObserverMap keyEnumerator]) + { + for (_NSKVOKeyObserver *keyObserver in + [_keyObserverMap objectForKey:key]) + { + if (keyObserver.root) + { + [NSException + raise:NSInvalidArgumentException + format: + @"Object %@ deallocated with observers still registered.", + keyObserver.object]; + } + } + } + } + [_keyObserverMap release]; + [_existingDependentKeys release]; + + GS_MUTEX_DESTROY(_lock); + + [super dealloc]; +} + +- (void)pushDependencyStack +{ + GS_MUTEX_LOCK(_lock); + if (_dependencyDepth == 0) + { + _existingDependentKeys = [NSMutableSet new]; + } + ++_dependencyDepth; + GS_MUTEX_UNLOCK(_lock); +} + +- (BOOL)lockDependentKeypath:(NSString *)keypath +{ + GS_MUTEX_LOCK(_lock); + if ([_existingDependentKeys containsObject:keypath]) + { + GS_MUTEX_UNLOCK(_lock); + return NO; + } + [_existingDependentKeys addObject:keypath]; + GS_MUTEX_UNLOCK(_lock); + return YES; +} + +- (void)popDependencyStack +{ + GS_MUTEX_LOCK(_lock); + --_dependencyDepth; + if (_dependencyDepth == 0) + { + [_existingDependentKeys release]; + _existingDependentKeys = nil; + } + GS_MUTEX_UNLOCK(_lock); +} + +- (void)addObserver:(_NSKVOKeyObserver *)observer +{ + NSString *key = observer.key; + NSMutableArray *observersForKey = nil; + GS_MUTEX_LOCK(_lock); + observersForKey = [_keyObserverMap objectForKey:key]; + if (!observersForKey) + { + observersForKey = [NSMutableArray array]; + [_keyObserverMap setObject:observersForKey forKey:key]; + } + [observersForKey addObject:observer]; + GS_MUTEX_UNLOCK(_lock); +} + +- (void)removeObserver:(_NSKVOKeyObserver *)observer +{ + GS_MUTEX_LOCK(_lock); + NSString *key = observer.key; + NSMutableArray *observersForKey = [_keyObserverMap objectForKey:key]; + [observersForKey removeObject:observer]; + observer.isRemoved = true; + if (observersForKey.count == 0) + { + [_keyObserverMap removeObjectForKey:key]; + } + GS_MUTEX_UNLOCK(_lock); +} + +- (NSArray *)observersForKey:(NSString *)key +{ + GS_MUTEX_LOCK(_lock); + NSArray *result = [[[_keyObserverMap objectForKey:key] copy] autorelease]; + GS_MUTEX_UNLOCK(_lock); + return result; +} + +- (bool)isEmpty +{ + GS_MUTEX_LOCK(_lock); + BOOL result = _keyObserverMap.count == 0; + GS_MUTEX_UNLOCK(_lock); + return result; +} +@end + +static _NSKVOObservationInfo * +_createObservationInfoForObject(id object) +{ + _NSKVOObservationInfo *observationInfo = [_NSKVOObservationInfo new]; + [object setObservationInfo:observationInfo]; + [observationInfo release]; + return observationInfo; +} +#pragma endregion + +#pragma region Observer / Key Registration +static _NSKVOKeyObserver * +_addKeypathObserver(id object, NSString *keypath, + _NSKVOKeypathObserver *keyPathObserver, + NSArray *affectedObservers); +static void +_removeKeyObserver(_NSKVOKeyObserver *keyObserver); + +// Private helper that backs the default implementation of +// keyPathsForValuesAffectingValueForKey: Returns nil instead of constructing an +// empty set if no keyPaths are found Also used internally as a minor +// optimization, to avoid constructing an empty set when it is not needed +static NSSet * +_keyPathsForValuesAffectingValueForKey(Class self, NSString *key) +{ + // This function can be a KVO bottleneck, so it will prefer to use c string + // manipulation when safe + NSUInteger keyLength = [key length]; + if (keyLength > 0) + { + static const char *const sc_prefix = "keyPathsForValuesAffecting"; + static const size_t sc_bufferSize = 128; + static const size_t sc_prefixLength = strlen(sc_prefix); // 26 + + const char *rawKey; + size_t rawKeyLength; + SEL sel; + + // max length of a key that can guaranteed fit in the char buffer, + // even if UTF16->UTF8 conversion causes length to double, or a null + // terminator is needed + static const size_t sc_safeKeyLength + = (sc_bufferSize - sc_prefixLength) / 2 - 1; // 50 + + rawKey = [key UTF8String]; + rawKeyLength = strlen(rawKey); + + if (keyLength <= sc_safeKeyLength) + { + // fast path using c string manipulation, will cover most cases, as + // most keyPaths are short + char selectorName[sc_bufferSize] + = "keyPathsForValuesAffecting"; // 26 chars + selectorName[sc_prefixLength] = toupper(rawKey[0]); + // Copy the rest of the key, including the null terminator + memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], rawKeyLength); + sel = sel_registerName(selectorName); + } + else // Guaranteed path for long keyPaths + { + size_t keyLength; + size_t bufferSize; + char *selectorName; + + keyLength = strlen(rawKey); + bufferSize = sc_prefixLength + keyLength + 1; + selectorName = (char *) malloc(bufferSize); + memcpy(selectorName, sc_prefix, sc_prefixLength); + + selectorName[sc_prefixLength] = toupper(rawKey[0]); + // Copy the rest of the key, including the null terminator + memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], keyLength); + + sel = sel_registerName(selectorName); + free(selectorName); + } + + if ([self respondsToSelector:sel]) + { + return [self performSelector:sel]; + } + } + return nil; +} + +// Add all observers with declared dependencies on this one: +// * All keypaths that could trigger a change (keypaths for values affecting +// us). +// * The head of the remaining keypath. +static void +_addNestedObserversAndOptionallyDependents(_NSKVOKeyObserver *keyObserver, + bool dependents) +{ + id object = keyObserver.object; + NSString *key = keyObserver.key; + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + _NSKVOObservationInfo *observationInfo + = (__bridge _NSKVOObservationInfo *) [object observationInfo] + ?: _createObservationInfoForObject(object); + + // Aggregate all keys whose values will affect us. + if (dependents) + { + NSSet *valueInfluencingKeys + = _keyPathsForValuesAffectingValueForKey([object class], key); + if (valueInfluencingKeys.count > 0) + { + // affectedKeyObservers is the list of observers that must be notified + // of changes. If we have descendants, we have to add ourselves to the + // growing list of affected keys. If not, we must pass it along + // unmodified. (This is a minor optimization: we don't need to signal + // for our own reconstruction + // if we have no subpath observers.) + NSArray *affectedKeyObservers + = (keyObserver.restOfKeypath + ? ([keyObserver.affectedObservers + arrayByAddingObject:keyObserver] + ?: [NSArray arrayWithObject:keyObserver]) + : keyObserver.affectedObservers); + + [observationInfo pushDependencyStack]; + [observationInfo + lockDependentKeypath:keyObserver.key]; // Don't allow our own key to + // be recreated. + + NSMutableArray *dependentObservers = + [NSMutableArray arrayWithCapacity:[valueInfluencingKeys count]]; + for (NSString *dependentKeypath in valueInfluencingKeys) + { + if ([observationInfo lockDependentKeypath:dependentKeypath]) + { + _NSKVOKeyObserver *dependentObserver + = _addKeypathObserver(object, dependentKeypath, + keypathObserver, + affectedKeyObservers); + if (dependentObserver) + { + [dependentObservers addObject:dependentObserver]; + } + } + } + keyObserver.dependentObservers = dependentObservers; + + [observationInfo popDependencyStack]; + } + } + else + { + // Our dependents still exist, but their leaves have been pruned. Give + // them the same treatment as us: recreate their leaves. + for (_NSKVOKeyObserver *dependentKeyObserver in keyObserver + .dependentObservers) + { + _addNestedObserversAndOptionallyDependents(dependentKeyObserver, + false); + } + } + + // If restOfKeypath is non-nil, we have to chain on further observers. + if (keyObserver.restOfKeypath && !keyObserver.restOfKeypathObserver) + { + keyObserver.restOfKeypathObserver + = _addKeypathObserver([object valueForKey:key], + keyObserver.restOfKeypath, keypathObserver, + keyObserver.affectedObservers); + } + + // Back-propagation of changes. + // This is where a value-affecting key signals to its dependent that it should + // be reconstructed. + for (_NSKVOKeyObserver *affectedObserver in keyObserver.affectedObservers) + { + if (!affectedObserver.restOfKeypathObserver) + { + affectedObserver.restOfKeypathObserver + = _addKeypathObserver([affectedObserver.object + valueForKey:affectedObserver.key], + affectedObserver.restOfKeypath, + affectedObserver.keypathObserver, + affectedObserver.affectedObservers); + } + } +} + +static void +_addKeyObserver(_NSKVOKeyObserver *keyObserver) +{ + id object = keyObserver.object; + _NSKVOEnsureKeyWillNotify(object, keyObserver.key); + _NSKVOObservationInfo *observationInfo + = (__bridge _NSKVOObservationInfo *) [object observationInfo] + ?: _createObservationInfoForObject(object); + [observationInfo addObserver:keyObserver]; +} + +static _NSKVOKeyObserver * +_addKeypathObserver(id object, NSString *keypath, + _NSKVOKeypathObserver *keyPathObserver, + NSArray *affectedObservers) +{ + if (!object) + { + return nil; + } + NSString *key = nil; + NSString *restOfKeypath; + key = _NSKVCSplitKeypath(keypath, &restOfKeypath); + + _NSKVOKeyObserver *keyObserver = + [[[_NSKVOKeyObserver alloc] initWithObject:object + keypathObserver:keyPathObserver + key:key + restOfKeypath:restOfKeypath + affectedObservers:affectedObservers] autorelease]; + + if (object) + { + _addNestedObserversAndOptionallyDependents(keyObserver, true); + _addKeyObserver(keyObserver); + } + + return keyObserver; +} +#pragma endregion + +#pragma region Observer / Key Deregistration +static void +_removeNestedObserversAndOptionallyDependents(_NSKVOKeyObserver *keyObserver, + bool dependents) +{ + if (keyObserver.restOfKeypathObserver) + { + // Destroy the subpath observer recursively. + _removeKeyObserver(keyObserver.restOfKeypathObserver); + keyObserver.restOfKeypathObserver = nil; + } + + if (dependents) + { + // Destroy each observer whose value affects ours, recursively. + for (_NSKVOKeyObserver *dependentKeyObserver in keyObserver + .dependentObservers) + { + _removeKeyObserver(dependentKeyObserver); + } + + keyObserver.dependentObservers = nil; + } + else + { + // Our dependents must be kept alive but pruned. + for (_NSKVOKeyObserver *dependentKeyObserver in keyObserver + .dependentObservers) + { + _removeNestedObserversAndOptionallyDependents(dependentKeyObserver, + false); + } + } + + if (keyObserver.affectedObservers) + { + // Begin to reconstruct each observer that depends on our key's value + // (triggers in _addDependentAndNestedObservers). + for (_NSKVOKeyObserver *affectedObserver in keyObserver.affectedObservers) + { + _removeKeyObserver(affectedObserver.restOfKeypathObserver); + affectedObserver.restOfKeypathObserver = nil; + } + } +} + +static void +_removeKeyObserver(_NSKVOKeyObserver *keyObserver) +{ + if (!keyObserver) + { + return; + } + + _NSKVOObservationInfo *observationInfo + = (_NSKVOObservationInfo *) [keyObserver.object observationInfo]; + + [keyObserver retain]; + + _removeNestedObserversAndOptionallyDependents(keyObserver, true); + + // These are removed elsewhere; we're probably being cleared as a result of + // their deletion anyway. + keyObserver.affectedObservers = nil; + + [observationInfo removeObserver:keyObserver]; + + [keyObserver release]; +} + +static void +_removeKeypathObserver(id object, NSString *keypath, id observer, void *context) +{ + NSString *key = nil; + NSString *restOfKeypath; + key = _NSKVCSplitKeypath(keypath, &restOfKeypath); + + _NSKVOObservationInfo *observationInfo + = (_NSKVOObservationInfo *) [object observationInfo]; + for (_NSKVOKeyObserver *keyObserver in [observationInfo observersForKey:key]) + { + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + if (keypathObserver.observer == observer + && keypathObserver.object == object && + [keypathObserver.keypath isEqual:keypath] + && (!context || keypathObserver.context == context)) + { + _removeKeyObserver(keyObserver); + return; + } + } + + [NSException raise:NSInvalidArgumentException + format:@"Cannot remove observer %@ for keypath \"%@\" from %@ as " + @"it is not a registered observer.", + observer, keypath, object]; +} +#pragma endregion + +#pragma region KVO Core Implementation - NSObject category + +@implementation +NSObject (NSKeyValueObserving) +/** +@Status Interoperable +*/ +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + [NSException raise:NSInternalInconsistencyException + format:@"A key-value observation notification fired, but nobody " + @"responded to it: object %@, keypath %@, change %@.", + object, keyPath, change]; +} + +static void *s_kvoObservationInfoAssociationKey; // has no value; pointer used + // as an association key. + +/** +@Status Interoperable +*/ +- (void *)observationInfo +{ + return (__bridge void *) + objc_getAssociatedObject(self, &s_kvoObservationInfoAssociationKey); +} + +/** +@Status Interoperable +*/ +- (void)setObservationInfo:(void *)observationInfo +{ + objc_setAssociatedObject(self, &s_kvoObservationInfoAssociationKey, + (__bridge id) observationInfo, + OBJC_ASSOCIATION_RETAIN); +} + +/** +@Status Interoperable +*/ ++ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key +{ + if ([key length] > 0) + { + static const char *const sc_prefix = "automaticallyNotifiesObserversOf"; + static const size_t sc_prefixLength = strlen(sc_prefix); // 32 + const char *rawKey = [key UTF8String]; + size_t keyLength = strlen(rawKey); + size_t bufferSize = sc_prefixLength + keyLength + 1; + char *selectorName = (char *) malloc(bufferSize); + memcpy(selectorName, sc_prefix, sc_prefixLength); + selectorName[sc_prefixLength] = toupper(rawKey[0]); + memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], + keyLength); // copy keyLength characters to include terminating + // NULL from rawKey + SEL sel = sel_registerName(selectorName); + free(selectorName); + if ([self respondsToSelector:sel]) + { + return ((BOOL(*)(id, SEL)) objc_msgSend)(self, sel); + } + } + return YES; +} + +/** +@Status Interoperable +*/ ++ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key +{ + return _keyPathsForValuesAffectingValueForKey(self, key) ?: [NSSet set]; +} + +/** +@Status Interoperable +*/ +- (void)addObserver:(id)observer + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context +{ + _NSKVOKeypathObserver *keypathObserver = + [[[_NSKVOKeypathObserver alloc] initWithObject:self + observer:observer + keyPath:keyPath + options:options + context:context] autorelease]; + _NSKVOKeyObserver *rootObserver + = _addKeypathObserver(self, keyPath, keypathObserver, nil); + rootObserver.root = true; + + if ((options & NSKeyValueObservingOptionInitial)) + { + NSMutableDictionary *change = [NSMutableDictionary + dictionaryWithObjectsAndKeys:@(NSKeyValueChangeSetting), + NSKeyValueChangeKindKey, nil]; + + if ((options & NSKeyValueObservingOptionNew)) + { + id newValue = [self valueForKeyPath:keyPath] ?: [NSNull null]; + [change setObject:newValue forKey:NSKeyValueChangeNewKey]; + } + + [observer observeValueForKeyPath:keyPath + ofObject:self + change:change + context:context]; + } +} + +/** +@Status Interoperable +*/ +- (void)removeObserver:(id)observer + forKeyPath:(NSString *)keyPath + context:(void *)context +{ + _removeKeypathObserver(self, keyPath, observer, context); + _NSKVOObservationInfo *observationInfo + = (__bridge _NSKVOObservationInfo *) [self observationInfo]; + if ([observationInfo isEmpty]) + { + // TODO: was nullptr prior + [self setObservationInfo:nil]; + } +} + +/** +@Status Interoperable +*/ +- (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath +{ + [self removeObserver:observer forKeyPath:keyPath context:NULL]; +} + +// Reference platform does not provide the Set Mutation Kind in the changes +// dictionary, just shows which elements were inserted/removed/replaced +static inline NSKeyValueChange +_changeFromSetMutationKind(NSKeyValueSetMutationKind kind) +{ + switch (kind) + { + case NSKeyValueUnionSetMutation: + return NSKeyValueChangeInsertion; + case NSKeyValueMinusSetMutation: + case NSKeyValueIntersectSetMutation: + return NSKeyValueChangeRemoval; + default: + return NSKeyValueChangeReplacement; + } +} + +static inline id +_valueForPendingChangeAtIndexes(id notifyingObject, NSString *key, + NSString *keypath, id rootObject, + _NSKVOKeyObserver *keyObserver, + NSDictionary *pendingChange) +{ + id value = nil; + NSIndexSet *indexes = pendingChange[NSKeyValueChangeIndexesKey]; + if (indexes) + { + NSArray *collection = [notifyingObject valueForKey:key]; + NSString *restOfKeypath = keyObserver.restOfKeypath; + value = restOfKeypath.length > 0 + ? [collection valueForKeyPath:restOfKeypath] + : collection; + if ([value respondsToSelector:@selector(objectsAtIndexes:)]) + { + value = [value objectsAtIndexes:indexes]; + } + } + else + { + value = [rootObject valueForKeyPath:keypath]; + } + + return value ?: [NSNull null]; +} + +// void TFunc(_NSKVOKeyObserver* keyObserver); +inline static void +_dispatchWillChange(id notifyingObject, NSString *key, + DispatchChangeBlock block) +{ + _NSKVOObservationInfo *observationInfo + = (__bridge _NSKVOObservationInfo *) [notifyingObject observationInfo]; + for (_NSKVOKeyObserver *keyObserver in [observationInfo observersForKey:key]) + { + if (keyObserver.isRemoved) + { + continue; + } + + // Skip any keypaths that are in the process of changing. + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + if ([keypathObserver pushWillChange]) + { + // Call into the lambda function, which will do the actual set-up for + // pendingChanges + block(keyObserver); + + NSKeyValueObservingOptions options = keypathObserver.options; + if (options & NSKeyValueObservingOptionPrior) + { + NSMutableDictionary *change = keypathObserver.pendingChange; + [change setObject:@(YES) + forKey:NSKeyValueChangeNotificationIsPriorKey]; + [keypathObserver.observer + observeValueForKeyPath:keypathObserver.keypath + ofObject:keypathObserver.object + change:change + context:keypathObserver.context]; + [change + removeObjectForKey:NSKeyValueChangeNotificationIsPriorKey]; + } + } + + // This must happen regardless of whether we are currently notifying. + _removeNestedObserversAndOptionallyDependents(keyObserver, false); + } +} + +static void +_dispatchDidChange(id notifyingObject, NSString *key, DispatchChangeBlock block) +{ + _NSKVOObservationInfo *observationInfo + = (__bridge _NSKVOObservationInfo *) [notifyingObject observationInfo]; + NSArray<_NSKVOKeyObserver *> *observers = + [observationInfo observersForKey:key]; + for (_NSKVOKeyObserver *keyObserver in [observers reverseObjectEnumerator]) + { + if (keyObserver.isRemoved) + { + continue; + } + + // This must happen regardless of whether we are currently notifying. + _addNestedObserversAndOptionallyDependents(keyObserver, false); + + // Skip any keypaths that are in the process of changing. + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + if ([keypathObserver popDidChange]) + { + // Call into lambda, which will do set-up for finalizing changes + // dictionary + block(keyObserver); + + id observer = keypathObserver.observer; + NSString *keypath = keypathObserver.keypath; + id rootObject = keypathObserver.object; + NSMutableDictionary *change = keypathObserver.pendingChange; + void *context = keypathObserver.context; + [observer observeValueForKeyPath:keypath + ofObject:rootObject + change:change + context:context]; + keypathObserver.pendingChange = nil; + } + } +} + +/** +@Status Interoperable +*/ +- (void)willChangeValueForKey:(NSString *)key +{ + if ([self observationInfo]) + { + _dispatchWillChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { + NSMutableDictionary *change = + [NSMutableDictionary dictionaryWithObject:@(NSKeyValueChangeSetting) + forKey:NSKeyValueChangeKindKey]; + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueObservingOptions options = keypathObserver.options; + + if (options & NSKeyValueObservingOptionOld) + { + // For to-many mutations, we can't get the old values at indexes + // that have not yet been inserted. + id rootObject = keypathObserver.object; + NSString *keypath = keypathObserver.keypath; + id oldValue = [rootObject valueForKeyPath:keypath] ?: [NSNull null]; + change[NSKeyValueChangeOldKey] = oldValue; + } + + keypathObserver.pendingChange = change; + }); + } +} + +/** +@Status Interoperable +*/ +- (void)didChangeValueForKey:(NSString *)key +{ + if ([self observationInfo]) + { + _dispatchDidChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueObservingOptions options = keypathObserver.options; + NSMutableDictionary *change = keypathObserver.pendingChange; + if ((options & NSKeyValueObservingOptionNew) && + [change[NSKeyValueChangeKindKey] integerValue] + != NSKeyValueChangeRemoval) + { + NSString *keypath = keypathObserver.keypath; + id rootObject = keypathObserver.object; + id newValue = [rootObject valueForKeyPath:keypath] ?: [NSNull null]; + + change[NSKeyValueChangeNewKey] = newValue; + } + }); + } +} + +/** +@Status Interoperable +*/ +- (void)willChange:(NSKeyValueChange)changeKind + valuesAtIndexes:(NSIndexSet *)indexes + forKey:(NSString *)key +{ + __block NSKeyValueChange kind = changeKind; + if ([self observationInfo]) + { + _dispatchWillChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { + NSMutableDictionary *change = [NSMutableDictionary + dictionaryWithObjectsAndKeys:@(kind), NSKeyValueChangeKindKey, + indexes, NSKeyValueChangeIndexesKey, + nil]; + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueObservingOptions options = keypathObserver.options; + id rootObject = keypathObserver.object; + + // The reference platform does not support to-many mutations on nested + // keypaths. We have to treat them as to-one mutations to support + // aggregate functions. + if (kind != NSKeyValueChangeSetting + && keyObserver.restOfKeypathObserver) + { + // This only needs to be done in willChange because didChange + // derives from the existing changeset. + change[NSKeyValueChangeKindKey] = @(kind = NSKeyValueChangeSetting); + + // Make change Old/New values the entire collection rather than a + // to-many change with objectsAtIndexes: + [change removeObjectForKey:NSKeyValueChangeIndexesKey]; + } + + if ((options & NSKeyValueObservingOptionOld) + && kind != NSKeyValueChangeInsertion) + { + // For to-many mutations, we can't get the old values at indexes + // that have not yet been inserted. + NSString *keypath = keypathObserver.keypath; + change[NSKeyValueChangeOldKey] + = _valueForPendingChangeAtIndexes(self, key, keypath, rootObject, + keyObserver, change); + } + + keypathObserver.pendingChange = change; + }); + } +} + +/** +@Status Interoperable +*/ +- (void)didChange:(NSKeyValueChange)changeKind + valuesAtIndexes:(NSIndexSet *)indexes + forKey:(NSString *)key +{ + if ([self observationInfo]) + { + _dispatchDidChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueObservingOptions options = keypathObserver.options; + NSMutableDictionary *change = keypathObserver.pendingChange; + if ((options & NSKeyValueObservingOptionNew) && + [change[NSKeyValueChangeKindKey] integerValue] + != NSKeyValueChangeRemoval) + { + // For to-many mutations, we can't get the new values at indexes + // that have been deleted. + id rootObject = keypathObserver.object; + NSString *keypath = keypathObserver.keypath; + id newValue + = _valueForPendingChangeAtIndexes(self, key, keypath, rootObject, + keyObserver, change); + + change[NSKeyValueChangeNewKey] = newValue; + } + }); + } +} + +// Need to know the previous value for the set if we need to find the values +// added +static const NSString *_NSKeyValueChangeOldSetValue + = @"_NSKeyValueChangeOldSetValue"; + +/** + @Status Interoperable +*/ +- (void)willChangeValueForKey:(NSString *)key + withSetMutation:(NSKeyValueSetMutationKind)mutationKind + usingObjects:(NSSet *)objects +{ + if ([self observationInfo]) + { + NSKeyValueChange changeKind = _changeFromSetMutationKind(mutationKind); + _dispatchWillChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { + NSMutableDictionary *change = + [NSMutableDictionary dictionaryWithObject:@(changeKind) + forKey:NSKeyValueChangeKindKey]; + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueObservingOptions options = keypathObserver.options; + id rootObject = keypathObserver.object; + NSString *keypath = keypathObserver.keypath; + + NSSet *oldValues = [rootObject valueForKeyPath:keypath]; + if ((options & NSKeyValueObservingOptionOld) + && changeKind != NSKeyValueChangeInsertion) + { + // The old value should only contain values which are removed from + // the original dictionary + switch (mutationKind) + { + case NSKeyValueMinusSetMutation: + // The only objects which were removed are those both in + // oldValues and objects + change[NSKeyValueChangeOldKey] = + [oldValues objectsPassingTest:^(id obj, BOOL *stop) { + return [objects containsObject:obj]; + }]; + break; + case NSKeyValueIntersectSetMutation: + case NSKeyValueSetSetMutation: + default: + // The only objects which were removed are those in oldValues + // and NOT in objects + change[NSKeyValueChangeOldKey] = + [oldValues objectsPassingTest:^BOOL(id obj, BOOL *stop) { + return [objects member:obj] ? NO : YES; + }]; + break; + } + } + + if (options & NSKeyValueObservingOptionNew) + { + // Save old value in change dictionary for + // didChangeValueForKey:withSetMutation:usingObjects: to use for + // determining added objects Only needed if observer wants New + // value + change[_NSKeyValueChangeOldSetValue] = + [[oldValues copy] autorelease]; + } + + keypathObserver.pendingChange = change; + }); + } +} + +/** + @Status Interoperable +*/ +- (void)didChangeValueForKey:(NSString *)key + withSetMutation:(NSKeyValueSetMutationKind)mutationKind + usingObjects:(NSSet *)objects +{ + if ([self observationInfo]) + { + _dispatchDidChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueChange changeKind = _changeFromSetMutationKind(mutationKind); + NSKeyValueObservingOptions options = keypathObserver.options; + + if ((options & NSKeyValueObservingOptionNew) + && changeKind != NSKeyValueChangeRemoval) + { + // New values only exist for inserting or replacing, not removing + NSMutableDictionary *change = keypathObserver.pendingChange; + NSSet *oldValues = change[_NSKeyValueChangeOldSetValue]; + // The new value should only contain values which are added to the + // original set The only objects added are those in objects but + // NOT in oldValues + NSSet *newValue = + [objects objectsPassingTest:^BOOL(id obj, BOOL *stop) { + return [objects member:obj] ? NO : YES; + }]; + + change[NSKeyValueChangeNewKey] = newValue; + [change removeObjectForKey:_NSKeyValueChangeOldSetValue]; + } + }); + } +} +@end + +#pragma endregion + +#pragma region KVO Core Implementation - NSArray category + +@implementation +NSArray (NSKeyValueObserving) + +/** + @Status Interoperable +*/ +- (void)addObserver:(id)observer + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context +{ + NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); +} + +/** + @Status Interoperable +*/ +- (void)removeObserver:(id)observer + forKeyPath:(NSString *)keyPath + context:(void *)context +{ + NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); +} + +/** + @Status Interoperable +*/ +- (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath +{ + NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); +} + +/** + @Status Caveat + @Notes This is a convenience method, no performance gains for using this over + individual calls to removeObserver to affected objects +*/ +- (void)addObserver:(id)observer + toObjectsAtIndexes:(NSIndexSet *)indexes + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context +{ + [indexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { + [[self objectAtIndex:index] addObserver:observer + forKeyPath:keyPath + options:options + context:context]; + }]; +} + +/** + @Status Caveat + @Notes This is a convenience method, no performance gains for using this over + individual calls to removeObserver to affected objects +*/ +- (void)removeObserver:(id)observer + fromObjectsAtIndexes:(NSIndexSet *)indexes + forKeyPath:(NSString *)keyPath + context:(void *)context +{ + [indexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { + [[self objectAtIndex:index] removeObserver:observer + forKeyPath:keyPath + context:context]; + }]; +} + +/** + @Status Caveat + @Notes This is a convenience method, no performance gains for using this over + individual calls to removeObserver to affected objects +*/ +- (void)removeObserver:(NSObject *)observer + fromObjectsAtIndexes:(NSIndexSet *)indexes + forKeyPath:(NSString *)keyPath +{ + [self removeObserver:observer + fromObjectsAtIndexes:indexes + forKeyPath:keyPath + context:NULL]; +} + +@end + +#pragma endregion + +#pragma region KVO Core Implementation - NSSet category + +@implementation +NSSet (NSKeyValueObserving) + +/** + @Status Interoperable +*/ +- (void)addObserver:(id)observer + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context +{ + NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); +} + +/** + @Status Interoperable +*/ +- (void)removeObserver:(id)observer + forKeyPath:(NSString *)keyPath + context:(void *)context +{ + NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); +} + +/** + @Status Interoperable +*/ +- (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath +{ + NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); +} + +@end + +#pragma endregion diff --git a/Source/NSKVOSwizzling.mm b/Source/NSKVOSwizzling.mm new file mode 100644 index 000000000..ff708e476 --- /dev/null +++ b/Source/NSKVOSwizzling.mm @@ -0,0 +1,614 @@ +//****************************************************************************** +// +// 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. +// +//****************************************************************************** + +#include "Foundation/NSIndexSet.h" +#import "common.h" +#import "NSKeyValueObserving-Internal.h" +// #import "NSObject_NSKeyValueCoding-Internal.h" + +#import +#import +#import "type_encoding_cases.h" + +#import + +/* 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]; +} + +template +static inline void +_NSKVOSetDispatch(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]; +} + +template +static inline void +_NSKVOSetDispatchIndividual(id self, SEL _cmd, id obj) +{ + NSSet *set = [NSSet setWithObject:obj]; + + NSString *key = _keyForSelector(self, _cmd); + [self willChangeValueForKey:key withSetMutation:Kind usingObjects:set]; + + objc_super super{self, ABI_SUPER(self)}; + auto imp = (void (*)(id, SEL, id)) objc_msg_lookup_super(&super, _cmd); + imp(self, _cmd, obj); + + [self didChangeValueForKey:key withSetMutation:Kind usingObjects:set]; +} + +// - (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, id)) 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, id)) objc_msg_lookup_super(&super, _cmd); + imp(self, _cmd, key); + [self didChangeValueForKey:key]; +} +#pragma endregion + +template +static void +notifyingSetImpl(id self, SEL _cmd, T val) +{ + NSString *key = _keyForSelector(self, _cmd); + + [self willChangeValueForKey:key]; + + // This matches what clang generates for [super xxx]. + struct objc_super super + { + self, ABI_SUPER(self) + }; + auto imp = (void (*)(id, SEL, T)) objc_msg_lookup_super(&super, _cmd); + imp(self, _cmd, val); + + [self didChangeValueForKey:key]; +} + +#define KVO_SET_IMPL_CASE(type, name, capitalizedName, encodingChar) \ + case encodingChar: \ + newImpl = reinterpret_cast(¬ifyingSetImpl); \ + break; + +SEL +KVCSetterForPropertyName(NSObject *self, const char *key) +{ + SEL sel = nullptr; + 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 nullptr; +} + +// 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, + static_cast(valueSize)]; + } + newImpl = reinterpret_cast(¬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); + replaceAndAssociateWithKey( + object, addMany, key, + (IMP) _NSKVOSetDispatch); + replaceAndAssociateWithKey( + object, removeOne, key, + (IMP) _NSKVOSetDispatchIndividual); + replaceAndAssociateWithKey( + object, removeMany, key, + (IMP) _NSKVOSetDispatch); + replaceAndAssociateWithKey( + object, formatSelector(@"intersect%s:", rawKey), key, + (IMP) _NSKVOSetDispatch); + } +} + +static std::unique_ptr +mutableBufferFromString(NSString *string) +{ + NSUInteger lengthInBytes = string.length + 1; + std::unique_ptr rawKey = std::make_unique(lengthInBytes); + [string getCString:rawKey.get() + 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; + } + + std::unique_ptr rawKey{mutableBufferFromString(key)}; + rawKey[0] = toupper(rawKey[0]); + + @synchronized(object) + { + _NSKVOEnsureObjectIsKVOAware(object); + _NSKVOEnsureSimpleKeyWillNotify(object, key, rawKey.get()); + _NSKVOEnsureOrderedCollectionWillNotify(object, key, rawKey.get()); + _NSKVOEnsureUnorderedCollectionWillNotify(object, key, rawKey.get()); + } +} diff --git a/Source/NSKeyValueObserving-Internal.h b/Source/NSKeyValueObserving-Internal.h new file mode 100644 index 000000000..ead47bea0 --- /dev/null +++ b/Source/NSKeyValueObserving-Internal.h @@ -0,0 +1,86 @@ +//****************************************************************************** +// +// 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. +// +//****************************************************************************** + +#import +#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 *> + *_keyObserverMap; + NSInteger _dependencyDepth; + NSMutableSet *_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 diff --git a/Source/type_encoding_cases.h b/Source/type_encoding_cases.h new file mode 100644 index 000000000..81ee0c080 --- /dev/null +++ b/Source/type_encoding_cases.h @@ -0,0 +1,58 @@ +//****************************************************************************** +// +// 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. +// +//****************************************************************************** + +#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, Class, '#') \ + _APPLY_TYPE_MACRO(SEL, selector, Selector, ':') +#define OBJC_APPLY_POINTER_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \ + _APPLY_TYPE_MACRO(char*, cString, CString, '*') + +#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) From 36d0966a260f6f21909b7d9fd5394b34b87d6e6e Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 4 Jun 2024 20:19:13 +0200 Subject: [PATCH 03/46] NSKVOSupport: Add test cases --- Tests/base/NSKVOSupport/TestInfo | 0 Tests/base/NSKVOSupport/basic.m | 69 ++++++++++++++ Tests/base/NSKVOSupport/newoldvalues.m | 122 +++++++++++++++++++++++++ 3 files changed, 191 insertions(+) create mode 100644 Tests/base/NSKVOSupport/TestInfo create mode 100644 Tests/base/NSKVOSupport/basic.m create mode 100644 Tests/base/NSKVOSupport/newoldvalues.m diff --git a/Tests/base/NSKVOSupport/TestInfo b/Tests/base/NSKVOSupport/TestInfo new file mode 100644 index 000000000..e69de29bb diff --git a/Tests/base/NSKVOSupport/basic.m b/Tests/base/NSKVOSupport/basic.m new file mode 100644 index 000000000..78f5a8bce --- /dev/null +++ b/Tests/base/NSKVOSupport/basic.m @@ -0,0 +1,69 @@ +#import +#import "ObjectTesting.h" + +@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 *)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") +} \ No newline at end of file diff --git a/Tests/base/NSKVOSupport/newoldvalues.m b/Tests/base/NSKVOSupport/newoldvalues.m new file mode 100644 index 000000000..41243a68e --- /dev/null +++ b/Tests/base/NSKVOSupport/newoldvalues.m @@ -0,0 +1,122 @@ +#import +#import "ObjectTesting.h" + +@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*)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 *)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 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"); + +} \ No newline at end of file From d009453c9de4ec228d3ed2f151c123ef4b26bff6 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 4 Jun 2024 21:15:49 +0200 Subject: [PATCH 04/46] NSKVOSwizzling: Ugly C Rewrite --- Source/GNUmakefile | 7 +- .../{NSKVOSwizzling.mm => NSKVOSwizzling.m} | 151 ++++++++++-------- Source/type_encoding_cases.h | 56 +++---- 3 files changed, 114 insertions(+), 100 deletions(-) rename Source/{NSKVOSwizzling.mm => NSKVOSwizzling.m} (77%) diff --git a/Source/GNUmakefile b/Source/GNUmakefile index a0bda4ba2..4393fe4a4 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -255,6 +255,8 @@ NSJSONSerialization.m \ NSKeyedArchiver.m \ NSKeyedUnarchiver.m \ NSKeyValueCoding.m \ +NSKVOSupport.m \ +NSKVOSwizzling.m \ NSLengthFormatter.m \ NSLinguisticTagger.m \ NSLocale.m \ @@ -356,12 +358,8 @@ NSXMLParser.m \ NSXPCConnection.m \ NSZone.m \ externs.m \ -NSKVOSupport.m \ objc-load.m -BASE_MMFILES = \ -NSKVOSwizzling.mm - ifneq ($(GNUSTEP_TARGET_OS), mingw32) ifneq ($(GNUSTEP_TARGET_OS), mingw64) @@ -608,7 +606,6 @@ endif # The Objective-C source files to be compiled libgnustep-base_OBJC_FILES = $(GNU_MFILES) \ $(BASE_MFILES) -libgnustep-base_OBJCC_FILES = $(BASE_MMFILES) libgnustep-base_C_FILES = $(GNU_CFILES) # Extra DLL exports file diff --git a/Source/NSKVOSwizzling.mm b/Source/NSKVOSwizzling.m similarity index 77% rename from Source/NSKVOSwizzling.mm rename to Source/NSKVOSwizzling.m index ff708e476..83eaa3477 100644 --- a/Source/NSKVOSwizzling.mm +++ b/Source/NSKVOSwizzling.m @@ -14,7 +14,6 @@ // //****************************************************************************** -#include "Foundation/NSIndexSet.h" #import "common.h" #import "NSKeyValueObserving-Internal.h" // #import "NSObject_NSKeyValueCoding-Internal.h" @@ -23,8 +22,6 @@ #import #import "type_encoding_cases.h" -#import - /* These are defined by the ABI and the runtime. */ #define ABI_SUPER(obj) (((Class **) obj)[0][1]) #define ABI_ISA(obj) (((Class *) obj)[0]) @@ -326,37 +323,39 @@ the KVO machinery (if implemented using manual subclassing) would delete all forKey:key]; } -template -static inline void -_NSKVOSetDispatch(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]; -} - -template -static inline void -_NSKVOSetDispatchIndividual(id self, SEL _cmd, id obj) -{ - NSSet *set = [NSSet setWithObject:obj]; +#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]; \ + } - NSString *key = _keyForSelector(self, _cmd); - [self willChangeValueForKey: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]; \ + } - objc_super super{self, ABI_SUPER(self)}; - auto imp = (void (*)(id, SEL, id)) objc_msg_lookup_super(&super, _cmd); - imp(self, _cmd, obj); +GENERATE_NSKVOSetDispatchIndividual_IMPL(NSKeyValueUnionSetMutation); +GENERATE_NSKVOSetDispatchIndividual_IMPL(NSKeyValueMinusSetMutation); +GENERATE_NSKVOSetDispatchIndividual_IMPL(NSKeyValueIntersectSetMutation); - [self didChangeValueForKey:key withSetMutation:Kind usingObjects:set]; -} +GENERATE_NSKVOSetDispatch_IMPL(NSKeyValueUnionSetMutation); +GENERATE_NSKVOSetDispatch_IMPL(NSKeyValueMinusSetMutation); +GENERATE_NSKVOSetDispatch_IMPL(NSKeyValueIntersectSetMutation); // - (void)setObject:(id)object forKey:(NSString*)key static void @@ -365,7 +364,7 @@ the KVO machinery (if implemented using manual subclassing) would delete all [self willChangeValueForKey:key]; struct objc_super super = {self, ABI_SUPER(self)}; setObjectForKeyIMP imp - = (void (*)(id, SEL, id, id)) objc_msg_lookup_super(&super, _cmd); + = (void (*)(id, SEL, id, NSString *)) objc_msg_lookup_super(&super, _cmd); imp(self, _cmd, object, key); [self didChangeValueForKey:key]; } @@ -377,40 +376,54 @@ the KVO machinery (if implemented using manual subclassing) would delete all [self willChangeValueForKey:key]; struct objc_super super = {self, ABI_SUPER(self)}; removeObjectForKeyIMP imp - = (void (*)(id, SEL, id)) objc_msg_lookup_super(&super, _cmd); + = (void (*)(id, SEL, NSString *)) objc_msg_lookup_super(&super, _cmd); imp(self, _cmd, key); [self didChangeValueForKey:key]; } #pragma endregion -template -static void -notifyingSetImpl(id self, SEL _cmd, T val) -{ - NSString *key = _keyForSelector(self, _cmd); +#define GENERATE_NOTIFYING_SET_IMPL(funcName, type) \ + static void funcName(id self, SEL _cmd, type val) \ + { \ + NSString *key = _keyForSelector(self, _cmd); \ + [self willChangeValueForKey:key]; \ + struct objc_super super = {self, ABI_SUPER(self)}; \ + void (*imp)(id, SEL, type) \ + = (void (*)(id, SEL, type)) objc_msg_lookup_super(&super, _cmd); \ + imp(self, _cmd, val); \ + [self didChangeValueForKey:key]; \ + } - [self willChangeValueForKey:key]; +GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplDouble, double); +GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplFloat, float); - // This matches what clang generates for [super xxx]. - struct objc_super super - { - self, ABI_SUPER(self) - }; - auto imp = (void (*)(id, SEL, T)) objc_msg_lookup_super(&super, _cmd); - imp(self, _cmd, val); +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); - [self didChangeValueForKey:key]; -} +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 = reinterpret_cast(¬ifyingSetImpl); \ - break; + case encodingChar: { \ + newImpl = (IMP) (¬ifyingSetImpl##capitalizedName); \ + break; \ + } SEL KVCSetterForPropertyName(NSObject *self, const char *key) { - SEL sel = nullptr; + 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 @@ -430,7 +443,7 @@ the KVO machinery (if implemented using manual subclassing) would delete all return sel; } - return nullptr; + return nil; } // invariant: rawKey has already been capitalized @@ -463,9 +476,9 @@ the KVO machinery (if implemented using manual subclassing) would delete all format:@"Class %s key %@ has a value size of %u bytes, " @"and cannot currently be KVO compliant.", class_getName(object_getClass(object)), key, - static_cast(valueSize)]; + (unsigned int) (valueSize)]; } - newImpl = reinterpret_cast(¬ifyingVariadicSetImpl); + newImpl = (IMP) (¬ifyingVariadicSetImpl); break; } default: @@ -557,28 +570,28 @@ the KVO machinery (if implemented using manual subclassing) would delete all { replaceAndAssociateWithKey( object, addOne, key, - (IMP) _NSKVOSetDispatchIndividual); + (IMP) _NSKVOSetDispatchIndividual_NSKeyValueUnionSetMutation); replaceAndAssociateWithKey( object, addMany, key, - (IMP) _NSKVOSetDispatch); + (IMP) _NSKVOSetDispatch_NSKeyValueUnionSetMutation); replaceAndAssociateWithKey( object, removeOne, key, - (IMP) _NSKVOSetDispatchIndividual); + (IMP) _NSKVOSetDispatchIndividual_NSKeyValueMinusSetMutation); replaceAndAssociateWithKey( object, removeMany, key, - (IMP) _NSKVOSetDispatch); + (IMP) _NSKVOSetDispatch_NSKeyValueMinusSetMutation); replaceAndAssociateWithKey( object, formatSelector(@"intersect%s:", rawKey), key, - (IMP) _NSKVOSetDispatch); + (IMP) _NSKVOSetDispatch_NSKeyValueIntersectSetMutation); } } -static std::unique_ptr +char * mutableBufferFromString(NSString *string) { - NSUInteger lengthInBytes = string.length + 1; - std::unique_ptr rawKey = std::make_unique(lengthInBytes); - [string getCString:rawKey.get() + NSUInteger lengthInBytes = [string length] + 1; + char *rawKey = (char *) malloc(lengthInBytes); + [string getCString:rawKey maxLength:lengthInBytes encoding:NSASCIIStringEncoding]; return rawKey; @@ -601,14 +614,16 @@ the KVO machinery (if implemented using manual subclassing) would delete all return; } - std::unique_ptr rawKey{mutableBufferFromString(key)}; + char *rawKey = mutableBufferFromString(key); rawKey[0] = toupper(rawKey[0]); @synchronized(object) { _NSKVOEnsureObjectIsKVOAware(object); - _NSKVOEnsureSimpleKeyWillNotify(object, key, rawKey.get()); - _NSKVOEnsureOrderedCollectionWillNotify(object, key, rawKey.get()); - _NSKVOEnsureUnorderedCollectionWillNotify(object, key, rawKey.get()); + _NSKVOEnsureSimpleKeyWillNotify(object, key, rawKey); + _NSKVOEnsureOrderedCollectionWillNotify(object, key, rawKey); + _NSKVOEnsureUnorderedCollectionWillNotify(object, key, rawKey); } + + free(rawKey); } diff --git a/Source/type_encoding_cases.h b/Source/type_encoding_cases.h index 81ee0c080..f4db43849 100644 --- a/Source/type_encoding_cases.h +++ b/Source/type_encoding_cases.h @@ -18,41 +18,43 @@ // 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) +// #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') +#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) +#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, Class, '#') \ - _APPLY_TYPE_MACRO(SEL, selector, Selector, ':') -#define OBJC_APPLY_POINTER_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \ - _APPLY_TYPE_MACRO(char*, cString, CString, '*') +#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) +#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) From 43bc75646e419e98c3a9e4cb8211ce8f01b969ba Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 4 Jun 2024 23:04:01 +0200 Subject: [PATCH 05/46] NSKeyValueObserving: Use old implementation as fallback --- Source/GNUmakefile | 14 +- ...ueObserving-Internal.h => NSKVOInternal.h} | 54 +- Source/NSKVOSupport.m | 835 +++++++++--------- Source/NSKVOSwizzling.m | 175 ++-- Source/type_encoding_cases.h | 60 -- Tests/base/NSKVOSupport/basic.m | 96 +- Tests/base/NSKVOSupport/newoldvalues.m | 174 ++-- 7 files changed, 702 insertions(+), 706 deletions(-) rename Source/{NSKeyValueObserving-Internal.h => NSKVOInternal.h} (54%) delete mode 100644 Source/type_encoding_cases.h diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 4393fe4a4..7583d14b5 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -255,8 +255,6 @@ NSJSONSerialization.m \ NSKeyedArchiver.m \ NSKeyedUnarchiver.m \ NSKeyValueCoding.m \ -NSKVOSupport.m \ -NSKVOSwizzling.m \ NSLengthFormatter.m \ NSLinguisticTagger.m \ NSLocale.m \ @@ -360,6 +358,18 @@ 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 ($(OBJC2RUNTIME),1) + BASE_MFILES += \ + NSKVOSupport.m \ + NSKVOSwizzling.m +else + BASE_MFILES += \ + NSKeyValueObserving +endif + ifneq ($(GNUSTEP_TARGET_OS), mingw32) ifneq ($(GNUSTEP_TARGET_OS), mingw64) diff --git a/Source/NSKeyValueObserving-Internal.h b/Source/NSKVOInternal.h similarity index 54% rename from Source/NSKeyValueObserving-Internal.h rename to Source/NSKVOInternal.h index ead47bea0..1198969a1 100644 --- a/Source/NSKeyValueObserving-Internal.h +++ b/Source/NSKVOInternal.h @@ -14,6 +14,8 @@ // //****************************************************************************** +/* This Key Value Observing Implementation is tied to libobjc2 */ + #import #import "GSPThread.h" @@ -25,41 +27,41 @@ extern "C" { do \ { \ [NSException \ - raise:NSInvalidArgumentException \ - format:@"-[%s %s] is not supported. Key path: %@", \ - object_getClassName(self), sel_getName(_cmd), keyPath]; \ + 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; + 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; +@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; + 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 (nonatomic, assign) void *context; @property (atomic, retain) NSMutableDictionary *pendingChange; @end @@ -67,10 +69,10 @@ extern "C" { @interface _NSKVOObservationInfo : NSObject { NSMutableDictionary *> - *_keyObserverMap; - NSInteger _dependencyDepth; + *_keyObserverMap; + NSInteger _dependencyDepth; NSMutableSet *_existingDependentKeys; - gs_mutex_t _lock; + gs_mutex_t _lock; } - (instancetype)init; - (NSArray *)observersForKey:(NSString *)key; diff --git a/Source/NSKVOSupport.m b/Source/NSKVOSupport.m index ccf6d6031..423a256bd 100644 --- a/Source/NSKVOSupport.m +++ b/Source/NSKVOSupport.m @@ -15,9 +15,10 @@ // //****************************************************************************** +/* This Key Value Observing Implementation is tied to libobjc2 */ + #import "common.h" -#import "NSKeyValueObserving-Internal.h" -// #import "NSObject_NSKeyValueCoding-Internal.h" +#import "NSKVOInternal.h" #import #import @@ -46,10 +47,10 @@ @implementation _NSKVOKeyObserver - (instancetype)initWithObject:(id)object - keypathObserver:(_NSKVOKeypathObserver *)keypathObserver - key:(NSString *)key - restOfKeypath:(NSString *)restOfKeypath - affectedObservers:(NSArray *)affectedObservers + keypathObserver:(_NSKVOKeypathObserver *)keypathObserver + key:(NSString *)key + restOfKeypath:(NSString *)restOfKeypath + affectedObservers:(NSArray *)affectedObservers { if (self = [super init]) { @@ -95,10 +96,10 @@ - (void)setIsRemoved:(BOOL)removed @implementation _NSKVOKeypathObserver - (instancetype)initWithObject:(id)object - observer:(id)observer - keyPath:(NSString *)keypath - options:(NSKeyValueObservingOptions)options - context:(void *)context + observer:(id)observer + keyPath:(NSString *)keypath + options:(NSKeyValueObservingOptions)options + context:(void *)context { if (self = [super init]) { @@ -156,20 +157,20 @@ - (void)dealloc // We only want to flag for root observers: anything we created internally // is fair game to be destroyed. for (NSString *key in [_keyObserverMap keyEnumerator]) - { - for (_NSKVOKeyObserver *keyObserver in - [_keyObserverMap objectForKey:key]) - { - if (keyObserver.root) - { - [NSException - raise:NSInvalidArgumentException - format: - @"Object %@ deallocated with observers still registered.", - keyObserver.object]; - } - } - } + { + for (_NSKVOKeyObserver *keyObserver in + [_keyObserverMap objectForKey:key]) + { + if (keyObserver.root) + { + [NSException + raise:NSInvalidArgumentException + format: + @"Object %@ deallocated with observers still registered.", + keyObserver.object]; + } + } + } } [_keyObserverMap release]; [_existingDependentKeys release]; @@ -217,7 +218,7 @@ - (void)popDependencyStack - (void)addObserver:(_NSKVOKeyObserver *)observer { - NSString *key = observer.key; + NSString *key = observer.key; NSMutableArray *observersForKey = nil; GS_MUTEX_LOCK(_lock); observersForKey = [_keyObserverMap objectForKey:key]; @@ -233,7 +234,7 @@ - (void)addObserver:(_NSKVOKeyObserver *)observer - (void)removeObserver:(_NSKVOKeyObserver *)observer { GS_MUTEX_LOCK(_lock); - NSString *key = observer.key; + NSString *key = observer.key; NSMutableArray *observersForKey = [_keyObserverMap objectForKey:key]; [observersForKey removeObject:observer]; observer.isRemoved = true; @@ -274,8 +275,8 @@ - (bool)isEmpty #pragma region Observer / Key Registration static _NSKVOKeyObserver * _addKeypathObserver(id object, NSString *keypath, - _NSKVOKeypathObserver *keyPathObserver, - NSArray *affectedObservers); + _NSKVOKeypathObserver *keyPathObserver, + NSArray *affectedObservers); static void _removeKeyObserver(_NSKVOKeyObserver *keyObserver); @@ -296,52 +297,52 @@ - (bool)isEmpty static const size_t sc_prefixLength = strlen(sc_prefix); // 26 const char *rawKey; - size_t rawKeyLength; - SEL sel; + size_t rawKeyLength; + SEL sel; // max length of a key that can guaranteed fit in the char buffer, // even if UTF16->UTF8 conversion causes length to double, or a null // terminator is needed static const size_t sc_safeKeyLength - = (sc_bufferSize - sc_prefixLength) / 2 - 1; // 50 + = (sc_bufferSize - sc_prefixLength) / 2 - 1; // 50 rawKey = [key UTF8String]; rawKeyLength = strlen(rawKey); if (keyLength <= sc_safeKeyLength) - { - // fast path using c string manipulation, will cover most cases, as - // most keyPaths are short - char selectorName[sc_bufferSize] - = "keyPathsForValuesAffecting"; // 26 chars - selectorName[sc_prefixLength] = toupper(rawKey[0]); - // Copy the rest of the key, including the null terminator - memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], rawKeyLength); - sel = sel_registerName(selectorName); - } + { + // fast path using c string manipulation, will cover most cases, as + // most keyPaths are short + char selectorName[sc_bufferSize] + = "keyPathsForValuesAffecting"; // 26 chars + selectorName[sc_prefixLength] = toupper(rawKey[0]); + // Copy the rest of the key, including the null terminator + memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], rawKeyLength); + sel = sel_registerName(selectorName); + } else // Guaranteed path for long keyPaths - { - size_t keyLength; - size_t bufferSize; - char *selectorName; + { + size_t keyLength; + size_t bufferSize; + char *selectorName; - keyLength = strlen(rawKey); - bufferSize = sc_prefixLength + keyLength + 1; - selectorName = (char *) malloc(bufferSize); - memcpy(selectorName, sc_prefix, sc_prefixLength); + keyLength = strlen(rawKey); + bufferSize = sc_prefixLength + keyLength + 1; + selectorName = (char *) malloc(bufferSize); + memcpy(selectorName, sc_prefix, sc_prefixLength); - selectorName[sc_prefixLength] = toupper(rawKey[0]); - // Copy the rest of the key, including the null terminator - memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], keyLength); + selectorName[sc_prefixLength] = toupper(rawKey[0]); + // Copy the rest of the key, including the null terminator + memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], keyLength); - sel = sel_registerName(selectorName); - free(selectorName); - } + sel = sel_registerName(selectorName); + free(selectorName); + } if ([self respondsToSelector:sel]) - { - return [self performSelector:sel]; - } + { + return [self performSelector:sel]; + } } return nil; } @@ -352,80 +353,80 @@ - (bool)isEmpty // * The head of the remaining keypath. static void _addNestedObserversAndOptionallyDependents(_NSKVOKeyObserver *keyObserver, - bool dependents) + bool dependents) { - id object = keyObserver.object; - NSString *key = keyObserver.key; + id object = keyObserver.object; + NSString *key = keyObserver.key; _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; _NSKVOObservationInfo *observationInfo = (__bridge _NSKVOObservationInfo *) [object observationInfo] - ?: _createObservationInfoForObject(object); + ?: _createObservationInfoForObject(object); // Aggregate all keys whose values will affect us. if (dependents) { NSSet *valueInfluencingKeys - = _keyPathsForValuesAffectingValueForKey([object class], key); + = _keyPathsForValuesAffectingValueForKey([object class], key); if (valueInfluencingKeys.count > 0) - { - // affectedKeyObservers is the list of observers that must be notified - // of changes. If we have descendants, we have to add ourselves to the - // growing list of affected keys. If not, we must pass it along - // unmodified. (This is a minor optimization: we don't need to signal - // for our own reconstruction - // if we have no subpath observers.) - NSArray *affectedKeyObservers - = (keyObserver.restOfKeypath - ? ([keyObserver.affectedObservers - arrayByAddingObject:keyObserver] - ?: [NSArray arrayWithObject:keyObserver]) - : keyObserver.affectedObservers); - - [observationInfo pushDependencyStack]; - [observationInfo - lockDependentKeypath:keyObserver.key]; // Don't allow our own key to - // be recreated. - - NSMutableArray *dependentObservers = - [NSMutableArray arrayWithCapacity:[valueInfluencingKeys count]]; - for (NSString *dependentKeypath in valueInfluencingKeys) - { - if ([observationInfo lockDependentKeypath:dependentKeypath]) - { - _NSKVOKeyObserver *dependentObserver - = _addKeypathObserver(object, dependentKeypath, - keypathObserver, - affectedKeyObservers); - if (dependentObserver) - { - [dependentObservers addObject:dependentObserver]; - } - } - } - keyObserver.dependentObservers = dependentObservers; - - [observationInfo popDependencyStack]; - } + { + // affectedKeyObservers is the list of observers that must be notified + // of changes. If we have descendants, we have to add ourselves to the + // growing list of affected keys. If not, we must pass it along + // unmodified. (This is a minor optimization: we don't need to signal + // for our own reconstruction + // if we have no subpath observers.) + NSArray *affectedKeyObservers + = (keyObserver.restOfKeypath + ? ([keyObserver.affectedObservers + arrayByAddingObject:keyObserver] + ?: [NSArray arrayWithObject:keyObserver]) + : keyObserver.affectedObservers); + + [observationInfo pushDependencyStack]; + [observationInfo + lockDependentKeypath:keyObserver.key]; // Don't allow our own key to + // be recreated. + + NSMutableArray *dependentObservers = + [NSMutableArray arrayWithCapacity:[valueInfluencingKeys count]]; + for (NSString *dependentKeypath in valueInfluencingKeys) + { + if ([observationInfo lockDependentKeypath:dependentKeypath]) + { + _NSKVOKeyObserver *dependentObserver + = _addKeypathObserver(object, dependentKeypath, + keypathObserver, + affectedKeyObservers); + if (dependentObserver) + { + [dependentObservers addObject:dependentObserver]; + } + } + } + keyObserver.dependentObservers = dependentObservers; + + [observationInfo popDependencyStack]; + } } else { // Our dependents still exist, but their leaves have been pruned. Give // them the same treatment as us: recreate their leaves. for (_NSKVOKeyObserver *dependentKeyObserver in keyObserver - .dependentObservers) - { - _addNestedObserversAndOptionallyDependents(dependentKeyObserver, - false); - } + .dependentObservers) + { + _addNestedObserversAndOptionallyDependents(dependentKeyObserver, + false); + } } // If restOfKeypath is non-nil, we have to chain on further observers. if (keyObserver.restOfKeypath && !keyObserver.restOfKeypathObserver) { keyObserver.restOfKeypathObserver - = _addKeypathObserver([object valueForKey:key], - keyObserver.restOfKeypath, keypathObserver, - keyObserver.affectedObservers); + = _addKeypathObserver([object valueForKey:key], + keyObserver.restOfKeypath, keypathObserver, + keyObserver.affectedObservers); } // Back-propagation of changes. @@ -434,14 +435,14 @@ - (bool)isEmpty for (_NSKVOKeyObserver *affectedObserver in keyObserver.affectedObservers) { if (!affectedObserver.restOfKeypathObserver) - { - affectedObserver.restOfKeypathObserver - = _addKeypathObserver([affectedObserver.object - valueForKey:affectedObserver.key], - affectedObserver.restOfKeypath, - affectedObserver.keypathObserver, - affectedObserver.affectedObservers); - } + { + affectedObserver.restOfKeypathObserver + = _addKeypathObserver([affectedObserver.object + valueForKey:affectedObserver.key], + affectedObserver.restOfKeypath, + affectedObserver.keypathObserver, + affectedObserver.affectedObservers); + } } } @@ -452,14 +453,14 @@ - (bool)isEmpty _NSKVOEnsureKeyWillNotify(object, keyObserver.key); _NSKVOObservationInfo *observationInfo = (__bridge _NSKVOObservationInfo *) [object observationInfo] - ?: _createObservationInfoForObject(object); + ?: _createObservationInfoForObject(object); [observationInfo addObserver:keyObserver]; } static _NSKVOKeyObserver * _addKeypathObserver(id object, NSString *keypath, - _NSKVOKeypathObserver *keyPathObserver, - NSArray *affectedObservers) + _NSKVOKeypathObserver *keyPathObserver, + NSArray *affectedObservers) { if (!object) { @@ -471,10 +472,10 @@ - (bool)isEmpty _NSKVOKeyObserver *keyObserver = [[[_NSKVOKeyObserver alloc] initWithObject:object - keypathObserver:keyPathObserver - key:key - restOfKeypath:restOfKeypath - affectedObservers:affectedObservers] autorelease]; + keypathObserver:keyPathObserver + key:key + restOfKeypath:restOfKeypath + affectedObservers:affectedObservers] autorelease]; if (object) { @@ -489,7 +490,7 @@ - (bool)isEmpty #pragma region Observer / Key Deregistration static void _removeNestedObserversAndOptionallyDependents(_NSKVOKeyObserver *keyObserver, - bool dependents) + bool dependents) { if (keyObserver.restOfKeypathObserver) { @@ -502,10 +503,10 @@ - (bool)isEmpty { // Destroy each observer whose value affects ours, recursively. for (_NSKVOKeyObserver *dependentKeyObserver in keyObserver - .dependentObservers) - { - _removeKeyObserver(dependentKeyObserver); - } + .dependentObservers) + { + _removeKeyObserver(dependentKeyObserver); + } keyObserver.dependentObservers = nil; } @@ -513,11 +514,11 @@ - (bool)isEmpty { // Our dependents must be kept alive but pruned. for (_NSKVOKeyObserver *dependentKeyObserver in keyObserver - .dependentObservers) - { - _removeNestedObserversAndOptionallyDependents(dependentKeyObserver, - false); - } + .dependentObservers) + { + _removeNestedObserversAndOptionallyDependents(dependentKeyObserver, + false); + } } if (keyObserver.affectedObservers) @@ -525,10 +526,10 @@ - (bool)isEmpty // Begin to reconstruct each observer that depends on our key's value // (triggers in _addDependentAndNestedObservers). for (_NSKVOKeyObserver *affectedObserver in keyObserver.affectedObservers) - { - _removeKeyObserver(affectedObserver.restOfKeypathObserver); - affectedObserver.restOfKeypathObserver = nil; - } + { + _removeKeyObserver(affectedObserver.restOfKeypathObserver); + affectedObserver.restOfKeypathObserver = nil; + } } } @@ -569,19 +570,19 @@ - (bool)isEmpty { _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; if (keypathObserver.observer == observer - && keypathObserver.object == object && - [keypathObserver.keypath isEqual:keypath] - && (!context || keypathObserver.context == context)) - { - _removeKeyObserver(keyObserver); - return; - } + && keypathObserver.object == object && + [keypathObserver.keypath isEqual:keypath] + && (!context || keypathObserver.context == context)) + { + _removeKeyObserver(keyObserver); + return; + } } [NSException raise:NSInvalidArgumentException - format:@"Cannot remove observer %@ for keypath \"%@\" from %@ as " - @"it is not a registered observer.", - observer, keypath, object]; + format:@"Cannot remove observer %@ for keypath \"%@\" from %@ as " + @"it is not a registered observer.", + observer, keypath, object]; } #pragma endregion @@ -593,18 +594,18 @@ - (bool)isEmpty @Status Interoperable */ - (void)observeValueForKeyPath:(NSString *)keyPath - ofObject:(id)object - change:(NSDictionary *)change - context:(void *)context + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context { [NSException raise:NSInternalInconsistencyException - format:@"A key-value observation notification fired, but nobody " - @"responded to it: object %@, keypath %@, change %@.", - object, keyPath, change]; + format:@"A key-value observation notification fired, but nobody " + @"responded to it: object %@, keypath %@, change %@.", + object, keyPath, change]; } static void *s_kvoObservationInfoAssociationKey; // has no value; pointer used - // as an association key. + // as an association key. /** @Status Interoperable @@ -621,8 +622,8 @@ - (void *)observationInfo - (void)setObservationInfo:(void *)observationInfo { objc_setAssociatedObject(self, &s_kvoObservationInfoAssociationKey, - (__bridge id) observationInfo, - OBJC_ASSOCIATION_RETAIN); + (__bridge id) observationInfo, + OBJC_ASSOCIATION_RETAIN); } /** @@ -634,21 +635,21 @@ + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { static const char *const sc_prefix = "automaticallyNotifiesObserversOf"; static const size_t sc_prefixLength = strlen(sc_prefix); // 32 - const char *rawKey = [key UTF8String]; - size_t keyLength = strlen(rawKey); - size_t bufferSize = sc_prefixLength + keyLength + 1; - char *selectorName = (char *) malloc(bufferSize); + const char *rawKey = [key UTF8String]; + size_t keyLength = strlen(rawKey); + size_t bufferSize = sc_prefixLength + keyLength + 1; + char *selectorName = (char *) malloc(bufferSize); memcpy(selectorName, sc_prefix, sc_prefixLength); selectorName[sc_prefixLength] = toupper(rawKey[0]); memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], - keyLength); // copy keyLength characters to include terminating - // NULL from rawKey + keyLength); // copy keyLength characters to include terminating + // NULL from rawKey SEL sel = sel_registerName(selectorName); free(selectorName); if ([self respondsToSelector:sel]) - { - return ((BOOL(*)(id, SEL)) objc_msgSend)(self, sel); - } + { + return ((BOOL(*)(id, SEL)) objc_msgSend)(self, sel); + } } return YES; } @@ -665,16 +666,16 @@ + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key @Status Interoperable */ - (void)addObserver:(id)observer - forKeyPath:(NSString *)keyPath - options:(NSKeyValueObservingOptions)options - context:(void *)context + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context { _NSKVOKeypathObserver *keypathObserver = [[[_NSKVOKeypathObserver alloc] initWithObject:self - observer:observer - keyPath:keyPath - options:options - context:context] autorelease]; + observer:observer + keyPath:keyPath + options:options + context:context] autorelease]; _NSKVOKeyObserver *rootObserver = _addKeypathObserver(self, keyPath, keypathObserver, nil); rootObserver.root = true; @@ -682,19 +683,19 @@ - (void)addObserver:(id)observer if ((options & NSKeyValueObservingOptionInitial)) { NSMutableDictionary *change = [NSMutableDictionary - dictionaryWithObjectsAndKeys:@(NSKeyValueChangeSetting), - NSKeyValueChangeKindKey, nil]; + dictionaryWithObjectsAndKeys:@(NSKeyValueChangeSetting), + NSKeyValueChangeKindKey, nil]; if ((options & NSKeyValueObservingOptionNew)) - { - id newValue = [self valueForKeyPath:keyPath] ?: [NSNull null]; - [change setObject:newValue forKey:NSKeyValueChangeNewKey]; - } + { + id newValue = [self valueForKeyPath:keyPath] ?: [NSNull null]; + [change setObject:newValue forKey:NSKeyValueChangeNewKey]; + } [observer observeValueForKeyPath:keyPath - ofObject:self - change:change - context:context]; + ofObject:self + change:change + context:context]; } } @@ -702,8 +703,8 @@ - (void)addObserver:(id)observer @Status Interoperable */ - (void)removeObserver:(id)observer - forKeyPath:(NSString *)keyPath - context:(void *)context + forKeyPath:(NSString *)keyPath + context:(void *)context { _removeKeypathObserver(self, keyPath, observer, context); _NSKVOObservationInfo *observationInfo @@ -742,23 +743,23 @@ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath static inline id _valueForPendingChangeAtIndexes(id notifyingObject, NSString *key, - NSString *keypath, id rootObject, - _NSKVOKeyObserver *keyObserver, - NSDictionary *pendingChange) + NSString *keypath, id rootObject, + _NSKVOKeyObserver *keyObserver, + NSDictionary *pendingChange) { - id value = nil; + id value = nil; NSIndexSet *indexes = pendingChange[NSKeyValueChangeIndexesKey]; if (indexes) { NSArray *collection = [notifyingObject valueForKey:key]; NSString *restOfKeypath = keyObserver.restOfKeypath; value = restOfKeypath.length > 0 - ? [collection valueForKeyPath:restOfKeypath] - : collection; + ? [collection valueForKeyPath:restOfKeypath] + : collection; if ([value respondsToSelector:@selector(objectsAtIndexes:)]) - { - value = [value objectsAtIndexes:indexes]; - } + { + value = [value objectsAtIndexes:indexes]; + } } else { @@ -771,40 +772,40 @@ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath // void TFunc(_NSKVOKeyObserver* keyObserver); inline static void _dispatchWillChange(id notifyingObject, NSString *key, - DispatchChangeBlock block) + DispatchChangeBlock block) { _NSKVOObservationInfo *observationInfo = (__bridge _NSKVOObservationInfo *) [notifyingObject observationInfo]; for (_NSKVOKeyObserver *keyObserver in [observationInfo observersForKey:key]) { if (keyObserver.isRemoved) - { - continue; - } + { + continue; + } // Skip any keypaths that are in the process of changing. _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; if ([keypathObserver pushWillChange]) - { - // Call into the lambda function, which will do the actual set-up for - // pendingChanges - block(keyObserver); - - NSKeyValueObservingOptions options = keypathObserver.options; - if (options & NSKeyValueObservingOptionPrior) - { - NSMutableDictionary *change = keypathObserver.pendingChange; - [change setObject:@(YES) - forKey:NSKeyValueChangeNotificationIsPriorKey]; - [keypathObserver.observer - observeValueForKeyPath:keypathObserver.keypath - ofObject:keypathObserver.object - change:change - context:keypathObserver.context]; - [change - removeObjectForKey:NSKeyValueChangeNotificationIsPriorKey]; - } - } + { + // Call into the lambda function, which will do the actual set-up for + // pendingChanges + block(keyObserver); + + NSKeyValueObservingOptions options = keypathObserver.options; + if (options & NSKeyValueObservingOptionPrior) + { + NSMutableDictionary *change = keypathObserver.pendingChange; + [change setObject:@(YES) + forKey:NSKeyValueChangeNotificationIsPriorKey]; + [keypathObserver.observer + observeValueForKeyPath:keypathObserver.keypath + ofObject:keypathObserver.object + change:change + context:keypathObserver.context]; + [change + removeObjectForKey:NSKeyValueChangeNotificationIsPriorKey]; + } + } // This must happen regardless of whether we are currently notifying. _removeNestedObserversAndOptionallyDependents(keyObserver, false); @@ -821,9 +822,9 @@ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath for (_NSKVOKeyObserver *keyObserver in [observers reverseObjectEnumerator]) { if (keyObserver.isRemoved) - { - continue; - } + { + continue; + } // This must happen regardless of whether we are currently notifying. _addNestedObserversAndOptionallyDependents(keyObserver, false); @@ -831,22 +832,22 @@ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath // Skip any keypaths that are in the process of changing. _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; if ([keypathObserver popDidChange]) - { - // Call into lambda, which will do set-up for finalizing changes - // dictionary - block(keyObserver); - - id observer = keypathObserver.observer; - NSString *keypath = keypathObserver.keypath; - id rootObject = keypathObserver.object; - NSMutableDictionary *change = keypathObserver.pendingChange; - void *context = keypathObserver.context; - [observer observeValueForKeyPath:keypath - ofObject:rootObject - change:change - context:context]; - keypathObserver.pendingChange = nil; - } + { + // Call into lambda, which will do set-up for finalizing changes + // dictionary + block(keyObserver); + + id observer = keypathObserver.observer; + NSString *keypath = keypathObserver.keypath; + id rootObject = keypathObserver.object; + NSMutableDictionary *change = keypathObserver.pendingChange; + void *context = keypathObserver.context; + [observer observeValueForKeyPath:keypath + ofObject:rootObject + change:change + context:context]; + keypathObserver.pendingChange = nil; + } } } @@ -858,23 +859,23 @@ - (void)willChangeValueForKey:(NSString *)key if ([self observationInfo]) { _dispatchWillChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { - NSMutableDictionary *change = - [NSMutableDictionary dictionaryWithObject:@(NSKeyValueChangeSetting) - forKey:NSKeyValueChangeKindKey]; - _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; - NSKeyValueObservingOptions options = keypathObserver.options; - - if (options & NSKeyValueObservingOptionOld) - { - // For to-many mutations, we can't get the old values at indexes - // that have not yet been inserted. - id rootObject = keypathObserver.object; - NSString *keypath = keypathObserver.keypath; - id oldValue = [rootObject valueForKeyPath:keypath] ?: [NSNull null]; - change[NSKeyValueChangeOldKey] = oldValue; - } - - keypathObserver.pendingChange = change; + NSMutableDictionary *change = + [NSMutableDictionary dictionaryWithObject:@(NSKeyValueChangeSetting) + forKey:NSKeyValueChangeKindKey]; + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueObservingOptions options = keypathObserver.options; + + if (options & NSKeyValueObservingOptionOld) + { + // For to-many mutations, we can't get the old values at indexes + // that have not yet been inserted. + id rootObject = keypathObserver.object; + NSString *keypath = keypathObserver.keypath; + id oldValue = [rootObject valueForKeyPath:keypath] ?: [NSNull null]; + change[NSKeyValueChangeOldKey] = oldValue; + } + + keypathObserver.pendingChange = change; }); } } @@ -887,19 +888,19 @@ - (void)didChangeValueForKey:(NSString *)key if ([self observationInfo]) { _dispatchDidChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { - _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; - NSKeyValueObservingOptions options = keypathObserver.options; - NSMutableDictionary *change = keypathObserver.pendingChange; - if ((options & NSKeyValueObservingOptionNew) && - [change[NSKeyValueChangeKindKey] integerValue] - != NSKeyValueChangeRemoval) - { - NSString *keypath = keypathObserver.keypath; - id rootObject = keypathObserver.object; - id newValue = [rootObject valueForKeyPath:keypath] ?: [NSNull null]; - - change[NSKeyValueChangeNewKey] = newValue; - } + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueObservingOptions options = keypathObserver.options; + NSMutableDictionary *change = keypathObserver.pendingChange; + if ((options & NSKeyValueObservingOptionNew) && + [change[NSKeyValueChangeKindKey] integerValue] + != NSKeyValueChangeRemoval) + { + NSString *keypath = keypathObserver.keypath; + id rootObject = keypathObserver.object; + id newValue = [rootObject valueForKeyPath:keypath] ?: [NSNull null]; + + change[NSKeyValueChangeNewKey] = newValue; + } }); } } @@ -909,47 +910,47 @@ - (void)didChangeValueForKey:(NSString *)key */ - (void)willChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes - forKey:(NSString *)key + forKey:(NSString *)key { __block NSKeyValueChange kind = changeKind; if ([self observationInfo]) { _dispatchWillChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { - NSMutableDictionary *change = [NSMutableDictionary - dictionaryWithObjectsAndKeys:@(kind), NSKeyValueChangeKindKey, - indexes, NSKeyValueChangeIndexesKey, - nil]; - _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; - NSKeyValueObservingOptions options = keypathObserver.options; - id rootObject = keypathObserver.object; - - // The reference platform does not support to-many mutations on nested - // keypaths. We have to treat them as to-one mutations to support - // aggregate functions. - if (kind != NSKeyValueChangeSetting - && keyObserver.restOfKeypathObserver) - { - // This only needs to be done in willChange because didChange - // derives from the existing changeset. - change[NSKeyValueChangeKindKey] = @(kind = NSKeyValueChangeSetting); - - // Make change Old/New values the entire collection rather than a - // to-many change with objectsAtIndexes: - [change removeObjectForKey:NSKeyValueChangeIndexesKey]; - } - - if ((options & NSKeyValueObservingOptionOld) - && kind != NSKeyValueChangeInsertion) - { - // For to-many mutations, we can't get the old values at indexes - // that have not yet been inserted. - NSString *keypath = keypathObserver.keypath; - change[NSKeyValueChangeOldKey] - = _valueForPendingChangeAtIndexes(self, key, keypath, rootObject, - keyObserver, change); - } - - keypathObserver.pendingChange = change; + NSMutableDictionary *change = [NSMutableDictionary + dictionaryWithObjectsAndKeys:@(kind), NSKeyValueChangeKindKey, + indexes, NSKeyValueChangeIndexesKey, + nil]; + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueObservingOptions options = keypathObserver.options; + id rootObject = keypathObserver.object; + + // The reference platform does not support to-many mutations on nested + // keypaths. We have to treat them as to-one mutations to support + // aggregate functions. + if (kind != NSKeyValueChangeSetting + && keyObserver.restOfKeypathObserver) + { + // This only needs to be done in willChange because didChange + // derives from the existing changeset. + change[NSKeyValueChangeKindKey] = @(kind = NSKeyValueChangeSetting); + + // Make change Old/New values the entire collection rather than a + // to-many change with objectsAtIndexes: + [change removeObjectForKey:NSKeyValueChangeIndexesKey]; + } + + if ((options & NSKeyValueObservingOptionOld) + && kind != NSKeyValueChangeInsertion) + { + // For to-many mutations, we can't get the old values at indexes + // that have not yet been inserted. + NSString *keypath = keypathObserver.keypath; + change[NSKeyValueChangeOldKey] + = _valueForPendingChangeAtIndexes(self, key, keypath, rootObject, + keyObserver, change); + } + + keypathObserver.pendingChange = change; }); } } @@ -959,28 +960,28 @@ - (void)willChange:(NSKeyValueChange)changeKind */ - (void)didChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes - forKey:(NSString *)key + forKey:(NSString *)key { if ([self observationInfo]) { _dispatchDidChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { - _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; - NSKeyValueObservingOptions options = keypathObserver.options; - NSMutableDictionary *change = keypathObserver.pendingChange; - if ((options & NSKeyValueObservingOptionNew) && - [change[NSKeyValueChangeKindKey] integerValue] - != NSKeyValueChangeRemoval) - { - // For to-many mutations, we can't get the new values at indexes - // that have been deleted. - id rootObject = keypathObserver.object; - NSString *keypath = keypathObserver.keypath; - id newValue - = _valueForPendingChangeAtIndexes(self, key, keypath, rootObject, - keyObserver, change); - - change[NSKeyValueChangeNewKey] = newValue; - } + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueObservingOptions options = keypathObserver.options; + NSMutableDictionary *change = keypathObserver.pendingChange; + if ((options & NSKeyValueObservingOptionNew) && + [change[NSKeyValueChangeKindKey] integerValue] + != NSKeyValueChangeRemoval) + { + // For to-many mutations, we can't get the new values at indexes + // that have been deleted. + id rootObject = keypathObserver.object; + NSString *keypath = keypathObserver.keypath; + id newValue + = _valueForPendingChangeAtIndexes(self, key, keypath, rootObject, + keyObserver, change); + + change[NSKeyValueChangeNewKey] = newValue; + } }); } } @@ -994,61 +995,61 @@ - (void)didChange:(NSKeyValueChange)changeKind @Status Interoperable */ - (void)willChangeValueForKey:(NSString *)key - withSetMutation:(NSKeyValueSetMutationKind)mutationKind - usingObjects:(NSSet *)objects + withSetMutation:(NSKeyValueSetMutationKind)mutationKind + usingObjects:(NSSet *)objects { if ([self observationInfo]) { NSKeyValueChange changeKind = _changeFromSetMutationKind(mutationKind); _dispatchWillChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { - NSMutableDictionary *change = - [NSMutableDictionary dictionaryWithObject:@(changeKind) - forKey:NSKeyValueChangeKindKey]; - _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; - NSKeyValueObservingOptions options = keypathObserver.options; - id rootObject = keypathObserver.object; - NSString *keypath = keypathObserver.keypath; - - NSSet *oldValues = [rootObject valueForKeyPath:keypath]; - if ((options & NSKeyValueObservingOptionOld) - && changeKind != NSKeyValueChangeInsertion) - { - // The old value should only contain values which are removed from - // the original dictionary - switch (mutationKind) - { - case NSKeyValueMinusSetMutation: - // The only objects which were removed are those both in - // oldValues and objects - change[NSKeyValueChangeOldKey] = - [oldValues objectsPassingTest:^(id obj, BOOL *stop) { - return [objects containsObject:obj]; - }]; - break; - case NSKeyValueIntersectSetMutation: - case NSKeyValueSetSetMutation: - default: - // The only objects which were removed are those in oldValues - // and NOT in objects - change[NSKeyValueChangeOldKey] = - [oldValues objectsPassingTest:^BOOL(id obj, BOOL *stop) { - return [objects member:obj] ? NO : YES; - }]; - break; - } - } - - if (options & NSKeyValueObservingOptionNew) - { - // Save old value in change dictionary for - // didChangeValueForKey:withSetMutation:usingObjects: to use for - // determining added objects Only needed if observer wants New - // value - change[_NSKeyValueChangeOldSetValue] = - [[oldValues copy] autorelease]; - } - - keypathObserver.pendingChange = change; + NSMutableDictionary *change = + [NSMutableDictionary dictionaryWithObject:@(changeKind) + forKey:NSKeyValueChangeKindKey]; + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueObservingOptions options = keypathObserver.options; + id rootObject = keypathObserver.object; + NSString *keypath = keypathObserver.keypath; + + NSSet *oldValues = [rootObject valueForKeyPath:keypath]; + if ((options & NSKeyValueObservingOptionOld) + && changeKind != NSKeyValueChangeInsertion) + { + // The old value should only contain values which are removed from + // the original dictionary + switch (mutationKind) + { + case NSKeyValueMinusSetMutation: + // The only objects which were removed are those both in + // oldValues and objects + change[NSKeyValueChangeOldKey] = + [oldValues objectsPassingTest:^(id obj, BOOL *stop) { + return [objects containsObject:obj]; + }]; + break; + case NSKeyValueIntersectSetMutation: + case NSKeyValueSetSetMutation: + default: + // The only objects which were removed are those in oldValues + // and NOT in objects + change[NSKeyValueChangeOldKey] = + [oldValues objectsPassingTest:^BOOL(id obj, BOOL *stop) { + return [objects member:obj] ? NO : YES; + }]; + break; + } + } + + if (options & NSKeyValueObservingOptionNew) + { + // Save old value in change dictionary for + // didChangeValueForKey:withSetMutation:usingObjects: to use for + // determining added objects Only needed if observer wants New + // value + change[_NSKeyValueChangeOldSetValue] = + [[oldValues copy] autorelease]; + } + + keypathObserver.pendingChange = change; }); } } @@ -1057,33 +1058,33 @@ - (void)willChangeValueForKey:(NSString *)key @Status Interoperable */ - (void)didChangeValueForKey:(NSString *)key - withSetMutation:(NSKeyValueSetMutationKind)mutationKind - usingObjects:(NSSet *)objects + withSetMutation:(NSKeyValueSetMutationKind)mutationKind + usingObjects:(NSSet *)objects { if ([self observationInfo]) { _dispatchDidChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { - _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; - NSKeyValueChange changeKind = _changeFromSetMutationKind(mutationKind); - NSKeyValueObservingOptions options = keypathObserver.options; - - if ((options & NSKeyValueObservingOptionNew) - && changeKind != NSKeyValueChangeRemoval) - { - // New values only exist for inserting or replacing, not removing - NSMutableDictionary *change = keypathObserver.pendingChange; - NSSet *oldValues = change[_NSKeyValueChangeOldSetValue]; - // The new value should only contain values which are added to the - // original set The only objects added are those in objects but - // NOT in oldValues - NSSet *newValue = - [objects objectsPassingTest:^BOOL(id obj, BOOL *stop) { - return [objects member:obj] ? NO : YES; - }]; - - change[NSKeyValueChangeNewKey] = newValue; - [change removeObjectForKey:_NSKeyValueChangeOldSetValue]; - } + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueChange changeKind = _changeFromSetMutationKind(mutationKind); + NSKeyValueObservingOptions options = keypathObserver.options; + + if ((options & NSKeyValueObservingOptionNew) + && changeKind != NSKeyValueChangeRemoval) + { + // New values only exist for inserting or replacing, not removing + NSMutableDictionary *change = keypathObserver.pendingChange; + NSSet *oldValues = change[_NSKeyValueChangeOldSetValue]; + // The new value should only contain values which are added to the + // original set The only objects added are those in objects but + // NOT in oldValues + NSSet *newValue = + [objects objectsPassingTest:^BOOL(id obj, BOOL *stop) { + return [objects member:obj] ? NO : YES; + }]; + + change[NSKeyValueChangeNewKey] = newValue; + [change removeObjectForKey:_NSKeyValueChangeOldSetValue]; + } }); } } @@ -1100,9 +1101,9 @@ - (void)didChangeValueForKey:(NSString *)key @Status Interoperable */ - (void)addObserver:(id)observer - forKeyPath:(NSString *)keyPath - options:(NSKeyValueObservingOptions)options - context:(void *)context + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context { NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } @@ -1111,8 +1112,8 @@ - (void)addObserver:(id)observer @Status Interoperable */ - (void)removeObserver:(id)observer - forKeyPath:(NSString *)keyPath - context:(void *)context + forKeyPath:(NSString *)keyPath + context:(void *)context { NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } @@ -1132,15 +1133,15 @@ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath */ - (void)addObserver:(id)observer toObjectsAtIndexes:(NSIndexSet *)indexes - forKeyPath:(NSString *)keyPath - options:(NSKeyValueObservingOptions)options - context:(void *)context + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context { [indexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { [[self objectAtIndex:index] addObserver:observer - forKeyPath:keyPath - options:options - context:context]; + forKeyPath:keyPath + options:options + context:context]; }]; } @@ -1151,13 +1152,13 @@ - (void)addObserver:(id)observer */ - (void)removeObserver:(id)observer fromObjectsAtIndexes:(NSIndexSet *)indexes - forKeyPath:(NSString *)keyPath - context:(void *)context + forKeyPath:(NSString *)keyPath + context:(void *)context { [indexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { [[self objectAtIndex:index] removeObserver:observer - forKeyPath:keyPath - context:context]; + forKeyPath:keyPath + context:context]; }]; } @@ -1168,12 +1169,12 @@ - (void)removeObserver:(id)observer */ - (void)removeObserver:(NSObject *)observer fromObjectsAtIndexes:(NSIndexSet *)indexes - forKeyPath:(NSString *)keyPath + forKeyPath:(NSString *)keyPath { [self removeObserver:observer fromObjectsAtIndexes:indexes - forKeyPath:keyPath - context:NULL]; + forKeyPath:keyPath + context:NULL]; } @end @@ -1189,9 +1190,9 @@ - (void)removeObserver:(NSObject *)observer @Status Interoperable */ - (void)addObserver:(id)observer - forKeyPath:(NSString *)keyPath - options:(NSKeyValueObservingOptions)options - context:(void *)context + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context { NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } @@ -1200,8 +1201,8 @@ - (void)addObserver:(id)observer @Status Interoperable */ - (void)removeObserver:(id)observer - forKeyPath:(NSString *)keyPath - context:(void *)context + forKeyPath:(NSString *)keyPath + context:(void *)context { NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } diff --git a/Source/NSKVOSwizzling.m b/Source/NSKVOSwizzling.m index 83eaa3477..180fcc901 100644 --- a/Source/NSKVOSwizzling.m +++ b/Source/NSKVOSwizzling.m @@ -14,13 +14,14 @@ // //****************************************************************************** +/* This Key Value Observing Implementation is tied to libobjc2 */ + #import "common.h" -#import "NSKeyValueObserving-Internal.h" -// #import "NSObject_NSKeyValueCoding-Internal.h" +#import "NSKVOInternal.h" #import #import -#import "type_encoding_cases.h" +#import "NSKVOTypeEncodingCases.h" /* These are defined by the ABI and the runtime. */ #define ABI_SUPER(obj) (((Class **) obj)[0][1]) @@ -76,12 +77,12 @@ the KVO machinery (if implemented using manual subclassing) would delete all */ object_addMethod_np(object, @selector(setObject:forKey:), - (IMP) (NSKVO$setObject$forKey$), "v@:@@"); + (IMP) (NSKVO$setObject$forKey$), "v@:@@"); object_addMethod_np(object, @selector(removeObjectForKey:), - (IMP) (NSKVO$removeObjectForKey$), "v@:@"); + (IMP) (NSKVO$removeObjectForKey$), "v@:@"); object_addMethod_np(object, @selector(_isKVOAware), (IMP) (NSKVO$nilIMP), - "v@:"); + "v@:"); } #pragma region Method Implementations @@ -96,7 +97,7 @@ the KVO machinery (if implemented using manual subclassing) would delete all _selectorMappingsForObject(id object) { static char s_selMapKey; - Class cls; + Class cls; // here we explicitly want the public // (non-hidden) class associated with the object. @@ -108,13 +109,13 @@ the KVO machinery (if implemented using manual subclassing) would delete all = (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); + selMappings = [NSMapTable + mapTableWithKeyOptions:NSPointerFunctionsOpaqueMemory + | NSPointerFunctionsOpaquePersonality + valueOptions:NSPointerFunctionsStrongMemory | + NSPointerFunctionsObjectPersonality]; + objc_setAssociatedObject(cls, &s_selMapKey, (id) selMappings, + OBJC_ASSOCIATION_RETAIN); } return selMappings; } @@ -124,7 +125,7 @@ the KVO machinery (if implemented using manual subclassing) would delete all _keyForSelector(id object, SEL selector) { return (NSString *) NSMapGet(_selectorMappingsForObject(object), - sel_getName(selector)); + sel_getName(selector)); } static inline void @@ -138,7 +139,7 @@ the KVO machinery (if implemented using manual subclassing) would delete all // came time to call didChangeValueForKey (because apparently ARC doesn't take // care of this properly) NSMapInsertIfAbsent(_selectorMappingsForObject(object), sel_getName(selector), - key); + key); } static void @@ -153,8 +154,8 @@ the KVO machinery (if implemented using manual subclassing) would delete all // 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); + 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); @@ -193,7 +194,7 @@ the KVO machinery (if implemented using manual subclassing) would delete all break; default: NSLog(@"Can't override setter with more than 6 sizeof(long int) stack " - @"arguments."); + @"arguments."); return; } @@ -213,16 +214,16 @@ the KVO machinery (if implemented using manual subclassing) would delete all static void NSKVONotifying$insertObject$inXxxAtIndex$(id self, SEL _cmd, id object, - NSUInteger index) + NSUInteger index) { NSString *key = _keyForSelector(self, _cmd); NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:index]; [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes - forKey:key]; + forKey:key]; - struct objc_super super = {self, ABI_SUPER(self)}; + 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); @@ -232,13 +233,13 @@ the KVO machinery (if implemented using manual subclassing) would delete all static void NSKVONotifying$insertXxx$atIndexes$(id self, SEL _cmd, id object, - NSIndexSet *indexes) + NSIndexSet *indexes) { NSString *key = _keyForSelector(self, _cmd); [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes - forKey:key]; + forKey:key]; struct objc_super super = {self, ABI_SUPER(self)}; insertAtIndexesIMP imp @@ -256,7 +257,7 @@ the KVO machinery (if implemented using manual subclassing) would delete all [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:key]; - struct objc_super super = {self, ABI_SUPER(self)}; + struct objc_super super = {self, ABI_SUPER(self)}; removeObjectAtIndexIMP imp = (void (*)(id, SEL, NSUInteger)) objc_msg_lookup_super(&super, _cmd); imp(self, _cmd, index); @@ -281,46 +282,46 @@ the KVO machinery (if implemented using manual subclassing) would delete all static void NSKVONotifying$replaceObjectInXxxAtIndex$withObject$(id self, SEL _cmd, - NSUInteger index, - id object) + NSUInteger index, + id object) { NSString *key = _keyForSelector(self, _cmd); NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:index]; [self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes - forKey:key]; + forKey:key]; - struct objc_super super = {self, ABI_SUPER(self)}; + 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]; + forKey:key]; } static void NSKVONotifying$replaceXxxAtIndexes$withXxx$(id self, SEL _cmd, - NSIndexSet *indexes, - NSArray *objects) + NSIndexSet *indexes, + NSArray *objects) { NSString *key = _keyForSelector(self, _cmd); [self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes - forKey:key]; + forKey:key]; struct objc_super super = {self, ABI_SUPER(self)}; replaceAtIndexesIMP imp = (void (*)(id, SEL, NSIndexSet *, NSArray *)) objc_msg_lookup_super(&super, - _cmd); + _cmd); imp(self, _cmd, indexes, objects); [self didChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes - forKey:key]; + forKey:key]; } #define GENERATE_NSKVOSetDispatch_IMPL(Kind) \ @@ -337,7 +338,7 @@ the KVO machinery (if implemented using manual subclassing) would delete all #define GENERATE_NSKVOSetDispatchIndividual_IMPL(Kind) \ static inline void _NSKVOSetDispatchIndividual_##Kind(id self, SEL _cmd, \ - id obj) \ + id obj) \ { \ NSSet *set = [NSSet setWithObject:obj]; \ NSString *key = _keyForSelector(self, _cmd); \ @@ -374,7 +375,7 @@ the KVO machinery (if implemented using manual subclassing) would delete all NSKVO$removeObjectForKey$(id self, SEL _cmd, NSString *key) { [self willChangeValueForKey:key]; - struct objc_super super = {self, ABI_SUPER(self)}; + struct objc_super super = {self, ABI_SUPER(self)}; removeObjectForKeyIMP imp = (void (*)(id, SEL, NSString *)) objc_msg_lookup_super(&super, _cmd); imp(self, _cmd, key); @@ -408,7 +409,7 @@ static void funcName(id self, SEL _cmd, type val) \ GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplUnsignedShort, unsigned short); GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplUnsignedLong, unsigned long); GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplUnsignedLongLong, - unsigned long long); + unsigned long long); GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplBool, bool); GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplObject, id); @@ -423,7 +424,7 @@ static void funcName(id self, SEL _cmd, type val) \ SEL KVCSetterForPropertyName(NSObject *self, const char *key) { - SEL sel = nil; + 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 @@ -462,29 +463,29 @@ static void funcName(id self, SEL _cmd, type val) \ NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:types]; const char *valueType = [sig getArgumentTypeAtIndex:2]; - IMP newImpl = NULL; + 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; + 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]; + format:@"Class %s is not KVO compliant for key %@.", + class_getName(object_getClass(object)), key]; return; } @@ -500,12 +501,12 @@ static void funcName(id self, SEL _cmd, type val) \ 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; - } + { + 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)); @@ -519,7 +520,7 @@ static void funcName(id self, SEL _cmd, type val) \ va_start(ap, format); SEL sel = NSSelectorFromString([[[NSString alloc] initWithFormat:format - arguments:ap] autorelease]); + arguments:ap] autorelease]); va_end(ap); return sel; } @@ -527,7 +528,7 @@ static void funcName(id self, SEL _cmd, type val) \ // invariant: rawKey has already been capitalized static inline void _NSKVOEnsureOrderedCollectionWillNotify(id object, NSString *key, - const char *rawKey) + const char *rawKey) { SEL insertOne = formatSelector(@"insertObject:in%sAtIndex:", rawKey); SEL insertMany = formatSelector(@"insert%s:atIndexes:", rawKey); @@ -535,29 +536,29 @@ static void funcName(id self, SEL _cmd, type val) \ [object respondsToSelector:insertMany]) { replaceAndAssociateWithKey(object, insertOne, key, - (IMP) - NSKVONotifying$insertObject$inXxxAtIndex$); + (IMP) + NSKVONotifying$insertObject$inXxxAtIndex$); replaceAndAssociateWithKey(object, insertMany, key, - (IMP) NSKVONotifying$insertXxx$atIndexes$); + (IMP) NSKVONotifying$insertXxx$atIndexes$); replaceAndAssociateWithKey( - object, formatSelector(@"removeObjectFrom%sAtIndex:", rawKey), key, - (IMP) NSKVONotifying$removeObjectFromXxxAtIndex$); + object, formatSelector(@"removeObjectFrom%sAtIndex:", rawKey), key, + (IMP) NSKVONotifying$removeObjectFromXxxAtIndex$); replaceAndAssociateWithKey(object, - formatSelector(@"remove%sAtIndexes:", rawKey), - key, (IMP) NSKVONotifying$removeXxxAtIndexes$); + formatSelector(@"remove%sAtIndexes:", rawKey), + key, (IMP) NSKVONotifying$removeXxxAtIndexes$); replaceAndAssociateWithKey( - object, formatSelector(@"replaceObjectIn%sAtIndex:withObject:", rawKey), - key, (IMP) NSKVONotifying$replaceObjectInXxxAtIndex$withObject$); + 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$); + 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) + const char *rawKey) { SEL addOne = formatSelector(@"add%sObject:", rawKey); SEL addMany = formatSelector(@"add%s:", rawKey); @@ -566,23 +567,23 @@ static void funcName(id self, SEL _cmd, type val) \ if (([object respondsToSelector:addOne] || [object respondsToSelector:addMany]) && ([object respondsToSelector:removeOne] || - [object respondsToSelector:removeMany])) + [object respondsToSelector:removeMany])) { replaceAndAssociateWithKey( - object, addOne, key, - (IMP) _NSKVOSetDispatchIndividual_NSKeyValueUnionSetMutation); + object, addOne, key, + (IMP) _NSKVOSetDispatchIndividual_NSKeyValueUnionSetMutation); replaceAndAssociateWithKey( - object, addMany, key, - (IMP) _NSKVOSetDispatch_NSKeyValueUnionSetMutation); + object, addMany, key, + (IMP) _NSKVOSetDispatch_NSKeyValueUnionSetMutation); replaceAndAssociateWithKey( - object, removeOne, key, - (IMP) _NSKVOSetDispatchIndividual_NSKeyValueMinusSetMutation); + object, removeOne, key, + (IMP) _NSKVOSetDispatchIndividual_NSKeyValueMinusSetMutation); replaceAndAssociateWithKey( - object, removeMany, key, - (IMP) _NSKVOSetDispatch_NSKeyValueMinusSetMutation); + object, removeMany, key, + (IMP) _NSKVOSetDispatch_NSKeyValueMinusSetMutation); replaceAndAssociateWithKey( - object, formatSelector(@"intersect%s:", rawKey), key, - (IMP) _NSKVOSetDispatch_NSKeyValueIntersectSetMutation); + object, formatSelector(@"intersect%s:", rawKey), key, + (IMP) _NSKVOSetDispatch_NSKeyValueIntersectSetMutation); } } @@ -590,10 +591,10 @@ static void funcName(id self, SEL _cmd, type val) \ mutableBufferFromString(NSString *string) { NSUInteger lengthInBytes = [string length] + 1; - char *rawKey = (char *) malloc(lengthInBytes); + char *rawKey = (char *) malloc(lengthInBytes); [string getCString:rawKey - maxLength:lengthInBytes - encoding:NSASCIIStringEncoding]; + maxLength:lengthInBytes + encoding:NSASCIIStringEncoding]; return rawKey; } diff --git a/Source/type_encoding_cases.h b/Source/type_encoding_cases.h deleted file mode 100644 index f4db43849..000000000 --- a/Source/type_encoding_cases.h +++ /dev/null @@ -1,60 +0,0 @@ -//****************************************************************************** -// -// 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. -// -//****************************************************************************** - -#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) diff --git a/Tests/base/NSKVOSupport/basic.m b/Tests/base/NSKVOSupport/basic.m index 78f5a8bce..c9685ba10 100644 --- a/Tests/base/NSKVOSupport/basic.m +++ b/Tests/base/NSKVOSupport/basic.m @@ -1,69 +1,87 @@ #import #import "ObjectTesting.h" -@interface Foo : NSObject -@property (assign) BOOL a; -@property (assign) NSInteger b; -@property (nonatomic, strong) NSString* c; -@property (nonatomic, strong) NSArray* d; +#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; +@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; +- (id)init +{ + self = [super init]; + if (self) + { + self.receivedCalls = 0; + } + return self; } static char observerContext; -- (void)startObserving:(Foo*)target +- (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]; + 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 *)change context:(void *)context +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context { - PASS(context == &observerContext, "context"); - PASS(object == self.object, "object"); - PASS([keyPath isEqualToString:self.expectedKeyPath], "key path"); - self.receivedCalls++; + 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]; +int +main(int argc, char *argv[]) +{ + [NSAutoreleasePool new]; + + Foo *foo = [Foo new]; + Observer *obs = [Observer new]; + [obs startObserving:foo]; - 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 = @"a"; - foo.a = YES; - PASS(obs.receivedCalls == 1, "received calls") + obs.expectedKeyPath = @"b"; + foo.b = 1; + PASS(obs.receivedCalls == 2, "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; +} - obs.expectedKeyPath = @"c"; - foo.c = @"henlo"; - PASS(obs.receivedCalls == 3, "received calls") -} \ No newline at end of file +#endif \ No newline at end of file diff --git a/Tests/base/NSKVOSupport/newoldvalues.m b/Tests/base/NSKVOSupport/newoldvalues.m index 41243a68e..0fac8b401 100644 --- a/Tests/base/NSKVOSupport/newoldvalues.m +++ b/Tests/base/NSKVOSupport/newoldvalues.m @@ -1,51 +1,54 @@ #import #import "ObjectTesting.h" +#if defined(__OBJC2__) + @class Bar; -@interface Foo : NSObject -@property (assign) Bar* globalBar; -@property (assign) NSInteger a; +@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; +@interface Bar : NSObject +@property (assign) NSInteger x; +@property (strong, nonatomic) Foo *firstFoo; +@property (strong, nonatomic) Foo *secondFoo; @end - @implementation Foo -+ (NSSet*)keyPathsForValuesAffectingB ++ (NSSet *)keyPathsForValuesAffectingB { - return [NSSet setWithArray:@[@"a", @"globalBar.x"]]; + return [NSSet setWithArray:@[ @"a", @"globalBar.x" ]]; } - (NSInteger)b { - return self.a + self.globalBar.x; + 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; +- (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; +@interface Observer : NSObject +@property (assign) Foo *object; @property (assign) NSInteger expectedOldValue; @property (assign) NSInteger expectedNewValue; @property (assign) NSInteger receivedCalls; @@ -53,70 +56,91 @@ @interface Observer : NSObject @implementation Observer -- (id)init { - self = [super init]; - if (self) { - self.receivedCalls = 0; - } - return self; +- (id)init +{ + self = [super init]; + if (self) + { + self.receivedCalls = 0; + } + return self; } static char observerContext; -- (void)startObserving:(Foo*)target +- (void)startObserving:(Foo *)target { - self.object = target; - [target addObserver:self forKeyPath:@"b" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:&observerContext]; + self.object = target; + [target + addObserver:self + forKeyPath:@"b" + options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld + context:&observerContext]; } -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context { - PASS(context == &observerContext, "context is correct"); - PASS(object == self.object, "object is correct"); + PASS(context == &observerContext, "context is correct"); + PASS(object == self.object, "object is correct"); - id newValue = change[NSKeyValueChangeNewKey]; - id oldValue = change[NSKeyValueChangeOldKey]; + 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++; + 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 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"); - -} \ No newline at end of file +int +main(int argc, char *argv[]) +{ + [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"); +} + +#else +int +main(int argc, char *argv[]) +{ + return 0; +} + +#endif \ No newline at end of file From 366e24b8f1265b2a27a8fb66e8360588a4f1fb09 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 4 Jun 2024 23:04:25 +0200 Subject: [PATCH 06/46] NSKeyValueObserving: Rename TypeEncodingCases header --- Source/NSKVOTypeEncodingCases.h | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 Source/NSKVOTypeEncodingCases.h diff --git a/Source/NSKVOTypeEncodingCases.h b/Source/NSKVOTypeEncodingCases.h new file mode 100644 index 000000000..c306e7be0 --- /dev/null +++ b/Source/NSKVOTypeEncodingCases.h @@ -0,0 +1,62 @@ +//****************************************************************************** +// +// 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) From e24b30837e34900f1236f37915b3d4fd524161f4 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 12:18:26 +0200 Subject: [PATCH 07/46] NSKVOSupport: Fix new objects not being added to NSKeyValueChangeNew set on set mutation --- Source/NSKVOSupport.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/NSKVOSupport.m b/Source/NSKVOSupport.m index 423a256bd..6fd1de42c 100644 --- a/Source/NSKVOSupport.m +++ b/Source/NSKVOSupport.m @@ -1079,7 +1079,7 @@ - (void)didChangeValueForKey:(NSString *)key // NOT in oldValues NSSet *newValue = [objects objectsPassingTest:^BOOL(id obj, BOOL *stop) { - return [objects member:obj] ? NO : YES; + return [oldValues member:obj] ? NO : YES; }]; change[NSKeyValueChangeNewKey] = newValue; From 479fe589d9e948447802c92df7f424b8aaa969d5 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 12:38:28 +0200 Subject: [PATCH 08/46] NSKeyValueMutableSet: Fix will and didChange notifications for set operations --- Source/NSKeyValueMutableSet.m | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Source/NSKeyValueMutableSet.m b/Source/NSKeyValueMutableSet.m index 0ce871220..0c8e969e7 100644 --- a/Source/NSKeyValueMutableSet.m +++ b/Source/NSKeyValueMutableSet.m @@ -682,6 +682,7 @@ - (void) addObject: (id)anObject { if (notifiesObservers && !changeInProgress) { + NSLog(@"Observer Object: %@ addingObject: %@", object, anObject); [object willChangeValueForKey: key withSetMutation: NSKeyValueUnionSetMutation usingObjects: [NSSet setWithObject: anObject]]; @@ -718,14 +719,14 @@ - (void) unionSet: (id)anObject { [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 +736,14 @@ - (void) minusSet: (id)anObject { [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 +753,14 @@ - (void) intersectSet: (id)anObject { [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 +770,14 @@ - (void) setSet: (id)anObject { [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 From b06b5a059ef3ceb6fdb5904877708253c98bd0e4 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 13:00:17 +0200 Subject: [PATCH 09/46] NSKeyValueMutableSet: Document Accessor Search Patterns --- Source/NSKeyValueMutableSet.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/NSKeyValueMutableSet.m b/Source/NSKeyValueMutableSet.m index 0c8e969e7..398ec79a8 100644 --- a/Source/NSKeyValueMutableSet.m +++ b/Source/NSKeyValueMutableSet.m @@ -104,6 +104,8 @@ + (NSKeyValueMutableSet *) setForKey: (NSString *)aKey ofObject: (id)anObject } + /* Ordering as specified in "Accessor Search Patterns" from Key-Value Coding + * Programming Guide */ proxy = [NSKeyValueFastMutableSet setForKey: aKey ofObject: anObject withCapitalizedKey: keybuf]; From b3601834f268f5f44a0827358ca44c84d60d34d2 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 13:23:24 +0200 Subject: [PATCH 10/46] NSKVOSupport: Add toMany test --- Tests/base/NSKVOSupport/kvoToMany.m | 1214 ++++++++++++++++++++++++ Tests/base/NSKVOSupport/newoldvalues.m | 4 +- 2 files changed, 1217 insertions(+), 1 deletion(-) create mode 100644 Tests/base/NSKVOSupport/kvoToMany.m diff --git a/Tests/base/NSKVOSupport/kvoToMany.m b/Tests/base/NSKVOSupport/kvoToMany.m new file mode 100644 index 000000000..cbf61aa7f --- /dev/null +++ b/Tests/base/NSKVOSupport/kvoToMany.m @@ -0,0 +1,1214 @@ +/** + kvoToMany.m + + Copyright (C) 2024 Free Software Foundation, Inc. + + Written by: Hugo Melder + 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 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. +*/ + +#import +#import "Testing.h" + +#if defined(__OBJC2__) + +#define PASS_ANY_THROW(expr, msg) \ + do \ + { \ + BOOL threw = NO; \ + @try \ + { \ + expr; \ + } \ + @catch (NSException * exception) \ + { \ + threw = YES; \ + } \ + PASS(threw, msg); \ + } while (0) + +@interface Observee : NSObject +{ + NSMutableArray *_bareArray; + NSMutableArray *_manualNotificationArray; + NSMutableArray *_kvcMediatedArray; + NSMutableArray *_arrayWithHelpers; + NSMutableSet *_setWithHelpers; + NSMutableSet *_kvcMediatedSet; + NSMutableSet *_manualNotificationSet; + NSSet *_roSet; +} + +- (NSArray *)manualNotificationArray; +- (NSSet *)setWithHelpers; + +@end + +typedef void (^ChangeCallback)(NSString *, id, NSDictionary *, void *); +typedef void (^PerformBlock)(Observee *); + +#define CHANGE_CB \ + ^(NSString * keyPath, id object, NSDictionary * change, void *context) + +@implementation Observee +- (instancetype)init +{ + self = [super init]; + if (self) + { + _bareArray = [NSMutableArray new]; + _manualNotificationArray = [NSMutableArray new]; + _kvcMediatedArray = [NSMutableArray new]; + _arrayWithHelpers = [NSMutableArray new]; + _setWithHelpers = [NSMutableSet new]; + _kvcMediatedSet = [NSMutableSet new]; + _manualNotificationSet = [NSMutableSet new]; + } + return self; +} + +- (void)dealloc +{ + [_bareArray release]; + [_manualNotificationArray release]; + [_kvcMediatedArray release]; + [_arrayWithHelpers release]; + [_setWithHelpers release]; + [_kvcMediatedSet release]; + [_manualNotificationSet release]; + [super dealloc]; +} + +/* Used for testing NSKeyValueFastMutableSet which is used in + * +[NSKeyValueMutableSet setForKey:ofObject:] */ + +- (NSSet *)proxySet +{ + return _kvcMediatedSet; +} + +- (void)addProxySetObject:(id)obj +{ + [_kvcMediatedSet addObject:obj]; +} + +- (void)removeProxySetObject:(id)obj +{ + [_kvcMediatedSet removeObject:obj]; +} + +- (void)addProxySet:(NSSet *)set +{ + [_kvcMediatedSet unionSet:set]; +} + +- (void)removeProxySet:(NSSet *)set +{ + [_kvcMediatedSet minusSet:set]; +} + +/* Used for testing NSKeyValueSlowMutableSet which is used + * when no add or remove method is available. */ +- (NSSet *)proxyRoSet +{ + return _roSet; +} + +- (void)setProxyRoSet:(NSSet *)set +{ + ASSIGN(_roSet, set); +} + +- (void)addObjectToBareArray:(NSObject *)object +{ + [_bareArray addObject:object]; +} + +- (void)addObjectToManualArray:(NSObject *)object +{ + NSIndexSet *indexes = + [NSIndexSet indexSetWithIndex:[_manualNotificationArray count]]; + [self willChange:NSKeyValueChangeInsertion + valuesAtIndexes:indexes + forKey:@"manualNotificationArray"]; + [_manualNotificationArray addObject:object]; + [self didChange:NSKeyValueChangeInsertion + valuesAtIndexes:indexes + forKey:@"manualNotificationArray"]; +} + +- (void)removeObjectFromManualArrayIndex:(NSUInteger)index +{ + NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:index]; + [self willChange:NSKeyValueChangeRemoval + valuesAtIndexes:indexes + forKey:@"manualNotificationArray"]; + [_manualNotificationArray removeObjectAtIndex:index]; + [self didChange:NSKeyValueChangeRemoval + valuesAtIndexes:indexes + forKey:@"manualNotificationArray"]; +} + +- (NSArray *)manualNotificationArray +{ + return _manualNotificationArray; +} + +- (void)insertObject:(NSObject *)object + inArrayWithHelpersAtIndex:(NSUInteger)index +{ + [_arrayWithHelpers insertObject:object atIndex:index]; +} + +- (void)removeObjectFromArrayWithHelpersAtIndex:(NSUInteger)index +{ + [_arrayWithHelpers removeObjectAtIndex:index]; +} + +- (NSSet *)setWithHelpers +{ + return _setWithHelpers; +} + +- (void)addSetWithHelpersObject:(id)obj +{ + [_setWithHelpers addObject:obj]; +} + +- (void)removeSetWithHelpersObject:(id)obj +{ + [_setWithHelpers removeObject:obj]; +} + +- (void)addSetWithHelpers:(NSSet *)set +{ + [_setWithHelpers unionSet:set]; +} + +- (void)removeSetWithHelpers:(NSSet *)set +{ + [_setWithHelpers minusSet:set]; +} + +- (void)intersectSetWithHelpers:(NSSet *)set +{ + [_setWithHelpers intersectSet:set]; +} + +- (void)setSetWithHelpers:(NSSet *)set +{ + [_setWithHelpers setSet:set]; +} + +- (void)manualSetAddObject:(id)obj +{ + NSSet *set = [NSSet setWithObject:obj]; + [self willChangeValueForKey:@"manualNotificationSet" + withSetMutation:NSKeyValueUnionSetMutation + usingObjects:set]; + [_manualNotificationSet addObject:obj]; + [self didChangeValueForKey:@"manualNotificationSet" + withSetMutation:NSKeyValueUnionSetMutation + usingObjects:set]; +} + +- (void)manualSetRemoveObject:(id)obj +{ + NSSet *set = [NSSet setWithObject:obj]; + [self willChangeValueForKey:@"manualNotificationSet" + withSetMutation:NSKeyValueMinusSetMutation + usingObjects:set]; + [_manualNotificationSet removeObject:obj]; + [self didChangeValueForKey:@"manualNotificationSet" + withSetMutation:NSKeyValueMinusSetMutation + usingObjects:set]; +} + +- (void)manualUnionSet:(NSSet *)set +{ + [self willChangeValueForKey:@"manualNotificationSet" + withSetMutation:NSKeyValueUnionSetMutation + usingObjects:set]; + [_manualNotificationSet unionSet:set]; + [self didChangeValueForKey:@"manualNotificationSet" + withSetMutation:NSKeyValueUnionSetMutation + usingObjects:set]; +} + +- (void)manualMinusSet:(NSSet *)set +{ + [self willChangeValueForKey:@"manualNotificationSet" + withSetMutation:NSKeyValueMinusSetMutation + usingObjects:set]; + [_manualNotificationSet minusSet:set]; + [self didChangeValueForKey:@"manualNotificationSet" + withSetMutation:NSKeyValueMinusSetMutation + usingObjects:set]; +} + +- (void)manualIntersectSet:(NSSet *)set +{ + [self willChangeValueForKey:@"manualNotificationSet" + withSetMutation:NSKeyValueIntersectSetMutation + usingObjects:set]; + [_manualNotificationSet intersectSet:set]; + [self didChangeValueForKey:@"manualNotificationSet" + withSetMutation:NSKeyValueIntersectSetMutation + usingObjects:set]; +} + +- (void)manualSetSet:(NSSet *)set +{ + [self willChangeValueForKey:@"manualNotificationSet" + withSetMutation:NSKeyValueSetSetMutation + usingObjects:set]; + [_manualNotificationSet setSet:set]; + [self didChangeValueForKey:@"manualNotificationSet" + withSetMutation:NSKeyValueSetSetMutation + usingObjects:set]; +} + +@end + +@interface TestObserver : NSObject +@property (nonatomic, strong) + NSMutableArray *callbacks; +@property (nonatomic) NSUInteger hits; +@property (nonatomic) NSUInteger callbackIndex; +@end + +@implementation TestObserver +- (instancetype)init +{ + self = [super init]; + if (self) + { + _callbacks = [NSMutableArray new]; + _hits = 0; + _callbackIndex = 0; + } + return self; +} + +- (void)dealloc +{ + [_callbacks release]; + [super dealloc]; +} + +- (void)performBlock:(void (^)(void))block + andExpectChangeCallbacks: + (NSArray *)callbacks +{ + self.hits = 0; + self.callbackIndex = 0; + ASSIGN(_callbacks, callbacks); + + block(); +} + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + if (self.callbacks.count > 0) + { + void (^callback)(NSString *, id, NSDictionary *, void *) + = self.callbacks[_callbackIndex]; + _callbackIndex = (_callbackIndex + 1) % [_callbacks count]; + callback(keyPath, object, change, context); + } + self.hits++; +} +@end + +@interface TestFacade : NSObject +@property (nonatomic, strong) Observee *observee; +@property (nonatomic, strong) TestObserver *observer; +@end + +@implementation TestFacade ++ (instancetype)newWithObservee:(Observee *)observee +{ + return [[self alloc] initWithObservee:observee]; +} + +- (instancetype)initWithObservee:(Observee *)observee +{ + self = [super init]; + if (self) + { + _observee = observee; + _observer = [TestObserver new]; + } + return self; +} + +- (void)dealloc +{ + [_observee release]; + [_observer release]; + [super dealloc]; +} + +- (void)performBlock:(void (^)(Observee *))block + andExpectChangeCallbacks: + (NSArray *)callbacks +{ + @try + { + [_observer + performBlock:^{ + block(_observee); + } + andExpectChangeCallbacks:callbacks]; + } + @catch (NSException *exception) + { + NSLog(@"Test failed with exception: %@", exception); + } +} + +- (void)observeKeyPath:(NSString *)keyPath + withOptions:(NSKeyValueObservingOptions)options + performingBlock:(void (^)(Observee *))block + andExpectChangeCallbacks: + (NSArray *)callbacks +{ + [self + performBlock:^(Observee *observee) { + [observee addObserver:self.observer + forKeyPath:keyPath + options:options + context:nil]; + @try + { + block(observee); + } + @finally + { + [observee removeObserver:self.observer + forKeyPath:keyPath]; + } + } + andExpectChangeCallbacks:callbacks]; +} + +- (NSUInteger)hits +{ + return [_observer hits]; +} +@end + +@interface DummyObject : NSObject +@property (nonatomic, copy) NSString *name; +@property (nonatomic, retain) DummyObject *sub; +@end + +@implementation DummyObject ++ (instancetype)makeDummy +{ + DummyObject *ret = [[DummyObject new] autorelease]; + ret.name = @"Value"; + return ret; +} + +- (void)dealloc +{ + [_name release]; + [_sub release]; + [super dealloc]; +} + +@end + +static void +ToMany_NoNotificationOnBareArray() +{ + START_SET("ToMany_NoNotificationOnBareArray"); + + Observee *observee = [Observee new]; + TestFacade *facade = [TestFacade newWithObservee:observee]; + + [facade observeKeyPath:@"bareArray" + withOptions:0 + performingBlock:^(Observee *observee) { + [observee addObjectToBareArray:@"hello"]; + } + andExpectChangeCallbacks:@[ + ^(NSString *keyPath, id object, NSDictionary *change, + void *context) { // Any notification here is illegal. + PASS(NO, "Any notification here is illegal."); + } + ]]; + PASS([facade hits] == 0, "No notifications were sent"); + + END_SET("ToMany_NoNotificationOnBareArray"); +} + +static void +ToMany_NotifyingArray() +{ + START_SET("ToMany_NotifyingArray"); + + ChangeCallback firstInsertCallback; + ChangeCallback secondInsertCallback; + ChangeCallback removalCallback; + ChangeCallback illegalChangeNotification; + + /* Callback Setup */ + + firstInsertCallback = CHANGE_CB + { + NSIndexSet *indexes; + + PASS_EQUAL(@(NSKeyValueChangeInsertion), change[NSKeyValueChangeKindKey], + "firstInsertCallback: Change is an insertion"); + + indexes = change[NSKeyValueChangeIndexesKey]; + + PASS(indexes != nil, "firstInsertCallback: Indexes are not nil"); + PASS([indexes firstIndex] == 0, "firstInsertCallback: Index is 0"); + + if (![change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) + { + PASS_EQUAL(@"object1", [change[NSKeyValueChangeNewKey] objectAtIndex:0], + "firstInsertCallback: New object is 'object1'"); + } + }; + + secondInsertCallback = CHANGE_CB + { + NSIndexSet *indexes; + + // We should get an add on index 1 of "object2" + PASS_EQUAL(@(NSKeyValueChangeInsertion), change[NSKeyValueChangeKindKey], + "secondInsertCallback: Change is an insertion"); + + indexes = change[NSKeyValueChangeIndexesKey]; + + PASS(indexes != nil, "secondInsertCallback: Indexes are not nil"); + PASS([indexes firstIndex] == 1, "secondInsertCallback: Index is 1"); + + if (![change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) + { + PASS_EQUAL(@"object2", [change[NSKeyValueChangeNewKey] objectAtIndex:0], + "secondInsertCallback: New object is 'object2'"); + } + }; + + removalCallback = CHANGE_CB + { + NSIndexSet *indexes; + + PASS_EQUAL(@(NSKeyValueChangeRemoval), change[NSKeyValueChangeKindKey], + "removalCallback: Change is a removal"); + + indexes = change[NSKeyValueChangeIndexesKey]; + + PASS(indexes != nil, "removalCallback: Indexes are not nil"); + PASS([indexes firstIndex] == 0, "removalCallback: Index is 0"); + if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) + { + PASS_EQUAL(@"object1", [change[NSKeyValueChangeOldKey] objectAtIndex:0], + "removalCallback: Old object is 'object1'"); + } + }; + + illegalChangeNotification + = CHANGE_CB{PASS(NO, "illegalChangeNotification: was called")}; + + /* Testing manually notifiying array (utilizes add and remove meths in + * Observee) */ + + Observee *observee; + TestFacade *facade; + + observee = [Observee new]; + facade = [TestFacade newWithObservee:observee]; + + // This test expects one change for each key; any more than that is a failure. + [facade observeKeyPath:@"manualNotificationArray" + withOptions:NSKeyValueObservingOptionOld + | NSKeyValueObservingOptionNew + performingBlock:^(Observee *observee) { + [observee addObjectToManualArray:@"object1"]; + [observee addObjectToManualArray:@"object2"]; + [observee removeObjectFromManualArrayIndex:0]; + } + andExpectChangeCallbacks:@[ + firstInsertCallback, secondInsertCallback, removalCallback, + illegalChangeNotification + ]]; + PASS([facade hits] == 3, "Three notifications were sent"); + + observee = [Observee new]; + facade = [TestFacade newWithObservee:observee]; + // This test expects two change notifications for each key; any more than that + // is a failure. + [facade observeKeyPath:@"manualNotificationArray" + withOptions:NSKeyValueObservingOptionPrior + | NSKeyValueObservingOptionOld + | NSKeyValueObservingOptionNew + performingBlock:^(Observee *observee) { + [observee addObjectToManualArray:@"object1"]; + [observee addObjectToManualArray:@"object2"]; + [observee removeObjectFromManualArrayIndex:0]; + } + andExpectChangeCallbacks:@[ + firstInsertCallback, firstInsertCallback, secondInsertCallback, + secondInsertCallback, removalCallback, removalCallback, + illegalChangeNotification + ]]; + + PASS([facade hits] == 6, "Six notifications were sent"); + PASS_EQUAL(@[ @"object2" ], [observee manualNotificationArray], + "Final array is 'object2'"); + + // This test expects one change notification: the initial one. Any more than + // that is a failure. + ChangeCallback initialNotificationCallback = CHANGE_CB + { + NSArray *expectedArray = @[ @"object2" ]; + PASS_EQUAL(expectedArray, change[NSKeyValueChangeNewKey], + "Initial notification: New array is 'object2'"); + NSLog(@"Initial notification: New array is %@", + change[NSKeyValueChangeNewKey]); + }; + + [facade observeKeyPath:@"manualNotificationArray" + withOptions:NSKeyValueObservingOptionInitial + | NSKeyValueObservingOptionNew + performingBlock:^(Observee *observee) { + } + andExpectChangeCallbacks:@[ + initialNotificationCallback, illegalChangeNotification + ]]; + PASS([facade hits] == 1, "One notification was sent"); + + /* Testing mediated array */ + [facade observeKeyPath:@"kvcMediatedArray" + withOptions:NSKeyValueObservingOptionOld + | NSKeyValueObservingOptionNew + performingBlock:^(Observee *observee) { + // This array is not assisted with setter functions and should go + // through the get/mutate/set codepath. + NSMutableArray *mediatedVersionOfArray = + [observee mutableArrayValueForKey:@"kvcMediatedArray"]; + [mediatedVersionOfArray addObject:@"object1"]; + [mediatedVersionOfArray addObject:@"object2"]; + [mediatedVersionOfArray removeObjectAtIndex:0]; + } + andExpectChangeCallbacks:@[ + firstInsertCallback, secondInsertCallback, removalCallback, + illegalChangeNotification + ]]; + PASS([facade hits] == 3, "Three notifications were sent"); + + /* Testing array with helpers */ + observee = [Observee new]; + facade = [TestFacade newWithObservee:observee]; + [facade observeKeyPath:@"arrayWithHelpers" + withOptions:NSKeyValueObservingOptionOld + | NSKeyValueObservingOptionNew + performingBlock:^(Observee *observee) { + // This array is assisted by setter functions, and should also + // dispatch one notification per change. + NSMutableArray *mediatedVersionOfArray = + [observee mutableArrayValueForKey:@"arrayWithHelpers"]; + [mediatedVersionOfArray addObject:@"object1"]; + [mediatedVersionOfArray addObject:@"object2"]; + [mediatedVersionOfArray removeObjectAtIndex:0]; + } + andExpectChangeCallbacks:@[ + firstInsertCallback, secondInsertCallback, removalCallback, + illegalChangeNotification + ]]; + PASS([facade hits] == 3, "Three notifications were sent"); + + observee = [Observee new]; + facade = [TestFacade newWithObservee:observee]; + // In this test, we use the same arrayWithHelpers as above, but interact with + // it manually. + [facade observeKeyPath:@"arrayWithHelpers" + withOptions:NSKeyValueObservingOptionOld + | NSKeyValueObservingOptionNew + performingBlock:^(Observee *observee) { + // This array is assisted by setter functions, and should also + // dispatch one notification per change. + [observee insertObject:@"object1" inArrayWithHelpersAtIndex:0]; + [observee insertObject:@"object2" inArrayWithHelpersAtIndex:1]; + [observee removeObjectFromArrayWithHelpersAtIndex:0]; + } + andExpectChangeCallbacks:@[ + firstInsertCallback, secondInsertCallback, removalCallback, + illegalChangeNotification + ]]; + PASS([facade hits] == 3, "Three notifications were sent"); + + END_SET("ToMany_NotifyingArray"); +} + +static void +ToMany_KVCMediatedArrayWithHelpers_AggregateFunction() +{ + START_SET("ToMany_KVCMediatedArrayWithHelpers_AggregateFunction"); + + ChangeCallback insertCallbackPost; + ChangeCallback illegalChangeNotification; + + insertCallbackPost = CHANGE_CB + { + PASS(change[NSKeyValueChangeNotificationIsPriorKey] == nil, "Post change"); + PASS_EQUAL(@(NSKeyValueChangeSetting), change[NSKeyValueChangeKindKey], + "Change is a setting"); + PASS_EQUAL(@(0), change[NSKeyValueChangeOldKey], "Old value is 0"); + PASS_EQUAL(@(1), change[NSKeyValueChangeNewKey], "New value is 1"); + + NSIndexSet *indexes = change[NSKeyValueChangeIndexesKey]; + PASS(indexes == nil, "Indexes are nil"); + }; + + illegalChangeNotification = CHANGE_CB + { + PASS(NO, "illegalChangeNotification"); + }; + + Observee *observee = [Observee new]; + TestFacade *facade = [TestFacade newWithObservee:observee]; + [facade observeKeyPath:@"arrayWithHelpers.@count" + withOptions:NSKeyValueObservingOptionOld + | NSKeyValueObservingOptionNew + performingBlock:^(Observee *observee) { + // This array is assisted by setter functions, and should also + // dispatch one notification per change. + NSMutableArray *mediatedVersionOfArray = + [observee mutableArrayValueForKey:@"arrayWithHelpers"]; + [mediatedVersionOfArray addObject:@"object1"]; + } + andExpectChangeCallbacks:@[ + insertCallbackPost, illegalChangeNotification + ]]; + PASS([facade hits] == 1, "One notification was sent"); + + observee = [Observee new]; + facade = [TestFacade newWithObservee:observee]; + // In this test, we use the same arrayWithHelpers as above, but interact with + // it manually. + [facade observeKeyPath:@"arrayWithHelpers.@count" + withOptions:NSKeyValueObservingOptionOld + | NSKeyValueObservingOptionNew + performingBlock:^(Observee *observee) { + // This array is assisted by setter functions, and should also + // dispatch one notification per change. + [observee insertObject:@"object1" inArrayWithHelpersAtIndex:0]; + } + andExpectChangeCallbacks:@[ + insertCallbackPost, illegalChangeNotification + ]]; + PASS([facade hits] == 1, "One notification was sent"); + + END_SET("ToMany_KVCMediatedArrayWithHelpers_AggregateFunction"); +} + +static void +ToMany_ToOne_ShouldDowngradeForOrderedObservation() +{ + START_SET("ToMany_ToOne_ShouldDowngradeForOrderedObservation"); + + ChangeCallback insertCallbackPost; + ChangeCallback illegalChangeNotification; + + insertCallbackPost = CHANGE_CB + { + PASS(change[NSKeyValueChangeNotificationIsPriorKey] == nil, "Post change"); + PASS_EQUAL(@(NSKeyValueChangeSetting), change[NSKeyValueChangeKindKey], + "Change is a setting"); + NSArray *expectedOld = @[ @"Value" ]; + PASS_EQUAL(expectedOld, change[NSKeyValueChangeOldKey], + "Old value is correct"); + NSArray *expectedNew = @[ @"Value", @"Value" ]; + PASS_EQUAL(expectedNew, change[NSKeyValueChangeNewKey], + "New value is correct"); + NSIndexSet *indexes = change[NSKeyValueChangeIndexesKey]; + PASS(indexes == nil, "Indexes are nil"); + }; + + illegalChangeNotification = CHANGE_CB + { + PASS(NO, "illegalChangeNotification"); + }; + + Observee *observee = [Observee new]; + [observee insertObject:[DummyObject makeDummy] inArrayWithHelpersAtIndex:0]; + + TestFacade *facade = [TestFacade newWithObservee:observee]; + [facade observeKeyPath:@"arrayWithHelpers.name" + withOptions:NSKeyValueObservingOptionOld + | NSKeyValueObservingOptionNew + performingBlock:^(Observee *observee) { + // This array is assisted by setter functions, and should also + // dispatch one notification per change. + [observee insertObject:[DummyObject makeDummy] + inArrayWithHelpersAtIndex:0]; + } + andExpectChangeCallbacks:@[ + insertCallbackPost, illegalChangeNotification + ]]; + PASS([facade hits] == 1, "One notification was sent"); + + END_SET("ToMany_ToOne_ShouldDowngradeForOrderedObservation"); +} + +static void +ObserverInformationShouldNotLeak() +{ + START_SET("ObserverInformationShouldNotLeak"); + + ChangeCallback onlyNewCallback; + ChangeCallback illegalChangeNotification; + + onlyNewCallback = CHANGE_CB + { + PASS(change[NSKeyValueChangeNewKey] != nil, "New key is not nil"); + PASS(change[NSKeyValueChangeOldKey] == nil, "Old key is nil"); + }; + + illegalChangeNotification = CHANGE_CB + { + PASS(NO, "illegalChangeNotification"); + }; + + Observee *observee = [Observee new]; + TestFacade *firstFacade = [TestFacade newWithObservee:observee]; + [observee + addObserver:firstFacade.observer + forKeyPath:@"manualNotificationArray" + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:nil]; + + TestFacade *facade = [TestFacade newWithObservee:observee]; + [facade observeKeyPath:@"manualNotificationArray" + withOptions:NSKeyValueObservingOptionNew + performingBlock:^(Observee *observee) { + [observee addObjectToManualArray:@"object1"]; + } + andExpectChangeCallbacks:@[ onlyNewCallback, illegalChangeNotification ]]; + + [observee removeObserver:firstFacade.observer + forKeyPath:@"manualNotificationArray"]; + + PASS([facade hits] == 1, "One notification was sent"); + + END_SET("ObserverInformationShouldNotLeak"); +} + +static void +NSArrayShouldNotBeObservable() +{ + START_SET("NSArrayShouldNotBeObservable"); + + NSArray *test = @[ @1, @2, @3 ]; + TestObserver *observer = [TestObserver new]; + PASS_ANY_THROW([test addObserver:observer + forKeyPath:@"count" + options:0 + context:nil], + "NSArray is not observable"); + + // These would throw anyways because there should be no observer for the key + // path, but test anyways + PASS_ANY_THROW([test removeObserver:observer forKeyPath:@"count"], + "Check removing non-existent observer"); + PASS_ANY_THROW([test removeObserver:observer forKeyPath:@"count" context:nil], + "Check removing non-existent observer"); + + END_SET("NSArrayShouldNotBeObservable"); +} + +static void +NSArrayShouldThrowWhenTryingToObserveIndexesOutOfRange() +{ + START_SET("NSArrayShouldThrowWhenTryingToObserveIndexesOutOfRange"); + + NSArray *test = @[ [Observee new], [Observee new] ]; + TestObserver *observer = [TestObserver new]; + PASS_ANY_THROW([test addObserver:observer + toObjectsAtIndexes:[NSIndexSet indexSetWithIndex:4] + forKeyPath:@"bareArray" + options:0 + context:nil], + "Observe index out of range"); + + END_SET("NSArrayShouldThrowWhenTryingToObserveIndexesOutOfRange"); +} + +static void +NSArrayObserveElements() +{ + START_SET("NSArrayObserveElements"); + + NSArray *observeeArray = @[ [Observee new], [Observee new], [Observee new] ]; + TestObserver *observer = [TestObserver new]; + PASS_RUNS([observeeArray + addObserver:observer + toObjectsAtIndexes:[NSIndexSet + indexSetWithIndexesInRange:NSMakeRange(0, 2)] + forKeyPath:@"manualNotificationArray" + options:(NSKeyValueObservingOptionOld + | NSKeyValueObservingOptionNew) + context:nil], + "Observe first two elements"); + + // First two elements in range for observation so observer will receive + // changes + [observeeArray[0] addObjectToManualArray:@"object1"]; + [observeeArray[0] addObjectToManualArray:@"object2"]; + PASS([observer hits] == 2, "First two elements in range for observation"); + + [observeeArray[1] addObjectToManualArray:@"object1"]; + PASS([observer hits] == 3, "Second element in range for observation"); + + // But the third element is not so observer will not receive changes + [observeeArray[2] addObjectToManualArray:@"object1"]; + PASS([observer hits] == 3, "Third element not in range for observation"); + + PASS_RUNS([observeeArray + removeObserver:observer + fromObjectsAtIndexes:[NSIndexSet + indexSetWithIndexesInRange:NSMakeRange(0, + 1)] + forKeyPath:@"manualNotificationArray"], + "remove observer from first element"); + + // Removed observer from first element, so modifying it will not report a + // change + [observeeArray[0] addObjectToManualArray:@"object3"]; + PASS([observer hits] == 3, "First element observer removed"); + + // But the second element is still being observed + [observeeArray[1] addObjectToManualArray:@"object2"]; + PASS([observer hits] == 4, "Second element still being observed"); + + PASS_RUNS([observeeArray + removeObserver:observer + fromObjectsAtIndexes:[NSIndexSet + indexSetWithIndexesInRange:NSMakeRange(1, + 1)] + forKeyPath:@"manualNotificationArray"], + "remove observer from second element"); + + [observeeArray[1] addObjectToManualArray:@"object3"]; + PASS([observer hits] == 4, "Second element observer removed"); + + END_SET("NSArrayObserveElements"); +} + +static void +NSSetShouldNotBeObservable() +{ + START_SET("NSSetShouldNotBeObservable"); + + NSSet *test = [NSSet setWithObjects:@1, @2, @3, nil]; + TestObserver *observer = [TestObserver new]; + PASS_ANY_THROW([test addObserver:observer + forKeyPath:@"count" + options:0 + context:nil], + "NSSet is not observable"); + + // These would throw anyways because there should be no observer for the key + // path, but test anyways + PASS_ANY_THROW([test removeObserver:observer forKeyPath:@"count"], + "Check removing non-existent observer"); + PASS_ANY_THROW([test removeObserver:observer forKeyPath:@"count" context:nil], + "Check removing non-existent observer"); + + END_SET("NSSetShouldNotBeObservable"); +} + +static void +NSSetMutationMethods() +{ + START_SET("NSSetMutationMethods"); + + __block BOOL setSetChanged = NO; + + // Union with @({@1, @2, @3}) to get @({@1, @2, @3}) + ChangeCallback unionCallback = CHANGE_CB + { + PASS_EQUAL(@(NSKeyValueChangeInsertion), change[NSKeyValueChangeKindKey], + "Union change is an insertion"); + NSSet *expected = [NSSet setWithObjects:@1, @2, @3, nil]; + PASS_EQUAL(change[NSKeyValueChangeNewKey], expected, + "Union new key is correct"); + PASS(change[NSKeyValueChangeOldKey] == nil, "Union old key is nil"); + }; + + // Minus with @({@1}) to get @({@2, @3}) + ChangeCallback minusCallback = CHANGE_CB + { + PASS_EQUAL(change[NSKeyValueChangeKindKey], @(NSKeyValueChangeRemoval), + "Minus change is a removal"); + PASS_EQUAL(change[NSKeyValueChangeOldKey], [NSSet setWithObject:@1], + "Minus old key is correct"); + PASS(change[NSKeyValueChangeNewKey] == nil, "Minus new key is nil"); + }; + + // Add @1 to @({@2, @3}) to get @({@1, @2, @3}) + ChangeCallback addCallback = CHANGE_CB + { + PASS_EQUAL(@(NSKeyValueChangeInsertion), change[NSKeyValueChangeKindKey], + "Add change is an insertion"); + NSLog(@"Change %@", change); + PASS_EQUAL([NSSet setWithObject:@1], change[NSKeyValueChangeNewKey], + "Add new key is correct"); + PASS(change[NSKeyValueChangeOldKey] == nil, "Add old key is nil"); + }; + + // Remove @1 from @({@1, @2, @3}) to get @({@2, @3}) + ChangeCallback removeCallback = CHANGE_CB + { + PASS_EQUAL(@(NSKeyValueChangeRemoval), change[NSKeyValueChangeKindKey], + "Remove change is a removal"); + PASS_EQUAL([NSSet setWithObject:@1], change[NSKeyValueChangeOldKey], + "Remove old key is correct"); + PASS(change[NSKeyValueChangeNewKey] == nil, "Remove new key is nil"); + }; + + // Intersect with @({@2}) to get @({2}) + ChangeCallback intersectCallback = CHANGE_CB + { + PASS_EQUAL(@(NSKeyValueChangeRemoval), change[NSKeyValueChangeKindKey], + "Intersect change is a removal"); + NSSet *expected = [NSSet setWithObject:@3]; + PASS_EQUAL(expected, change[NSKeyValueChangeOldKey], + "Intersect old key is correct"); + PASS(change[NSKeyValueChangeNewKey] == nil, "Intersect new key is nil"); + }; + + // Set with @({@3}) to get @({@3}) + ChangeCallback setCallback = CHANGE_CB + { + if (setSetChanged) + { + PASS_EQUAL(@(NSKeyValueChangeReplacement), + change[NSKeyValueChangeKindKey], + "Set change is a replacement"); + PASS_EQUAL([NSSet setWithObject:@2], change[NSKeyValueChangeOldKey], + "Set old key is correct"); + PASS_EQUAL([NSSet setWithObject:@3], change[NSKeyValueChangeNewKey], + "Set new key is correct"); + } + // setXxx method is not automatically swizzled for observation + else + { + PASS_EQUAL(@(NSKeyValueChangeSetting), change[NSKeyValueChangeKindKey], + "Set change is a setting"); + PASS_EQUAL([NSSet setWithObject:@3], change[NSKeyValueChangeOldKey], + "Set old key is correct"); + PASS_EQUAL([NSSet setWithObject:@3], change[NSKeyValueChangeNewKey], + "Set new key is correct"); + } + }; + + ChangeCallback illegalChangeNotification = CHANGE_CB + { + PASS(NO, "illegalChangeNotification"); + }; + + Observee *observee = [Observee new]; + TestFacade *facade = [TestFacade newWithObservee:observee]; + + [facade observeKeyPath:@"setWithHelpers" + withOptions:NSKeyValueObservingOptionNew + | NSKeyValueObservingOptionOld + performingBlock:^(Observee *observee) { + // This set is assisted by setter functions, and should also + // dispatch one notification per change. + [observee + addSetWithHelpers:[NSSet setWithObjects:@1, @2, @3, nil]]; + [observee removeSetWithHelpers:[NSSet setWithObject:@1]]; + [observee addSetWithHelpersObject:@1]; + [observee removeSetWithHelpersObject:@1]; + [observee intersectSetWithHelpers:[NSSet setWithObject:@2]]; + [observee setSetWithHelpers:[NSSet setWithObject:@3]]; + } + andExpectChangeCallbacks:@[ + unionCallback, minusCallback, addCallback, removeCallback, + intersectCallback, setCallback, illegalChangeNotification + ]]; + PASS([facade hits] == 6, "All six notifications were sent (setWithHelpers)"); + + setSetChanged = YES; + + observee = [Observee new]; + facade = [TestFacade newWithObservee:observee]; + + [facade observeKeyPath:@"kvcMediatedSet" + withOptions:NSKeyValueObservingOptionNew + | NSKeyValueObservingOptionOld + performingBlock:^(Observee *observee) { + // Proxy mutable set should dispatch one notification per change + // The proxy set is a NSKeyValueIvarMutableSet + NSMutableSet *proxySet = + [observee mutableSetValueForKey:@"kvcMediatedSet"]; + [proxySet unionSet:[NSSet setWithObjects:@1, @2, @3, nil]]; + [proxySet minusSet:[NSSet setWithObject:@1]]; + [proxySet addObject:@1]; + [proxySet removeObject:@1]; + [proxySet intersectSet:[NSSet setWithObject:@2]]; + [proxySet setSet:[NSSet setWithObject:@3]]; + } + andExpectChangeCallbacks:@[ + unionCallback, minusCallback, addCallback, removeCallback, + intersectCallback, setCallback, illegalChangeNotification + ]]; + PASS([facade hits] == 6, "All six notifications were sent (kvcMediatedSet)"); + + observee = [Observee new]; + facade = [TestFacade newWithObservee:observee]; + + [facade observeKeyPath:@"manualNotificationSet" + withOptions:NSKeyValueObservingOptionNew + | NSKeyValueObservingOptionOld + performingBlock:^(Observee *observee) { + // Manually should dispatch one notification per change + [observee manualUnionSet:[NSSet setWithObjects:@1, @2, @3, nil]]; + [observee manualMinusSet:[NSSet setWithObject:@1]]; + [observee manualSetAddObject:@1]; + [observee manualSetRemoveObject:@1]; + [observee manualIntersectSet:[NSSet setWithObject:@2]]; + [observee manualSetSet:[NSSet setWithObject:@3]]; + } + andExpectChangeCallbacks:@[ + unionCallback, minusCallback, addCallback, removeCallback, + intersectCallback, setCallback, illegalChangeNotification + ]]; + PASS([facade hits] == 6, + "All six notifications were sent (manualNotificationSet)"); + + /* Indirect proxy (addObject, etc.) to test + * NSKeyValueFastMutableSet */ + [facade observeKeyPath:@"proxySet" + withOptions:NSKeyValueObservingOptionNew + | NSKeyValueObservingOptionOld + performingBlock:^(Observee *observee) { + // Proxy mutable set should dispatch one notification per change + // The proxy set is a NSKeyValueIvarMutableSet + NSMutableSet *proxySet = + [observee mutableSetValueForKey:@"proxySet"]; + [proxySet unionSet:[NSSet setWithObjects:@1, @2, @3, nil]]; + [proxySet minusSet:[NSSet setWithObject:@1]]; + [proxySet addObject:@1]; + [proxySet removeObject:@1]; + [proxySet intersectSet:[NSSet setWithObject:@2]]; + [proxySet setSet:[NSSet setWithObject:@3]]; + } + andExpectChangeCallbacks:@[ + unionCallback, minusCallback, addCallback, removeCallback, + intersectCallback, setCallback, illegalChangeNotification + ]]; + PASS([facade hits] == 6, "All six notifications were sent (proxySet)"); + + /* Indirect slow proxy via NSInvocation to test NSKeyValueSlowMutableSet */ + /* Indirect proxy (addObject, etc.) to test + * NSKeyValueFastMutableSet */ + [facade observeKeyPath:@"proxyRoSet" + withOptions:NSKeyValueObservingOptionNew + | NSKeyValueObservingOptionOld + performingBlock:^(Observee *observee) { + NSMutableSet *proxySet = + [observee mutableSetValueForKey:@"proxyRoSet"]; + [proxySet unionSet:[NSSet setWithObjects:@1, @2, @3, nil]]; + [proxySet minusSet:[NSSet setWithObject:@1]]; + [proxySet addObject:@1]; + [proxySet removeObject:@1]; + [proxySet intersectSet:[NSSet setWithObject:@2]]; + [proxySet setSet:[NSSet setWithObject:@3]]; + } + andExpectChangeCallbacks:@[ + unionCallback, minusCallback, addCallback, removeCallback, + intersectCallback, setCallback, illegalChangeNotification + ]]; + PASS([facade hits] == 6, "All six notifications were sent (proxySet)"); + + END_SET("NSSetMutationMethods"); +} + +int +main(int argc, char *argv[]) +{ + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + + ToMany_NoNotificationOnBareArray(); + ToMany_NotifyingArray(); + ToMany_KVCMediatedArrayWithHelpers_AggregateFunction(); + + ToMany_ToOne_ShouldDowngradeForOrderedObservation(); + ObserverInformationShouldNotLeak(); + + NSArrayShouldNotBeObservable(); + NSArrayShouldThrowWhenTryingToObserveIndexesOutOfRange(); + NSArrayObserveElements(); + + NSSetShouldNotBeObservable(); + NSSetMutationMethods(); + + DESTROY(pool); + + return 0; +} + +#else + +int +main(int argc, const char *argv[]) +{ + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + + NSLog(@"This test requires an Objective-C 2.0 runtime and is not supported " + @"on this platform."); + + DESTROY(pool); + + return 0; +} + +#endif \ No newline at end of file diff --git a/Tests/base/NSKVOSupport/newoldvalues.m b/Tests/base/NSKVOSupport/newoldvalues.m index 0fac8b401..7e9254556 100644 --- a/Tests/base/NSKVOSupport/newoldvalues.m +++ b/Tests/base/NSKVOSupport/newoldvalues.m @@ -101,7 +101,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath int main(int argc, char *argv[]) { - [NSAutoreleasePool new]; + NSAutoreleasePool *arp = [NSAutoreleasePool new]; Bar *bar = [Bar new]; bar.x = 0; @@ -134,6 +134,8 @@ - (void)observeValueForKeyPath:(NSString *)keyPath bar.firstFoo.a = 2; PASS(obs1.receivedCalls == 3, "num observe calls"); PASS(obs2.receivedCalls == 2, "num observe calls"); + + DESTROY(arp); } #else From a25ac65f9b49ca1f0dc4250160c8ddfc2efabfa0 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 16:01:17 +0200 Subject: [PATCH 11/46] NSKeyValueCoding: Change notifications when changing value via setValue:forKey: --- Source/NSKeyValueCoding.m | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Source/NSKeyValueCoding.m b/Source/NSKeyValueCoding.m index 05e73917e..fefd44d6d 100644 --- a/Source/NSKeyValueCoding.m +++ b/Source/NSKeyValueCoding.m @@ -141,6 +141,7 @@ static inline void setupCompat() } } } + GSObjCSetVal(self, key, anObject, sel, type, size, off); } @@ -346,6 +347,8 @@ - (void) setValue: (id)anObject forKey: (NSString*)aKey { 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 @@ - (void) setValue: (id)anObject forKey: (NSString*)aKey maxLength: size + 1 encoding: NSUTF8StringEncoding]; size = strlen(key); + + if (shouldNotify) + { + [self willChangeValueForKey: aKey]; + } + SetValueForKey(self, anObject, key, size); + + if (shouldNotify) + { + [self didChangeValueForKey: aKey]; + } } From d957cb82da8cabe5938c47e49d1f5dda6f231823 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 16:02:09 +0200 Subject: [PATCH 12/46] NSKVOSupport: Add more tests --- Tests/base/NSKVOSupport/general.m | 1917 +++++++++++++++++++++++++++++ 1 file changed, 1917 insertions(+) create mode 100644 Tests/base/NSKVOSupport/general.m diff --git a/Tests/base/NSKVOSupport/general.m b/Tests/base/NSKVOSupport/general.m new file mode 100644 index 000000000..ab76cacfa --- /dev/null +++ b/Tests/base/NSKVOSupport/general.m @@ -0,0 +1,1917 @@ +/** + general.m + + Copyright (C) 2024 Free Software Foundation, Inc. + + Written by: Hugo Melder + 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 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. +*/ + +#import +#import "Testing.h" + +#if defined(__OBJC2__) + +#define PASS_ANY_THROW(expr, msg) \ + do \ + { \ + BOOL threw = NO; \ + @try \ + { \ + expr; \ + } \ + @catch (NSException * exception) \ + { \ + threw = YES; \ + } \ + PASS(threw, msg); \ + } while (0) + +@interface TestKVOSelfObserver : NSObject +{ + id _dummy; +} +@end +@implementation TestKVOSelfObserver +- (id)init +{ + if (self = [super init]) + { + [self addObserver:self forKeyPath:@"dummy" options:0 context:nil]; + } + return self; +} +- (void)dealloc +{ + [self removeObserver:self forKeyPath:@"dummy"]; + [super dealloc]; +} +@end + +@interface TestKVOChange : NSObject +@property (nonatomic, copy) NSString *keypath; +@property (nonatomic, assign /*weak but no arc*/) id object; +@property (nonatomic, copy) NSDictionary *info; +@property (nonatomic, assign) void *context; +@end +@implementation TestKVOChange ++ (id)changeWithKeypath:(NSString *)keypath + object:(id)object + info:(NSDictionary *)info + context:(void *)context +{ + TestKVOChange *change = [[[self alloc] init] autorelease]; + change.keypath = keypath; + change.object = object; + change.info = info; + change.context = context; + return change; +} +@end + +@interface TestKVOObserver : NSObject +{ + NSMutableDictionary *_changedKeypaths; +} +- (void)observeValueForKeyPath:(NSString *)keypath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context; +- (NSSet *)changesForKeypath:(NSString *)keypath; +- (NSInteger)numberOfObservedChanges; +@end + +@implementation TestKVOObserver +- (id)init +{ + if (self = [super init]) + { + _changedKeypaths = [NSMutableDictionary dictionary]; + } + return self; +} +- (void)observeValueForKeyPath:(NSString *)keypath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + @synchronized(self) + { + NSMutableSet *changeSet = _changedKeypaths[keypath]; + if (!changeSet) + { + changeSet = [NSMutableSet set]; + _changedKeypaths[keypath] = changeSet; + } + [changeSet addObject:[TestKVOChange changeWithKeypath:keypath + object:object + info:change + context:context]]; + } +} +- (NSSet *)changesForKeypath:(NSString *)keypath +{ + @synchronized(self) + { + return [_changedKeypaths[keypath] copy]; + } +} +- (void)clear +{ + @synchronized(self) + { + [_changedKeypaths removeAllObjects]; + } +} +- (NSInteger)numberOfObservedChanges +{ + @synchronized(self) + { + NSInteger accumulator = 0; + for (NSString *keypath in [_changedKeypaths allKeys]) + { + accumulator += [[_changedKeypaths objectForKey:keypath] count]; + } + return accumulator; + } +} +@end + +struct TestKVOStruct +{ + int a, b, c; +}; + +@interface TestKVOObject : NSObject +{ + NSString *_internal_derivedObjectProperty; + NSString *_internal_keyDerivedTwoTimes; + int _manuallyNotifyingIntegerProperty; + int _ivarWithoutSetter; +} + +@property (nonatomic, retain) NSString *nonNotifyingObjectProperty; + +@property (nonatomic, retain) NSString *basicObjectProperty; +@property (nonatomic, assign) uint32_t basicPodProperty; +@property (nonatomic, assign) struct TestKVOStruct structProperty; + +// derivedObjectProperty is derived from basicObjectProperty. +@property (nonatomic, readonly) NSString *derivedObjectProperty; + +@property (nonatomic, retain) TestKVOObject *cascadableKey; +@property (nonatomic, readonly) TestKVOObject *derivedCascadableKey; + +@property (nonatomic, retain) id recursiveDependent1; +@property (nonatomic, retain) id recursiveDependent2; + +@property (nonatomic, retain) NSMutableDictionary *dictionaryProperty; + +@property (nonatomic, retain) id boolTrigger1; +@property (nonatomic, retain) id boolTrigger2; +@property (nonatomic, readonly) bool dependsOnTwoKeys; + +// This modifies the internal integer property and notifies about it. +- (void)incrementManualIntegerProperty; +@end + +@implementation TestKVOObject +- (void)dealloc +{ + [_cascadableKey release]; + [_nonNotifyingObjectProperty release]; + [_basicObjectProperty release]; + [_recursiveDependent1 release]; + [_recursiveDependent2 release]; + [_dictionaryProperty release]; + [_boolTrigger1 release]; + [_boolTrigger2 release]; + [super dealloc]; +} + ++ (NSSet *)keyPathsForValuesAffectingDerivedObjectProperty +{ + return [NSSet setWithObject:@"basicObjectProperty"]; +} + ++ (NSSet *)keyPathsForValuesAffectingRecursiveDependent1 +{ + return [NSSet setWithObject:@"recursiveDependent2"]; +} + ++ (NSSet *)keyPathsForValuesAffectingRecursiveDependent2 +{ + return [NSSet setWithObject:@"recursiveDependent1"]; +} + ++ (NSSet *)keyPathsForValuesAffectingDerivedCascadableKey +{ + return [NSSet setWithObject:@"cascadableKey"]; +} + ++ (NSSet *)keyPathsForValuesAffectingKeyDependentOnSubKeypath +{ + return [NSSet setWithObject:@"dictionaryProperty.subDictionary"]; +} + ++ (NSSet *)keyPathsForValuesAffectingKeyDerivedTwoTimes +{ + return [NSSet setWithObject:@"derivedObjectProperty"]; +} + ++ (NSSet *)keyPathsForValuesAffectingDependsOnTwoKeys +{ + return [NSSet setWithArray:@[ @"boolTrigger1", @"boolTrigger2" ]]; +} + ++ (NSSet *)keyPathsForValuesAffectingDependsOnTwoSubKeys +{ + return [NSSet setWithArray:@[ + @"cascadableKey.boolTrigger1", @"cascadableKey.boolTrigger2" + ]]; +} + +- (bool)dependsOnTwoKeys +{ + return _boolTrigger1 != nil && _boolTrigger2 != nil; +} + +- (bool)dependsOnTwoSubKeys +{ + return _cascadableKey.boolTrigger1 != nil + && _cascadableKey.boolTrigger2 != nil; +} + +- (id)keyDependentOnSubKeypath +{ + return _dictionaryProperty[@"subDictionary"]; +} + ++ (BOOL)automaticallyNotifiesObserversOfManuallyNotifyingIntegerProperty +{ + return NO; +} + ++ (BOOL)automaticallyNotifiesObserversOfNonNotifyingObjectProperty +{ + return NO; +} + +- (NSString *)derivedObjectProperty +{ + return _internal_derivedObjectProperty; +} + +- (void)setBasicObjectProperty:(NSString *)basicObjectProperty +{ + [_basicObjectProperty release]; + _basicObjectProperty = [basicObjectProperty retain]; + _internal_derivedObjectProperty = + [NSString stringWithFormat:@"!!!%@!!!", _basicObjectProperty]; + _internal_keyDerivedTwoTimes = + [NSString stringWithFormat:@"---%@---", [self derivedObjectProperty]]; +} + +- (NSString *)keyDerivedTwoTimes +{ + return _internal_keyDerivedTwoTimes; +} + +- (TestKVOObject *)derivedCascadableKey +{ + return _cascadableKey; +} + +- (void)incrementManualIntegerProperty +{ + [self willChangeValueForKey:@"manuallyNotifyingIntegerProperty"]; + _manuallyNotifyingIntegerProperty++; + [self didChangeValueForKey:@"manuallyNotifyingIntegerProperty"]; +} +@end + +@interface TestKVOObject2 : NSObject +@property (nonatomic, assign) float someFloat; +@end +@implementation TestKVOObject2 +@end + +static void +BasicChangeNotification() +{ + START_SET("BasicChangeNotification"); + + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + observed.basicObjectProperty = @"Hello"; + + PASS_EQUAL([[observer changesForKeypath:@"basicObjectProperty"] count], 1, + "One change on basicObjectProperty should have fired."); + PASS_EQUAL([[observer changesForKeypath:@"basicPodProperty"] count], 0, + "Zero changes on basicPodProperty should have fired."); + PASS_EQUAL([[observer changesForKeypath:@"derivedObjectProperty"] count], 0, + "Zero changes on derivedObjectProperty should have fired."); + + PASS_EQUAL([[[observer changesForKeypath:@"basicObjectProperty"] anyObject] + object], + observed, + "The notification object should match the observed object."); + PASS_EQUAL( + nil, + [[[[observer changesForKeypath:@"basicObjectProperty"] anyObject] info] + objectForKey:NSKeyValueChangeOldKey], + "There should be no old value included in the change notification."); + PASS_EQUAL( + [[[[observer changesForKeypath:@"basicObjectProperty"] anyObject] info] + objectForKey:NSKeyValueChangeNewKey], + @"Hello", + "The new value stored in the change notification should be Hello."); + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"basicObjectProperty"], + "remove observer should not throw"); + + END_SET("BasicChangeNotification"); +} + +static void +ExclusiveChangeNotification() +{ + START_SET("ExclusiveChangeNotification"); + + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + TestKVOObserver *observer2 = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + [observed addObserver:observer2 + forKeyPath:@"basicPodProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + + [observed setBasicObjectProperty:@"Hello"]; + [observed setBasicPodProperty:1]; + + PASS_EQUAL([[observer changesForKeypath:@"basicObjectProperty"] count], 1, + "One change on basicObjectProperty should have fired."); + PASS_EQUAL( + [[observer2 changesForKeypath:@"basicObjectProperty"] count], 0, + "No changes on basicObjectProperty for second observer should have fired."); + PASS_EQUAL([[observer2 changesForKeypath:@"basicPodProperty"] count], 1, + "One change on basicPodProperty should have fired."); + PASS_EQUAL( + [[observer changesForKeypath:@"basicPodProperty"] count], 0, + "No changes on basicPodProperty for first observer should have fired."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"basicObjectProperty"], + "remove observer should not throw"); + PASS_RUNS([observed removeObserver:observer2 forKeyPath:@"basicPodProperty"], + "remove observer should not throw"); + + END_SET("ExclusiveChangeNotification"); +} + +static void +ManualChangeNotification() +{ + START_SET("ManualChangeNotification"); + + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"manuallyNotifyingIntegerProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + [observed incrementManualIntegerProperty]; + + PASS_EQUAL( + [[observer changesForKeypath:@"manuallyNotifyingIntegerProperty"] count], 1, + "One change on manuallyNotifyingIntegerProperty should have fired."); + PASS_EQUAL( + [[[[observer changesForKeypath:@"manuallyNotifyingIntegerProperty"] + anyObject] info] objectForKey:NSKeyValueChangeNewKey], + @(1), + "The new value stored in the change notification should be a boxed 1."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"manuallyNotifyingIntegerProperty"], + "remove observer should not throw"); + + END_SET("ManualChangeNotification"); +} + +static void +BasicChangeCaptureOld() +{ + START_SET("BasicChangeCaptureOld"); + + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionOld + context:NULL]; + observed.basicObjectProperty = @"Hello"; + + PASS_EQUAL([[observer changesForKeypath:@"basicObjectProperty"] count], 1, + "One change on basicObjectProperty should have fired."); + + PASS_EQUAL([[[[observer changesForKeypath:@"basicObjectProperty"] anyObject] + info] objectForKey:NSKeyValueChangeOldKey], + [NSNull null], + "The old value stored in the change notification should be null."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"basicObjectProperty"], + "remove observer should not throw"); + + END_SET("BasicChangeCaptureOld"); +} + +static void +CascadingNotificationWithEmptyLeaf() +{ + START_SET("CascadingNotificationWithEmptyLeaf"); + + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed + addObserver:observer + forKeyPath:@"cascadableKey.basicObjectProperty" + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + TestKVOObject *subObject = [[[TestKVOObject alloc] init] autorelease]; + subObject.basicObjectProperty = @"Hello"; + observed.cascadableKey = subObject; + + PASS_EQUAL( + [[observer changesForKeypath:@"cascadableKey.basicObjectProperty"] count], + 1, "One change on cascadableKey.basicObjectProperty should have fired."); + + PASS_EQUAL([[[[observer + changesForKeypath:@"cascadableKey.basicObjectProperty"] + anyObject] info] objectForKey:NSKeyValueChangeOldKey], + [NSNull null], + "The old value stored in the change notification should be null."); + + [observer clear]; + + TestKVOObject *subObject2 = [[[TestKVOObject alloc] init] autorelease]; + subObject2.basicObjectProperty = @"Hello"; + observed.cascadableKey = subObject2; + + PASS_EQUAL( + [[observer changesForKeypath:@"cascadableKey.basicObjectProperty"] count], + 1, + "A second change on cascadableKey.basicObjectProperty should have fired."); + + subObject.basicObjectProperty = @"Spurious?"; + + PASS(2 != + [[observer changesForKeypath:@"cascadableKey.basicObjectProperty"] + count], + "A change to the detached subkey should not have triggered a spurious " + "notification."); + + PASS_EQUAL( + [[[[observer changesForKeypath:@"cascadableKey.basicObjectProperty"] + anyObject] info] objectForKey:NSKeyValueChangeOldKey], + @"Hello", + "The old value stored in the change notification should be Hello."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"cascadableKey.basicObjectProperty"], + "remove observer should not throw"); + + END_SET("CascadingNotificationWithEmptyLeaf"); +} + +static void +PriorNotification() +{ + START_SET("PriorNotification"); + + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed + addObserver:observer + forKeyPath:@"basicObjectProperty" + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior) + context:NULL]; + observed.basicObjectProperty = @"Hello"; + + PASS_EQUAL( + [[observer changesForKeypath:@"basicObjectProperty"] count], 2, + "Two changes on basicObjectProperty should have fired (one prior change)."); + + PASS_EQUAL( + [[[[observer changesForKeypath:@"basicObjectProperty"] anyObject] info] + objectForKey:NSKeyValueChangeOldKey], + [NSNull null], + "The old value stored in the change notification should be null or nil."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"basicObjectProperty"], + "remove observer should not throw"); + + END_SET("PriorNotification"); +} + +static void +DependentKeyNotification() +{ + START_SET("DependentKeyNotification"); + + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"derivedObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + observed.basicObjectProperty = @"Hello"; + + PASS_EQUAL([[observer changesForKeypath:@"basicObjectProperty"] count], 0, + "No changes on basicObjectProperty should have fired (we did not " + "register for it)."); + PASS_EQUAL([[observer changesForKeypath:@"derivedObjectProperty"] count], 1, + "One change on derivedObjectProperty should have fired."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"derivedObjectProperty"], + "remove observer should not throw"); + + PASS_EQUAL([[[[observer changesForKeypath:@"derivedObjectProperty"] anyObject] + info] objectForKey:NSKeyValueChangeNewKey], + @"!!!Hello!!!", + "The new value stored in the change notification should be " + "!!!Hello!!! (the derived object)."); + + END_SET("DependentKeyNotification"); +} + +static void +PODNotification() +{ + START_SET("PODNotification"); + + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"basicPodProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + observed.basicPodProperty = 10; + + PASS_EQUAL([[observer changesForKeypath:@"basicPodProperty"] count], 1, + "One change on basicPodProperty should have fired."); + + PASS([[[[[observer changesForKeypath:@"basicPodProperty"] anyObject] info] + objectForKey:NSKeyValueChangeNewKey] isKindOfClass:[NSNumber class]], + "The new value stored in the change notification should be an NSNumber " + "instance."); + PASS_EQUAL( + [[[[observer changesForKeypath:@"basicPodProperty"] anyObject] info] + objectForKey:NSKeyValueChangeNewKey], + @(10), + "The new value stored in the change notification should be a boxed 10."); + + PASS_RUNS([observed removeObserver:observer forKeyPath:@"basicPodProperty"], + "remove observer should not throw"); + + END_SET("PODNotification"); +} + +static void +StructNotification() +{ // Basic change notification on a struct type + START_SET("StructNotification"); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + PASS_EQUAL([[observer changesForKeypath:@"basicObjectProperty"] count], 0, + "No changes on basicObjectProperty should have fired."); + [observed addObserver:observer + forKeyPath:@"structProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + struct TestKVOStruct structValue = {1, 2, 3}; + observed.structProperty = structValue; + + PASS_EQUAL([[observer changesForKeypath:@"structProperty"] count], 1, + "One change on structProperty should have fired."); + + PASS(YES == + [[[[[observer changesForKeypath:@"structProperty"] anyObject] info] + objectForKey:NSKeyValueChangeNewKey] isKindOfClass:[NSValue class]], + "The new value stored in the change notification should be " + "an NSValue instance."); + PASS(strcmp([[[[[observer changesForKeypath:@"structProperty"] anyObject] + info] objectForKey:NSKeyValueChangeNewKey] objCType], + @encode(struct TestKVOStruct)) + == 0, + "The new objc type stored in the change notification should have " + "an objc type matching our Struct."); + + PASS_RUNS([observed removeObserver:observer forKeyPath:@"structProperty"], + "remove observer should not throw"); + PASS_RUNS([pool release], "release should not throw"); + + END_SET("StructNotification"); +} + +static void +DisabledNotification() +{ // No notification for non-notifying keypaths. + START_SET("DisabledNotification"); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"nonNotifyingObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + observed.nonNotifyingObjectProperty = @"Whatever"; + + PASS_EQUAL([[observer changesForKeypath:@"nonNotifyingObjectProperty"] count], + 0, "No changes for nonNotifyingObjectProperty should have fired."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"nonNotifyingObjectProperty"], + "remove observer should not throw"); + PASS_RUNS([pool release], "release should not throw"); + + END_SET("DisabledNotification"); +} + +static void +DisabledInitialNotification() +{ // Initial notification for non-notifying keypaths. + START_SET("DisabledInitialNotification"); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"nonNotifyingObjectProperty" + options:NSKeyValueObservingOptionInitial + context:NULL]; + observed.nonNotifyingObjectProperty = @"Whatever"; + + PASS_EQUAL([[observer changesForKeypath:@"nonNotifyingObjectProperty"] count], + 1, + "An INITIAL notification for nonNotifyingObjectProperty should " + "have fired."); + + PASS_EQUAL(@(NSKeyValueChangeSetting), + [[[[observer changesForKeypath:@"nonNotifyingObjectProperty"] + anyObject] info] objectForKey:NSKeyValueChangeKindKey], + "The change kind should be NSKeyValueChangeSetting."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"nonNotifyingObjectProperty"], + "remove observer should not throw"); + PASS_RUNS([pool release], "release should not throw"); + + END_SET("DisabledInitialNotification"); +} + +static void +SetValueForKeyIvarNotification() +{ // Notification of ivar change through setValue:forKey: + START_SET("SetValueForKeyIvarNotification"); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"ivarWithoutSetter" + options:NSKeyValueObservingOptionNew + context:NULL]; + [observed setValue:@(1024) forKey:@"ivarWithoutSetter"]; + + PASS_EQUAL([[observer changesForKeypath:@"ivarWithoutSetter"] count], 1, + "One change on ivarWithoutSetter should have fired (using " + "setValue:forKey:)."); + + PASS_EQUAL( + [[[[observer changesForKeypath:@"ivarWithoutSetter"] anyObject] info] + objectForKey:NSKeyValueChangeNewKey], + @(1024), + "The new value stored in the change notification should a boxed 1024."); + + PASS_RUNS([observed removeObserver:observer forKeyPath:@"ivarWithoutSetter"], + "remove observer should not throw"); + PASS_RUNS([pool release], "release should not throw"); + + END_SET("SetValueForKeyIvarNotification"); +} + +static void +DictionaryNotification() +{ // Basic notification on a dictionary, which does not have properties or + // ivars. + START_SET("DictionaryNotification"); + + NSMutableDictionary *observed = [NSMutableDictionary dictionary]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed setObject:[[[TestKVOObject alloc] init] autorelease] + forKey:@"subKey"]; + + [observed addObserver:observer + forKeyPath:@"arbitraryValue" + options:NSKeyValueObservingOptionNew + context:NULL]; + [observed addObserver:observer + forKeyPath:@"subKey.basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + + [observed setObject:@"Whatever" forKey:@"arbitraryValue"]; + [observed setValue:@"Whatever2" forKeyPath:@"arbitraryValue"]; + [observed setValue:@"Whatever2" forKeyPath:@"subKey.basicObjectProperty"]; + + PASS_EQUAL( + [[observer changesForKeypath:@"arbitraryValue"] count], 2, + "On a NSMutableDictionary, a change notification for arbitraryValue."); + PASS_EQUAL([[observer changesForKeypath:@"subKey.basicObjectProperty"] count], + 1, + "On a NSMutableDictionary, a change notification for " + "subKey.basicObjectProperty."); + + PASS_RUNS([observed removeObserver:observer forKeyPath:@"arbitraryValue"], + "remove observer should not throw"); + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"subKey.basicObjectProperty"], + "remove observer should not throw"); + + END_SET("DictionaryNotification"); +} + +static void +BasicDeregistration() +{ // Deregistration test + START_SET("BasicDeregistration"); + + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"basicObjectProperty" + context:NULL], + "remove observer should not throw"); + observed.basicObjectProperty = @"Hello"; + + PASS_EQUAL([[observer changesForKeypath:@"basicObjectProperty"] count], 0, + "No changes on basicObjectProperty should have fired."); + + TestKVOObject *subObject = [[[TestKVOObject alloc] init] autorelease]; + observed.cascadableKey = subObject; + + [observed addObserver:observer + forKeyPath:@"cascadableKey.basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"cascadableKey.basicObjectProperty" + context:NULL], + "remove observer should not throw"); + + subObject.basicObjectProperty = @"Hello"; + + PASS_EQUAL( + [[observer changesForKeypath:@"cascadableKey.basicObjectProperty"] count], + 0, "No changes on cascadableKey.basicObjectProperty should have fired."); + + END_SET("BasicDeregistration"); +} + +static void +DerivedKeyOnSubpath1() +{ + START_SET("DerivedKeyOnSubpath1"); + + TestKVOObject *observed = [[TestKVOObject alloc] init]; + TestKVOObserver *observer = [[TestKVOObserver alloc] init]; + + [observed addObserver:observer + forKeyPath:@"cascadableKey.derivedObjectProperty.length" + options:NSKeyValueObservingOptionNew + context:NULL]; + + TestKVOObject *subObject = [[TestKVOObject alloc] init]; + subObject.basicObjectProperty = @"Hello"; + observed.cascadableKey = subObject; + + PASS_EQUAL([[observer + changesForKeypath:@"cascadableKey.derivedObjectProperty.length"] + count], + 1, "One change on cascade.derived.length should have fired."); + PASS_EQUAL( + [[[[observer + changesForKeypath:@"cascadableKey.derivedObjectProperty.length"] + anyObject] info] objectForKey:NSKeyValueChangeNewKey], + @(11), + "The new value stored in the change notification should a boxed 11."); + + PASS_RUNS([observed + removeObserver:observer + forKeyPath:@"cascadableKey.derivedObjectProperty.length" + context:NULL], + "remove observer should not throw"); + + [observer clear]; + + subObject.basicObjectProperty = @"Whatever"; + + PASS_EQUAL( + [[observer changesForKeypath:@"cascadableKey.derivedObjectProperty.length"] + count], + 0, "No additional changes on cascade.derived.length should have fired."); + + [subObject release]; + [observer release]; + [observed release]; + + END_SET("DerivedKeyOnSubpath1"); +} + +static void +Subpath1() +{ // Test normally-nested observation and value replacement + START_SET("Subpath1"); + + TestKVOObject *observed = [[TestKVOObject alloc] init]; + TestKVOObserver *observer = [[TestKVOObserver alloc] init]; + [observed addObserver:observer + forKeyPath:@"cascadableKey.cascadableKey" + options:0 + context:nil]; + + TestKVOObject *child = [[TestKVOObject alloc] init]; + + [observed setCascadableKey:child]; + [observed setCascadableKey:nil]; + + PASS_EQUAL(2, [observer numberOfObservedChanges], + "Two changes should have been observed."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"cascadableKey.cascadableKey"], + "remove observer should not throw"); + + [child release]; + [observer release]; + [observed release]; + + END_SET("Subpath1"); +} + +static void +SubpathSubpath() +{ // Test deeply-nested observation + START_SET("SubpathSubpath"); + + TestKVOObject *observed = [[TestKVOObject alloc] init]; + TestKVOObserver *observer = [[TestKVOObserver alloc] init]; + [observed addObserver:observer + forKeyPath:@"cascadableKey.cascadableKey.cascadableKey" + options:0 + context:nil]; + + TestKVOObject *child = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObject *childChild = [[[TestKVOObject alloc] init] autorelease]; + + observed.cascadableKey = child; + observed.cascadableKey.cascadableKey = childChild; + observed.cascadableKey.cascadableKey = nil; + observed.cascadableKey = nil; + + PASS_EQUAL(4, [observer numberOfObservedChanges], + "Four changes should have been observed."); + + PASS_RUNS([observed + removeObserver:observer + forKeyPath:@"cascadableKey.cascadableKey.cascadableKey"], + "remove observer should not throw"); + + [observer release]; + [observed release]; + + END_SET("SubpathSubpath"); +} + +static void +SubpathWithHeadReplacement() +{ // Test key value replacement and re-registration (1) + START_SET("SubpathWithHeadReplacement"); + + TestKVOObject *observed = [[TestKVOObject alloc] init]; + TestKVOObserver *observer = [[TestKVOObserver alloc] init]; + + TestKVOObject *child = [[[TestKVOObject alloc] init] autorelease]; + observed.cascadableKey = child; + + [observed addObserver:observer + forKeyPath:@"cascadableKey.cascadableKey" + options:0 + context:nil]; + + [observed setCascadableKey:nil]; + + PASS_EQUAL(1, [observer numberOfObservedChanges], + "One change should have been observed."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"cascadableKey.cascadableKey"], + "remove observer should not throw"); + + [observer release]; + [observed release]; + + END_SET("SubpathWithHeadReplacement"); +} + +static void +SubpathWithTailAndHeadReplacement() +{ // Test key value replacement and re-registration (2) + START_SET("SubpathWithTailAndHeadReplacement"); + + TestKVOObject *observed = [[TestKVOObject alloc] init]; + TestKVOObserver *observer = [[TestKVOObserver alloc] init]; + + TestKVOObject *child = [[[TestKVOObject alloc] init] autorelease]; + observed.cascadableKey = child; + + TestKVOObject *childChild = [[[TestKVOObject alloc] init] autorelease]; + child.cascadableKey = childChild; + + [observed addObserver:observer + forKeyPath:@"cascadableKey.cascadableKey.cascadableKey" + options:0 + context:nil]; + + observed.cascadableKey.cascadableKey = nil; + observed.cascadableKey = nil; + + PASS_EQUAL(2, [observer numberOfObservedChanges], + "Two changes should have been observed."); + + PASS_RUNS([observed + removeObserver:observer + forKeyPath:@"cascadableKey.cascadableKey.cascadableKey"], + "remove observer should not throw"); + + [observer release]; + [observed release]; + + END_SET("SubpathWithTailAndHeadReplacement"); +} + +static void +SubpathWithMultipleReplacement() +{ // Test key value replacement and re-registration (3) + START_SET("SubpathWithMultipleReplacement"); + + TestKVOObject *observed = [[TestKVOObject alloc] init]; + TestKVOObserver *observer = [[TestKVOObserver alloc] init]; + TestKVOObject *child1 = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObject *child2 = [[[TestKVOObject alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"cascadableKey.cascadableKey" + options:0 + context:nil]; + + observed.cascadableKey = child1; + + observed.cascadableKey = child2; + + observed.cascadableKey = nil; + + PASS_EQUAL(3, [observer numberOfObservedChanges], + "Three changes should have been observed."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"cascadableKey.cascadableKey"], + "remove observer should not throw"); + + [observer release]; + [observed release]; + + END_SET("SubpathWithMultipleReplacement"); +} + +static void +SubpathWithMultipleReplacement2() +{ // Test a more complex nested observation system + START_SET("SubpathWithMultipleReplacement2"); + + TestKVOObject *observed = [[TestKVOObject alloc] init]; + TestKVOObserver *observer = [[TestKVOObserver alloc] init]; + TestKVOObject *child1 = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObject *child2 = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObject *child3 = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObject *child4 = [[[TestKVOObject alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"cascadableKey.cascadableKey" + options:0 + context:nil]; + + observed.cascadableKey = child1; + + observed.cascadableKey = nil; + + observed.cascadableKey = child2; + + observed.cascadableKey = nil; + + observed.cascadableKey = child3; + child3.cascadableKey = child4; + + observed.cascadableKey = nil; + + PASS_EQUAL(7, [observer numberOfObservedChanges], + "Seven changes should have " + "been observed."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"cascadableKey.cascadableKey"], + "remove observer should not throw"); + + [observer release]; + [observed release]; + + END_SET("SubpathWithMultipleReplacement2"); +} + +static void +SubpathsWithInitialNotification() +{ // Test initial observation on nested keys + START_SET("SubpathsWithInitialNotification"); + + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + TestKVOObject *child1 = [[[TestKVOObject alloc] init] autorelease]; + observed.cascadableKey = child1; + + [observed + addObserver:observer + forKeyPath:@"cascadableKey.basicObjectProperty" + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:nil]; + [observed + addObserver:observer + forKeyPath:@"cascadableKey.basicPodProperty" + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:nil]; + [observed + addObserver:observer + forKeyPath:@"cascadableKey.derivedObjectProperty" + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:nil]; + + PASS_EQUAL(3, [observer numberOfObservedChanges], + "Three changes should have " + "been observed."); + PASS_EQUAL([NSNull null], + [[[[observer + changesForKeypath:@"cascadableKey.basicObjectProperty"] + anyObject] info] objectForKey:NSKeyValueChangeNewKey], + "The initial value of basicObjectProperty should be nil."); + PASS_EQUAL(@(0), + [[[[observer changesForKeypath:@"cascadableKey.basicPodProperty"] + anyObject] info] objectForKey:NSKeyValueChangeNewKey], + "The initial value of basicPodProperty should be 0."); + PASS_EQUAL([NSNull null], + [[[[observer + changesForKeypath:@"cascadableKey.derivedObjectProperty"] + anyObject] info] objectForKey:NSKeyValueChangeNewKey], + "The initial value of derivedObjectProperty should be nil."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"cascadableKey.basicObjectProperty"], + "remove observer should not throw"); + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"cascadableKey.basicPodProperty"], + "remove observer should not throw"); + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"cascadableKey.derivedObjectProperty"], + "remove observer should not throw"); + + END_SET("SubpathsWithInitialNotification"); +} + +static void +CyclicDependency() +{ // Make sure that dependency loops don't cause crashes. + START_SET("CyclicDependency"); + + TestKVOObject *observed = [[TestKVOObject alloc] init]; + TestKVOObserver *observer = [[TestKVOObserver alloc] init]; + PASS_RUNS([observed addObserver:observer + forKeyPath:@"recursiveDependent1" + options:1 + context:nil], + "add observer should not throw"); + PASS_RUNS([observed addObserver:observer + forKeyPath:@"recursiveDependent2" + options:1 + context:nil], + "add observer should not throw"); + observed.recursiveDependent1 = @"x"; + observed.recursiveDependent2 = @"y"; + PASS_EQUAL(4, [observer numberOfObservedChanges], + "Four changes should have " + "been observed."); + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"recursiveDependent1"], + "remove observer should not throw"); + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"recursiveDependent2"], + "remove observer should not throw"); + + [observer release]; + [observed release]; + + END_SET("CyclicDependency"); +} + +static void +ObserveAllProperties() +{ + START_SET("ObserveAllProperties"); + + TestKVOObject *observed = [[TestKVOObject alloc] init]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + [observed addObserver:observer + forKeyPath:@"basicPodProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + [observed addObserver:observer + forKeyPath:@"structProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + [observed addObserver:observer + forKeyPath:@"derivedObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + [observed addObserver:observer + forKeyPath:@"cascadableKey" + options:NSKeyValueObservingOptionNew + context:NULL]; + [observed addObserver:observer + forKeyPath:@"cascadableKey.basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + + struct TestKVOStruct s = {1, 2, 3}; + + observed.basicObjectProperty = @"WHAT"; // 2 here + observed.basicPodProperty = 10; // 1 + observed.structProperty = s; + + TestKVOObject *subObject = [[[TestKVOObject alloc] init] autorelease]; + subObject.basicObjectProperty = @"Hello"; + observed.cascadableKey = subObject; // 2 here + + PASS_EQUAL([observer numberOfObservedChanges], 6, + "There should have been 6 observed changes on the observer."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"basicObjectProperty"], + "remove observer for keyPath basicObjectProperty should not throw"); + PASS_RUNS([observed removeObserver:observer forKeyPath:@"basicPodProperty"], + "remove observer for keyPath basicPodProperty should not throw"); + PASS_RUNS([observed removeObserver:observer forKeyPath:@"structProperty"], + "remove observer for keyPath structProperty should not throw"); + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"derivedObjectProperty"], + "remove observer should not throw"); + PASS_RUNS([observed removeObserver:observer forKeyPath:@"cascadableKey"], + "remove observer for keyPath cascadableKey should not throw"); + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"cascadableKey.basicObjectProperty"], + "remove observer for keyPath cascadableKey.basicObjectProperty " + "should not throw"); + + END_SET("ObserveAllProperties"); +} + +static void +RemoveWithoutContext() +{ // Test removal without specifying context. + START_SET("RemoveWithoutContext"); + + TestKVOObject *observed = [[TestKVOObject alloc] init]; + TestKVOObserver *observer = [[TestKVOObserver alloc] init]; + + [observed addObserver:observer + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:(void *) (1)]; + [observed addObserver:observer + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:(void *) (2)]; + + PASS_RUNS( + [observed removeObserver:observer forKeyPath:@"basicObjectProperty"], + "removing observer forKeyPath=basicObjectProperty should not throw"); + + observed.basicObjectProperty = @""; + + PASS_EQUAL([observer numberOfObservedChanges], 1, + "There should be only one change notification despite " + "registering two with contexts."); + + PASS_RUNS( + [observed removeObserver:observer forKeyPath:@"basicObjectProperty"], + "removing observer forKeyPath=basicObjectProperty should not throw"); + + [observer release]; + [observed release]; + + END_SET("RemoveWithoutContext"); +} + +static void +RemoveWithDuplicateContext() +{ // Test adding duplicate contexts + START_SET("RemoveWithDuplicateContext"); + + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:(void *) (1)]; + [observed addObserver:observer + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:(void *) (1)]; + + observed.basicObjectProperty = @""; + + PASS_EQUAL([observer numberOfObservedChanges], 2, + "There should be two observed changes, despite the identical " + "registration."); + + PASS_RUNS( + [observed removeObserver:observer + forKeyPath:@"basicObjectProperty" + context:(void *) (1)], + "removing observer forKeyPath=basicObjectProperty should not throw"); + + observed.basicObjectProperty = @""; + + PASS_EQUAL([observer numberOfObservedChanges], 3, + "There should be one additional observed change; the removal " + "should have only effected one."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"basicObjectProperty" + context:(void *) (1)], + "removing observer forKeyPath=basicObjectProperty does not throw"); + + END_SET("RemoveWithDuplicateContext"); +} + +static void +RemoveOneOfTwoObservers() +{ // Test adding duplicate contexts + START_SET("RemoveOneOfTwoObservers"); + + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + TestKVOObserver *observer2 = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + [observed addObserver:observer2 + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + + observed.basicObjectProperty = @""; + + PASS_EQUAL([observer numberOfObservedChanges], 1, + "There should be one observed change per observer."); + PASS_EQUAL([observer2 numberOfObservedChanges], 1, + "There should be one observed change per observer."); + + PASS_RUNS([observed removeObserver:observer2 + forKeyPath:@"basicObjectProperty"], + "removing observer2 should not throw"); + + observed.basicObjectProperty = @""; + + PASS_EQUAL([observer numberOfObservedChanges], 2, + "There should be one additional observed change; the removal " + "should have only removed the second observer."); + + PASS_EQUAL([observer2 numberOfObservedChanges], 1, + "Observer2 should have only observed one change."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"basicObjectProperty"], + "removing observer should not throw"); + + END_SET("RemoveOneOfTwoObservers"); +} + +static void +RemoveUnregistered() +{ // Test removing an urnegistered observer + START_SET("RemoveUnregistered"); + + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + PASS_ANY_THROW( + [observed removeObserver:observer + forKeyPath:@"basicObjectProperty" + context:(void *) (1)], + "Removing an unregistered observer should throw an exception."); + + END_SET("RemoveUnregistered"); +} + +static void +SelfObservationDealloc() +{ // Test deallocation of an object that is its own observer + TestKVOSelfObserver *observed = [[TestKVOSelfObserver alloc] init]; + PASS_RUNS([observed release], "deallocating self-observing object should not " + "throw"); +} + +static void +DeepSubpathWithCompleteTree() +{ + START_SET("DeepSubpathWithCompleteTree"); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TestKVOObject2 *floatGuy = [[[TestKVOObject2 alloc] init] autorelease]; + floatGuy.someFloat = 1.234f; + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObject *child = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + child.dictionaryProperty = [NSMutableDictionary + dictionaryWithObjectsAndKeys:floatGuy, @"floatGuy", nil]; + observed.cascadableKey = child; + [observed addObserver:observer + forKeyPath:@"cascadableKey.dictionaryProperty.floatGuy.someFloat" + options:0 + context:nil]; + observed.cascadableKey = child; + PASS_EQUAL([observer numberOfObservedChanges], 1, + "One change should have " + "been observed."); + + PASS_RUNS( + [observed + removeObserver:observer + forKeyPath:@"cascadableKey.dictionaryProperty.floatGuy.someFloat"], + "remove observer should not throw"); + PASS_RUNS([pool release], "release pool should not throw"); + + END_SET("DeepSubpathWithCompleteTree"); +} + +static void +DeepSubpathWithIncompleteTree() +{ + START_SET("DeepSubpathWithIncompleteTree"); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + // The same test as above, but testing nil value reconstitution to ensure that + // the keypath is wired up properly. + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + [observed addObserver:observer + forKeyPath:@"cascadableKey.dictionaryProperty.floatGuy.someFloat" + options:0 + context:nil]; + + TestKVOObject2 *floatGuy = [[[TestKVOObject2 alloc] init] autorelease]; + floatGuy.someFloat = 1.234f; + TestKVOObject *child = [[[TestKVOObject alloc] init] autorelease]; + child.dictionaryProperty = [NSMutableDictionary + dictionaryWithObjectsAndKeys:floatGuy, @"floatGuy", nil]; + + observed.cascadableKey = child; + observed.cascadableKey = child; + + PASS_EQUAL([observer numberOfObservedChanges], 2, + "Two changes should have " + "been observed."); + + PASS_RUNS( + [observed + removeObserver:observer + forKeyPath:@"cascadableKey.dictionaryProperty.floatGuy.someFloat"], + "remove observer should not throw"); + PASS_RUNS([pool release], "release pool should not throw"); + + END_SET("DeepSubpathWithIncompleteTree"); +} + +static void +SubpathOnDerivedKey() +{ + START_SET("SubpathOnDerivedKey"); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObject *child = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObject *child2 = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + observed.cascadableKey = child; + child.dictionaryProperty = + [NSMutableDictionary dictionaryWithDictionary:@{@"Key1" : @"Value1"}]; + + [observed addObserver:observer + forKeyPath:@"derivedCascadableKey.dictionaryProperty.Key1" + options:0 + context:nil]; + + observed.cascadableKey = child2; + child2.dictionaryProperty = + [NSMutableDictionary dictionaryWithDictionary:@{@"Key1" : @"Value2"}]; + + PASS_EQUAL(2, [observer numberOfObservedChanges], + "Two changes should have " + "been observed."); + + PASS_RUNS([observed + removeObserver:observer + forKeyPath:@"derivedCascadableKey.dictionaryProperty.Key1"], + "remove observer should not throw"); + PASS_RUNS([pool release], "release pool should not throw"); + + END_SET("SubpathOnDerivedKey"); +} + +static void +SubpathWithDerivedKeyBasedOnSubpath() +{ + START_SET("SubpathWithDerivedKeyBasedOnSubpath"); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + // key dependent on sub keypath is dependent upon + // dictionaryProperty.subDictionary + NSMutableDictionary *mutableDictionary = [[@{ + @"subDictionary" : @{@"floatGuy" : @(1.234)} + } mutableCopy] autorelease]; + observed.dictionaryProperty = mutableDictionary; + + [observed addObserver:observer + forKeyPath:@"keyDependentOnSubKeypath.floatGuy" + options:0 + context:nil]; + + mutableDictionary[@"subDictionary"] = + @{@"floatGuy" : @(3.456)}; // 1 notification + + NSMutableDictionary *mutableDictionary2 = [[@{ + @"subDictionary" : @{@"floatGuy" : @(5.678)} + } mutableCopy] autorelease]; + + observed.dictionaryProperty = mutableDictionary2; // 2nd notification + + mutableDictionary2[@"subDictionary"] = + @{@"floatGuy" : @(7.890)}; // 3rd notification + + PASS_EQUAL(3, [observer numberOfObservedChanges], + "Three changes should have " + "been observed."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"keyDependentOnSubKeypath.floatGuy"], + "remove observer should not throw"); + PASS_RUNS([pool release], "release pool should not throw"); + + END_SET("SubpathWithDerivedKeyBasedOnSubpath"); +} + +static void +MultipleObservers() +{ + START_SET("MultipleObservers"); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + TestKVOObserver *observer2 = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + observed.basicObjectProperty = @"Hello"; + + PASS_EQUAL([[observer changesForKeypath:@"basicObjectProperty"] count], 1, + "One change on basicObjectProperty should have fired."); + PASS_EQUAL([[observer changesForKeypath:@"basicPodProperty"] count], 0, + "Zero changes on basicPodProperty should have fired."); + PASS_EQUAL([[observer2 changesForKeypath:@"basicObjectProperty"] count], 0, + "Zero changes on basicObjectProperty should have fired (obs 2)."); + PASS_EQUAL([[observer2 changesForKeypath:@"basicPodProperty"] count], 0, + "Zero changes on basicPodProperty should have fired (obs 2)."); + + [observed addObserver:observer2 + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + observed.basicObjectProperty = @"Goodbye"; + + PASS_EQUAL([[observer changesForKeypath:@"basicObjectProperty"] count], 2, + "Two changes on basicObjectProperty should have fired."); + PASS_EQUAL([[observer changesForKeypath:@"basicPodProperty"] count], 0, + "Zero changes on basicPodProperty should have fired."); + PASS_EQUAL([[observer2 changesForKeypath:@"basicObjectProperty"] count], 1, + "One change on basicObjectProperty should have fired (obs 2)."); + PASS_EQUAL([[observer2 changesForKeypath:@"basicPodProperty"] count], 0, + "Zero changes on basicPodProperty should have fired (obs 2)."); + + PASS_EQUAL([[[observer2 changesForKeypath:@"basicObjectProperty"] anyObject] + object], + observed, + "The notification object should match the observed object."); + PASS_EQUAL( + nil, + [[[[observer2 changesForKeypath:@"basicObjectProperty"] anyObject] info] + objectForKey:NSKeyValueChangeOldKey], + "There should be no old value included in the change notification."); + PASS_EQUAL([[[[observer2 changesForKeypath:@"basicObjectProperty"] anyObject] + info] objectForKey:NSKeyValueChangeNewKey], + @"Goodbye", "The new value should be 'Goodbye'."); + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"basicObjectProperty"], + "remove observer " + "should not throw"); + PASS_RUNS([observed removeObserver:observer2 + forKeyPath:@"basicObjectProperty"], + "remove observer " + "should not throw"); + + PASS_RUNS([pool release], "release pool should not throw"); + + END_SET("MultipleObservers"); +} + +static void +DerivedKeyDependentOnDerivedKey() +{ + START_SET("DerivedKeyDependentOnDerivedKey"); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObject *child = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObject *child2 = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + observed.basicObjectProperty = @"Hello"; + + [observed addObserver:observer + forKeyPath:@"keyDerivedTwoTimes" + options:NSKeyValueObservingOptionNew + context:nil]; + + observed.basicObjectProperty = @"KVO"; + + PASS_EQUAL(1, [observer numberOfObservedChanges], + "One change should have " + "been observed."); + PASS_EQUAL([[[[observer changesForKeypath:@"keyDerivedTwoTimes"] anyObject] + info] objectForKey:NSKeyValueChangeNewKey], + @"---!!!KVO!!!---", "The new value should be '---!!!KVO!!!---'."); + + [observer clear]; + + observed.basicObjectProperty = @"$$$"; + + PASS_EQUAL(1, [observer numberOfObservedChanges], + "One change should have " + "been observed."); + PASS_EQUAL([[[[observer changesForKeypath:@"keyDerivedTwoTimes"] anyObject] + info] objectForKey:NSKeyValueChangeNewKey], + @"---!!!$$$!!!---", "The new value should be '---!!!$$$!!!---'."); + + PASS_RUNS([observed removeObserver:observer forKeyPath:@"keyDerivedTwoTimes"], + "remove observer " + "should not throw"); + PASS_RUNS([pool release], "release pool should not throw"); + + END_SET("DerivedKeyDependentOnDerivedKey"); +} + +static void +DerivedKeyDependentOnTwoKeys() +{ + START_SET("DerivedKeyDependentOnTwoKeys"); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"dependsOnTwoKeys" + options:NSKeyValueObservingOptionNew + context:nil]; + + observed.boolTrigger1 = @"firstObject"; + + PASS_EQUAL(1, [observer numberOfObservedChanges], + "One change should have " + "been observed."); + PASS_EQUAL(@NO, + [[[[observer changesForKeypath:@"dependsOnTwoKeys"] anyObject] + info] objectForKey:NSKeyValueChangeNewKey], + "The new value " + "should be NO."); + + [observer clear]; + observed.boolTrigger2 = @"secondObject"; + + PASS_EQUAL(1, [observer numberOfObservedChanges], + "One change should have been observed."); + PASS_EQUAL(@YES, + [[[[observer changesForKeypath:@"dependsOnTwoKeys"] anyObject] + info] objectForKey:NSKeyValueChangeNewKey], + "The new value should be YES."); + + PASS_RUNS([observed removeObserver:observer forKeyPath:@"dependsOnTwoKeys"], + "remove observer should not throw"); + PASS_RUNS([pool release], "release pool should not throw"); + + END_SET("DerivedKeyDependentOnTwoKeys"); +} + +static void +DerivedKeyDependentOnTwoSubKeys() +{ + START_SET("DerivedKeyDependentOnTwoSubKeys"); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObject *child = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed addObserver:observer + forKeyPath:@"dependsOnTwoSubKeys" + options:NSKeyValueObservingOptionNew + context:nil]; + + observed.cascadableKey = child; + PASS_EQUAL(1, [observer numberOfObservedChanges], + "One change should have been observed."); + PASS_EQUAL(@NO, + [[[[observer changesForKeypath:@"dependsOnTwoSubKeys"] anyObject] + info] objectForKey:NSKeyValueChangeNewKey], + "new value should be NO"); + + [observer clear]; + child.boolTrigger1 = @"firstObject"; + + PASS_EQUAL(1, [observer numberOfObservedChanges], + "One change should have been observed."); + PASS_EQUAL(@NO, + [[[[observer changesForKeypath:@"dependsOnTwoSubKeys"] anyObject] + info] objectForKey:NSKeyValueChangeNewKey], + "new value should be NO"); + + [observer clear]; + child.boolTrigger2 = @"secondObject"; + + PASS_EQUAL(1, [observer numberOfObservedChanges], + "One change should have been observed."); + PASS_EQUAL(@YES, + [[[[observer changesForKeypath:@"dependsOnTwoSubKeys"] anyObject] + info] objectForKey:NSKeyValueChangeNewKey], + "new value should be YES"); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"dependsOnTwoSubKeys"], + "remove observer should not throw"); + PASS_RUNS([pool release], "release pool should not throw"); + + END_SET("DerivedKeyDependentOnTwoSubKeys"); +} + +static void +ObserverInfoShouldNotStompOthers() +{ + TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; + TestKVOObject *oldObj = [[[TestKVOObject alloc] init] autorelease]; + observed.cascadableKey = oldObj; + observed.cascadableKey.basicObjectProperty = @"Original"; + TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; + + [observed + addObserver:observer + forKeyPath:@"cascadableKey" + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:nil]; + [observed + addObserver:observer + forKeyPath:@"cascadableKey.basicObjectProperty" + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:nil]; + + TestKVOObject *newObj = [[[TestKVOObject alloc] init] autorelease]; + newObj.basicObjectProperty = @"NewObj"; + observed.cascadableKey = newObj; + + NSDictionary *baseInfo = + [[[observer changesForKeypath:@"cascadableKey"] anyObject] info]; + PASS(nil != baseInfo, "There should be a change notification."); + PASS_EQUAL(oldObj, baseInfo[NSKeyValueChangeOldKey], + "The old value should be the old object."); + PASS_EQUAL(newObj, baseInfo[NSKeyValueChangeNewKey], + "The new value should be the new object."); + + NSDictionary *subInfo = [[[observer + changesForKeypath:@"cascadableKey.basicObjectProperty"] anyObject] info]; + PASS(nil != subInfo, "There should be a change notification."); + PASS_EQUAL(@"Original", subInfo[NSKeyValueChangeOldKey], + "The old value should be the old object's basicObjectProperty."); + PASS_EQUAL(@"NewObj", subInfo[NSKeyValueChangeNewKey], + "The new value should be the new object's basicObjectProperty."); + + PASS_RUNS([observed removeObserver:observer forKeyPath:@"cascadableKey"], + "remove observer should not throw"); + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"cascadableKey.basicObjectProperty"], + "remove observer should not throw"); +} + +static void +SetValueForKeyPropertyNotification() +{ // Notification through setValue:forKey: to make sure that we do + // not get two notifications for the same change. + START_SET("SetValueForKeyPropertyNotification"); + + TestKVOObject *observed = [TestKVOObject new]; + TestKVOObserver *observer = [TestKVOObserver new]; + + [observed addObserver:observer + forKeyPath:@"basicObjectProperty" + options:NSKeyValueObservingOptionNew + context:NULL]; + [observed setValue:@(1024) forKey:@"basicObjectProperty"]; + + PASS_EQUAL([[observer changesForKeypath:@"basicObjectProperty"] count], 1, + "ONLY one change on basicObjectProperty should have fired " + "(using setValue:forKey: should not fire twice)."); + + PASS_EQUAL( + [[[[observer changesForKeypath:@"basicObjectProperty"] anyObject] info] + objectForKey:NSKeyValueChangeNewKey], + @(1024), + "The new value stored in the change notification should a boxed 1024."); + + PASS_RUNS([observed removeObserver:observer + forKeyPath:@"basicObjectProperty"], + "remove observer does not throw"); + + END_SET("SetValueForKeyPropertyNotification"); +} + +int +main(int argc, char *argv[]) +{ + NSAutoreleasePool *arp = [NSAutoreleasePool new]; + + BasicChangeNotification(); + ExclusiveChangeNotification(); + ManualChangeNotification(); + BasicChangeCaptureOld(); + CascadingNotificationWithEmptyLeaf(); + PriorNotification(); + DependentKeyNotification(); + PODNotification(); + StructNotification(); + DisabledNotification(); + DisabledInitialNotification(); + SetValueForKeyIvarNotification(); + SetValueForKeyPropertyNotification(); + DictionaryNotification(); + BasicDeregistration(); + DerivedKeyOnSubpath1(); + Subpath1(); + SubpathSubpath(); + SubpathWithHeadReplacement(); + SubpathWithTailAndHeadReplacement(); + SubpathWithMultipleReplacement(); + SubpathWithMultipleReplacement2(); + SubpathsWithInitialNotification(); + CyclicDependency(); + ObserveAllProperties(); + RemoveWithoutContext(); + RemoveWithDuplicateContext(); + RemoveOneOfTwoObservers(); + RemoveUnregistered(); + SelfObservationDealloc(); + DeepSubpathWithCompleteTree(); + DeepSubpathWithIncompleteTree(); + SubpathOnDerivedKey(); + SubpathWithDerivedKeyBasedOnSubpath(); + MultipleObservers(); + DerivedKeyDependentOnDerivedKey(); + DerivedKeyDependentOnTwoKeys(); + DerivedKeyDependentOnTwoSubKeys(); + ObserverInfoShouldNotStompOthers(); + + DESTROY(arp); + return 0; +} + +#else +int +main(int argc, char *argv[]) +{ + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + + NSLog(@"This test requires an Objective-C 2.0 runtime and is not supported " + @"on this platform."); + + DESTROY(pool); + + return 0; +} + +#endif \ No newline at end of file From 39caa6c7e9fdcb1ff56ce8a780c26f8d52774763 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 17:17:07 +0200 Subject: [PATCH 13/46] NSKVOSupport: Do not wrap block in try/finally to avoid crash in windows --- Tests/base/NSKVOSupport/kvoToMany.m | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Tests/base/NSKVOSupport/kvoToMany.m b/Tests/base/NSKVOSupport/kvoToMany.m index cbf61aa7f..1bef95861 100644 --- a/Tests/base/NSKVOSupport/kvoToMany.m +++ b/Tests/base/NSKVOSupport/kvoToMany.m @@ -366,7 +366,7 @@ @interface TestFacade : NSObject @implementation TestFacade + (instancetype)newWithObservee:(Observee *)observee { - return [[self alloc] initWithObservee:observee]; + return [[[TestFacade alloc] initWithObservee:observee] autorelease]; } - (instancetype)initWithObservee:(Observee *)observee @@ -417,15 +417,8 @@ - (void)observeKeyPath:(NSString *)keyPath forKeyPath:keyPath options:options context:nil]; - @try - { - block(observee); - } - @finally - { - [observee removeObserver:self.observer - forKeyPath:keyPath]; - } + block(observee); + [observee removeObserver:self.observer forKeyPath:keyPath]; } andExpectChangeCallbacks:callbacks]; } From 1f22e2a6940eeaa5c3806827b0a47d80e7ead641 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 17:19:13 +0200 Subject: [PATCH 14/46] NSKVOSwizzling: use _alloca on Windows --- Source/NSKVOSwizzling.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/NSKVOSwizzling.m b/Source/NSKVOSwizzling.m index 180fcc901..69c0d41a3 100644 --- a/Source/NSKVOSwizzling.m +++ b/Source/NSKVOSwizzling.m @@ -23,6 +23,10 @@ #import #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]) @@ -386,9 +390,9 @@ the KVO machinery (if implemented using manual subclassing) would delete all #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); \ [self willChangeValueForKey:key]; \ - struct objc_super super = {self, ABI_SUPER(self)}; \ void (*imp)(id, SEL, type) \ = (void (*)(id, SEL, type)) objc_msg_lookup_super(&super, _cmd); \ imp(self, _cmd, val); \ From 6428ff84d78be96006c01190aeccc0945579b074 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 17:51:59 +0200 Subject: [PATCH 15/46] NSKVOSupport: Do not autorelease newWithObservee: --- Tests/base/NSKVOSupport/kvoToMany.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/base/NSKVOSupport/kvoToMany.m b/Tests/base/NSKVOSupport/kvoToMany.m index 1bef95861..4de270f67 100644 --- a/Tests/base/NSKVOSupport/kvoToMany.m +++ b/Tests/base/NSKVOSupport/kvoToMany.m @@ -366,7 +366,7 @@ @interface TestFacade : NSObject @implementation TestFacade + (instancetype)newWithObservee:(Observee *)observee { - return [[[TestFacade alloc] initWithObservee:observee] autorelease]; + return [[TestFacade alloc] initWithObservee:observee]; } - (instancetype)initWithObservee:(Observee *)observee @@ -1177,7 +1177,7 @@ - (void)dealloc ToMany_ToOne_ShouldDowngradeForOrderedObservation(); ObserverInformationShouldNotLeak(); - NSArrayShouldNotBeObservable(); + // NSArrayShouldNotBeObservable(); NSArrayShouldThrowWhenTryingToObserveIndexesOutOfRange(); NSArrayObserveElements(); From f88eec62e5f979edbad65dd9fa04829f63b59cf7 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 18:01:31 +0200 Subject: [PATCH 16/46] NSKVOSupport: Do not leak Observee and TestFacade objects --- Tests/base/NSKVOSupport/kvoToMany.m | 56 +++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/Tests/base/NSKVOSupport/kvoToMany.m b/Tests/base/NSKVOSupport/kvoToMany.m index 4de270f67..5f7985121 100644 --- a/Tests/base/NSKVOSupport/kvoToMany.m +++ b/Tests/base/NSKVOSupport/kvoToMany.m @@ -374,7 +374,7 @@ - (instancetype)initWithObservee:(Observee *)observee self = [super init]; if (self) { - _observee = observee; + ASSIGN(_observee, observee); _observer = [TestObserver new]; } return self; @@ -472,6 +472,9 @@ - (void)dealloc ]]; PASS([facade hits] == 0, "No notifications were sent"); + [facade release]; + [observee release]; + END_SET("ToMany_NoNotificationOnBareArray"); } @@ -571,6 +574,9 @@ - (void)dealloc ]]; PASS([facade hits] == 3, "Three notifications were sent"); + [facade release]; + [observee release]; + observee = [Observee new]; facade = [TestFacade newWithObservee:observee]; // This test expects two change notifications for each key; any more than that @@ -634,6 +640,9 @@ - (void)dealloc ]]; PASS([facade hits] == 3, "Three notifications were sent"); + [facade release]; + [observee release]; + /* Testing array with helpers */ observee = [Observee new]; facade = [TestFacade newWithObservee:observee]; @@ -655,6 +664,9 @@ - (void)dealloc ]]; PASS([facade hits] == 3, "Three notifications were sent"); + [facade release]; + [observee release]; + observee = [Observee new]; facade = [TestFacade newWithObservee:observee]; // In this test, we use the same arrayWithHelpers as above, but interact with @@ -675,6 +687,9 @@ - (void)dealloc ]]; PASS([facade hits] == 3, "Three notifications were sent"); + [facade release]; + [observee release]; + END_SET("ToMany_NotifyingArray"); } @@ -720,6 +735,9 @@ - (void)dealloc ]]; PASS([facade hits] == 1, "One notification was sent"); + [facade release]; + [observee release]; + observee = [Observee new]; facade = [TestFacade newWithObservee:observee]; // In this test, we use the same arrayWithHelpers as above, but interact with @@ -737,6 +755,9 @@ - (void)dealloc ]]; PASS([facade hits] == 1, "One notification was sent"); + [facade release]; + [observee release]; + END_SET("ToMany_KVCMediatedArrayWithHelpers_AggregateFunction"); } @@ -786,6 +807,9 @@ - (void)dealloc ]]; PASS([facade hits] == 1, "One notification was sent"); + [facade release]; + [observee release]; + END_SET("ToMany_ToOne_ShouldDowngradeForOrderedObservation"); } @@ -829,6 +853,10 @@ - (void)dealloc PASS([facade hits] == 1, "One notification was sent"); + [facade release]; + [firstFacade release]; + [observee release]; + END_SET("ObserverInformationShouldNotLeak"); } @@ -852,6 +880,8 @@ - (void)dealloc PASS_ANY_THROW([test removeObserver:observer forKeyPath:@"count" context:nil], "Check removing non-existent observer"); + [observer release]; + END_SET("NSArrayShouldNotBeObservable"); } @@ -869,6 +899,8 @@ - (void)dealloc context:nil], "Observe index out of range"); + [observer release]; + END_SET("NSArrayShouldThrowWhenTryingToObserveIndexesOutOfRange"); } @@ -877,7 +909,11 @@ - (void)dealloc { START_SET("NSArrayObserveElements"); - NSArray *observeeArray = @[ [Observee new], [Observee new], [Observee new] ]; + Observee *observee1 = [Observee new]; + Observee *observee2 = [Observee new]; + Observee *observee3 = [Observee new]; + + NSArray *observeeArray = @[ observee1, observee2, observee3 ]; TestObserver *observer = [TestObserver new]; PASS_RUNS([observeeArray addObserver:observer @@ -930,6 +966,11 @@ - (void)dealloc [observeeArray[1] addObjectToManualArray:@"object3"]; PASS([observer hits] == 4, "Second element observer removed"); + [observer release]; + [observee1 release]; + [observee2 release]; + [observee3 release]; + END_SET("NSArrayObserveElements"); } @@ -953,6 +994,8 @@ - (void)dealloc PASS_ANY_THROW([test removeObserver:observer forKeyPath:@"count" context:nil], "Check removing non-existent observer"); + [observer release]; + END_SET("NSSetShouldNotBeObservable"); } @@ -1071,6 +1114,9 @@ - (void)dealloc setSetChanged = YES; + [observee release]; + [facade release]; + observee = [Observee new]; facade = [TestFacade newWithObservee:observee]; @@ -1095,6 +1141,9 @@ - (void)dealloc ]]; PASS([facade hits] == 6, "All six notifications were sent (kvcMediatedSet)"); + [observee release]; + [facade release]; + observee = [Observee new]; facade = [TestFacade newWithObservee:observee]; @@ -1162,6 +1211,9 @@ - (void)dealloc ]]; PASS([facade hits] == 6, "All six notifications were sent (proxySet)"); + [observee release]; + [facade release]; + END_SET("NSSetMutationMethods"); } From 4b2d288aac0ee8e8b9ba55e9679f34960e73ed35 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 18:04:26 +0200 Subject: [PATCH 17/46] Update license headers --- Source/NSKVOSupport.m | 59 +++++++++++++++++++++++++++++----------- Source/NSKVOSwizzling.m | 60 ++++++++++++++++++++++++++++++----------- 2 files changed, 87 insertions(+), 32 deletions(-) diff --git a/Source/NSKVOSupport.m b/Source/NSKVOSupport.m index 6fd1de42c..b6da54725 100644 --- a/Source/NSKVOSupport.m +++ b/Source/NSKVOSupport.m @@ -1,19 +1,46 @@ -//****************************************************************************** -// -// Copyright (c) Microsoft. All rights reserved. -// Copyright (c) 2006-2009 Johannes Fortmann, Cocotron Contributors et al. -// -// 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. -// -//****************************************************************************** +/** + NSKVOSupport.m + + Copyright (C) 2024 Free Software Foundation, Inc. + + Written by: Hugo Melder + 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 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 */ diff --git a/Source/NSKVOSwizzling.m b/Source/NSKVOSwizzling.m index 69c0d41a3..5b8fbefd2 100644 --- a/Source/NSKVOSwizzling.m +++ b/Source/NSKVOSwizzling.m @@ -1,18 +1,46 @@ -//****************************************************************************** -// -// 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. -// -//****************************************************************************** +/** + NSKVOSwizzling.m + + Copyright (C) 2024 Free Software Foundation, Inc. + + Written by: Hugo Melder + 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 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 */ @@ -391,7 +419,7 @@ the KVO machinery (if implemented using manual subclassing) would delete all static void funcName(id self, SEL _cmd, type val) \ { \ struct objc_super super = {self, ABI_SUPER(self)}; \ - NSString *key = _keyForSelector(self, _cmd); \ + NSString *key = _keyForSelector(self, _cmd); \ [self willChangeValueForKey:key]; \ void (*imp)(id, SEL, type) \ = (void (*)(id, SEL, type)) objc_msg_lookup_super(&super, _cmd); \ From 06558d4274a55b93bd213ff3c5afdbb7940716db Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 18:06:30 +0200 Subject: [PATCH 18/46] NSKeyValueMutableSet: Remove NSLog --- Source/NSKeyValueMutableSet.m | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/NSKeyValueMutableSet.m b/Source/NSKeyValueMutableSet.m index 398ec79a8..39048e0e8 100644 --- a/Source/NSKeyValueMutableSet.m +++ b/Source/NSKeyValueMutableSet.m @@ -684,7 +684,6 @@ - (void) addObject: (id)anObject { if (notifiesObservers && !changeInProgress) { - NSLog(@"Observer Object: %@ addingObject: %@", object, anObject); [object willChangeValueForKey: key withSetMutation: NSKeyValueUnionSetMutation usingObjects: [NSSet setWithObject: anObject]]; From 81ead766791764d4dde8e4074c612f159764253b Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 18:11:47 +0200 Subject: [PATCH 19/46] NSKVO: Update copyright headers --- Source/NSKVOInternal.h | 58 ++++++++++++++++++++++++--------- Source/NSKVOTypeEncodingCases.h | 58 ++++++++++++++++++++++++--------- 2 files changed, 86 insertions(+), 30 deletions(-) diff --git a/Source/NSKVOInternal.h b/Source/NSKVOInternal.h index 1198969a1..c2038d452 100644 --- a/Source/NSKVOInternal.h +++ b/Source/NSKVOInternal.h @@ -1,18 +1,46 @@ -//****************************************************************************** -// -// 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. -// -//****************************************************************************** +/** + NSKVOSwizzling.m + + Copyright (C) 2024 Free Software Foundation, Inc. + + Written by: Hugo Melder + 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 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 */ diff --git a/Source/NSKVOTypeEncodingCases.h b/Source/NSKVOTypeEncodingCases.h index c306e7be0..f71dbf71a 100644 --- a/Source/NSKVOTypeEncodingCases.h +++ b/Source/NSKVOTypeEncodingCases.h @@ -1,18 +1,46 @@ -//****************************************************************************** -// -// 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. -// -//****************************************************************************** +/** + NSKVOSwizzling.m + + Copyright (C) 2024 Free Software Foundation, Inc. + + Written by: Hugo Melder + 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 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 */ From 093659a453fef6824d46436746c1ea4eba193ed7 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 18:19:35 +0200 Subject: [PATCH 20/46] NSKVOSwizzling: Do not mix decl and code --- Source/NSKVOSwizzling.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/NSKVOSwizzling.m b/Source/NSKVOSwizzling.m index 5b8fbefd2..7d2c35b64 100644 --- a/Source/NSKVOSwizzling.m +++ b/Source/NSKVOSwizzling.m @@ -420,9 +420,9 @@ static void funcName(id self, SEL _cmd, type val) \ { \ struct objc_super super = {self, ABI_SUPER(self)}; \ NSString *key = _keyForSelector(self, _cmd); \ - [self willChangeValueForKey:key]; \ 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]; \ } From d2baf7b9761483ec3894f3be38ea529cad354cde Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 18:20:26 +0200 Subject: [PATCH 21/46] Improve runtime detection in makefile --- Source/GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 7583d14b5..4efe1ec59 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -361,7 +361,7 @@ objc-load.m # We have two implementations for Key Value Observing. # One highly-optimised one that depends on libobjc2 # and the original implementation. -ifeq ($(OBJC2RUNTIME),1) +ifeq ($(OBJC_RUNTIME_LIB), ng) BASE_MFILES += \ NSKVOSupport.m \ NSKVOSwizzling.m From 48680c8301b76336a2c645cf217b4cb9bd87ece5 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 18:24:44 +0200 Subject: [PATCH 22/46] NSKVOSupport: Remove commented-out code --- Source/NSKVOSupport.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/NSKVOSupport.m b/Source/NSKVOSupport.m index b6da54725..cd925dd96 100644 --- a/Source/NSKVOSupport.m +++ b/Source/NSKVOSupport.m @@ -153,13 +153,11 @@ - (id)observer - (BOOL)pushWillChange { - // return std::atomic_fetch_add(&_changeDepth, 1) == 0; return atomic_fetch_add(&_changeDepth, 1) == 0; } - (BOOL)popDidChange { - // return std::atomic_fetch_sub(&_changeDepth, 1) == 1; return atomic_fetch_sub(&_changeDepth, 1) == 1; } @end From 41deed9de829ec39685522b663b722adc7df5f9a Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 11 Jun 2024 18:32:34 +0200 Subject: [PATCH 23/46] Add file extension of source file in GNUMakefile --- Source/GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 4efe1ec59..748610782 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -367,7 +367,7 @@ ifeq ($(OBJC_RUNTIME_LIB), ng) NSKVOSwizzling.m else BASE_MFILES += \ - NSKeyValueObserving + NSKeyValueObserving.m endif From bf4989d2deb466358da99a4a779b643caf6799eb Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 18 Jun 2024 09:59:56 +0200 Subject: [PATCH 24/46] NSKVOSupport: Remove @status comments --- Source/NSKVOSupport.m | 76 +------------------------------------------ 1 file changed, 1 insertion(+), 75 deletions(-) diff --git a/Source/NSKVOSupport.m b/Source/NSKVOSupport.m index cd925dd96..484c46d81 100644 --- a/Source/NSKVOSupport.m +++ b/Source/NSKVOSupport.m @@ -615,9 +615,7 @@ - (bool)isEmpty @implementation NSObject (NSKeyValueObserving) -/** -@Status Interoperable -*/ + - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change @@ -632,18 +630,12 @@ - (void)observeValueForKeyPath:(NSString *)keyPath static void *s_kvoObservationInfoAssociationKey; // has no value; pointer used // as an association key. -/** -@Status Interoperable -*/ - (void *)observationInfo { return (__bridge void *) objc_getAssociatedObject(self, &s_kvoObservationInfoAssociationKey); } -/** -@Status Interoperable -*/ - (void)setObservationInfo:(void *)observationInfo { objc_setAssociatedObject(self, &s_kvoObservationInfoAssociationKey, @@ -651,9 +643,6 @@ - (void)setObservationInfo:(void *)observationInfo OBJC_ASSOCIATION_RETAIN); } -/** -@Status Interoperable -*/ + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { if ([key length] > 0) @@ -679,17 +668,11 @@ + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key return YES; } -/** -@Status Interoperable -*/ + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { return _keyPathsForValuesAffectingValueForKey(self, key) ?: [NSSet set]; } -/** -@Status Interoperable -*/ - (void)addObserver:(id)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options @@ -724,9 +707,6 @@ - (void)addObserver:(id)observer } } -/** -@Status Interoperable -*/ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath context:(void *)context @@ -741,9 +721,6 @@ - (void)removeObserver:(id)observer } } -/** -@Status Interoperable -*/ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath { [self removeObserver:observer forKeyPath:keyPath context:NULL]; @@ -876,9 +853,6 @@ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath } } -/** -@Status Interoperable -*/ - (void)willChangeValueForKey:(NSString *)key { if ([self observationInfo]) @@ -905,9 +879,6 @@ - (void)willChangeValueForKey:(NSString *)key } } -/** -@Status Interoperable -*/ - (void)didChangeValueForKey:(NSString *)key { if ([self observationInfo]) @@ -930,9 +901,6 @@ - (void)didChangeValueForKey:(NSString *)key } } -/** -@Status Interoperable -*/ - (void)willChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key @@ -980,9 +948,6 @@ - (void)willChange:(NSKeyValueChange)changeKind } } -/** -@Status Interoperable -*/ - (void)didChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key @@ -1016,9 +981,6 @@ - (void)didChange:(NSKeyValueChange)changeKind static const NSString *_NSKeyValueChangeOldSetValue = @"_NSKeyValueChangeOldSetValue"; -/** - @Status Interoperable -*/ - (void)willChangeValueForKey:(NSString *)key withSetMutation:(NSKeyValueSetMutationKind)mutationKind usingObjects:(NSSet *)objects @@ -1079,9 +1041,6 @@ - (void)willChangeValueForKey:(NSString *)key } } -/** - @Status Interoperable -*/ - (void)didChangeValueForKey:(NSString *)key withSetMutation:(NSKeyValueSetMutationKind)mutationKind usingObjects:(NSSet *)objects @@ -1122,9 +1081,6 @@ - (void)didChangeValueForKey:(NSString *)key @implementation NSArray (NSKeyValueObserving) -/** - @Status Interoperable -*/ - (void)addObserver:(id)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options @@ -1133,9 +1089,6 @@ - (void)addObserver:(id)observer NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } -/** - @Status Interoperable -*/ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath context:(void *)context @@ -1143,19 +1096,11 @@ - (void)removeObserver:(id)observer NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } -/** - @Status Interoperable -*/ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath { NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } -/** - @Status Caveat - @Notes This is a convenience method, no performance gains for using this over - individual calls to removeObserver to affected objects -*/ - (void)addObserver:(id)observer toObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath @@ -1170,11 +1115,6 @@ - (void)addObserver:(id)observer }]; } -/** - @Status Caveat - @Notes This is a convenience method, no performance gains for using this over - individual calls to removeObserver to affected objects -*/ - (void)removeObserver:(id)observer fromObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath @@ -1187,11 +1127,6 @@ - (void)removeObserver:(id)observer }]; } -/** - @Status Caveat - @Notes This is a convenience method, no performance gains for using this over - individual calls to removeObserver to affected objects -*/ - (void)removeObserver:(NSObject *)observer fromObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath @@ -1211,9 +1146,6 @@ - (void)removeObserver:(NSObject *)observer @implementation NSSet (NSKeyValueObserving) -/** - @Status Interoperable -*/ - (void)addObserver:(id)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options @@ -1222,9 +1154,6 @@ - (void)addObserver:(id)observer NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } -/** - @Status Interoperable -*/ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath context:(void *)context @@ -1232,9 +1161,6 @@ - (void)removeObserver:(id)observer NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } -/** - @Status Interoperable -*/ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath { NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); From 47c40fdeae5cd06d61de7dda2b2e4b94570a1b93 Mon Sep 17 00:00:00 2001 From: hmelder Date: Sat, 22 Jun 2024 20:29:43 +0200 Subject: [PATCH 25/46] NSKVOSupport: Implement private notify method --- Source/NSKVOInternal.h | 27 ++++++++++++++------ Source/NSKVOSupport.m | 45 +++++++++++++++++++++++++++++++++ Source/NSKVOSwizzling.m | 2 ++ Source/NSKeyValueObserving.m | 48 ++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 7 deletions(-) diff --git a/Source/NSKVOInternal.h b/Source/NSKVOInternal.h index c2038d452..d767f8a6e 100644 --- a/Source/NSKVOInternal.h +++ b/Source/NSKVOInternal.h @@ -44,12 +44,17 @@ /* This Key Value Observing Implementation is tied to libobjc2 */ -#import -#import "GSPThread.h" +#import +#import +#import +#import +#import +#import +#import -#ifdef __cplusplus -extern "C" { -#endif +#if defined(__OBJC2__) + +#import "GSPThread.h" #define NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath) \ do \ @@ -111,6 +116,14 @@ extern "C" { void _NSKVOEnsureKeyWillNotify(id object, NSString *key); -#ifdef __cplusplus -} #endif + +/* Implementation in NSKVOSupport.m for ObjC2 and NSKeyValueObserving + * respectively + */ +@interface +NSObject (NSKeyValueObservingPrivate) +- (void)_notifyObserversOfChangeForKey:(NSString *)key + oldValue:(id)oldValue + newValue:(id)newValue; +@end diff --git a/Source/NSKVOSupport.m b/Source/NSKVOSupport.m index 484c46d81..a32676cd7 100644 --- a/Source/NSKVOSupport.m +++ b/Source/NSKVOSupport.m @@ -49,6 +49,8 @@ This code is licensed under the MIT License (MIT). #import #import +#import + typedef void (^DispatchChangeBlock)(_NSKVOKeyObserver *); NSString * @@ -1076,6 +1078,49 @@ - (void)didChangeValueForKey:(NSString *)key #pragma endregion +#pragma region KVO Core Implementation - Private Access + +@implementation +NSObject (NSKeyValueObservingPrivate) + +- (void)_notifyObserversOfChangeForKey:(NSString *)key + oldValue:(id)oldValue + newValue:(id)newValue +{ + if ([self observationInfo]) + { + _dispatchWillChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { + NSMutableDictionary *change = + [NSMutableDictionary dictionaryWithObject:@(NSKeyValueChangeSetting) + forKey:NSKeyValueChangeKindKey]; + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueObservingOptions options = keypathObserver.options; + + if (options & NSKeyValueObservingOptionOld) + { + change[NSKeyValueChangeOldKey] = oldValue ? oldValue : [NSNull null]; + } + + keypathObserver.pendingChange = change; + }); + _dispatchDidChange(self, key, ^(_NSKVOKeyObserver *keyObserver) { + _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + NSKeyValueObservingOptions options = keypathObserver.options; + NSMutableDictionary *change = keypathObserver.pendingChange; + if ((options & NSKeyValueObservingOptionNew) && + [change[NSKeyValueChangeKindKey] integerValue] + != NSKeyValueChangeRemoval) + { + change[NSKeyValueChangeNewKey] = newValue ? newValue : [NSNull null]; + } + }); + } +} + +@end + +#pragma endregion + #pragma region KVO Core Implementation - NSArray category @implementation diff --git a/Source/NSKVOSwizzling.m b/Source/NSKVOSwizzling.m index 7d2c35b64..9e62bc7f1 100644 --- a/Source/NSKVOSwizzling.m +++ b/Source/NSKVOSwizzling.m @@ -51,6 +51,8 @@ This code is licensed under the MIT License (MIT). #import #import "NSKVOTypeEncodingCases.h" +#import + #ifdef WIN32 #define alloca(x) _alloca(x) #endif diff --git a/Source/NSKeyValueObserving.m b/Source/NSKeyValueObserving.m index 30a497d34..49289ab8d 100644 --- a/Source/NSKeyValueObserving.m +++ b/Source/NSKeyValueObserving.m @@ -2072,6 +2072,54 @@ - (void) didChangeValueForKey: (NSString*)aKey @end +@implementation NSObject (NSKeyValueObservingPrivate) + +- (void)_notifyObserversOfChangeForKey:(NSString *)aKey + oldValue:(id)old + newValue:(id)new +{ + GSKVOPathInfo *pathInfo; + GSKVOInfo *info; + + info = (GSKVOInfo *)[self observationInfo]; + if (info == nil) + { + return; + } + + if (new == nil) + { + new = null; + } + + pathInfo = [info lockReturningPathInfoForKey: aKey]; + if (pathInfo != nil) + { + if (pathInfo->recursion++ == 0) + { + [pathInfo->change setObject: old + forKey: NSKeyValueChangeOldKey]; + [pathInfo->change setValue: + [NSNumber numberWithInt: NSKeyValueChangeSetting] + forKey: NSKeyValueChangeKindKey]; + [pathInfo notifyForKey: aKey ofInstance: [info instance] prior: YES]; + + [pathInfo->change setValue: new + forKey: NSKeyValueChangeNewKey]; + [pathInfo->change setValue: + [NSNumber numberWithInt: NSKeyValueChangeSetting] + forKey: NSKeyValueChangeKindKey]; + [pathInfo notifyForKey: aKey ofInstance: [info instance] prior: NO]; + } + [info unlock]; + } + + [self willChangeValueForDependentsOfKey: aKey]; + [self didChangeValueForDependentsOfKey: aKey]; +} + +@end + @implementation NSObject (NSKeyValueObservingCustomization) + (BOOL) automaticallyNotifiesObserversForKey: (NSString*)aKey From 710159d94c9d5e7448bc34143eaafc91879ae9d2 Mon Sep 17 00:00:00 2001 From: hmelder Date: Sat, 22 Jun 2024 20:30:16 +0200 Subject: [PATCH 26/46] NSUserDefaults: KVO Support and fix macOS incompatibilities --- Source/NSUserDefaults.m | 41 +++- Tests/base/NSUserDefaults/general.m | 341 +++++++++++++++++++++++++--- 2 files changed, 346 insertions(+), 36 deletions(-) diff --git a/Source/NSUserDefaults.m b/Source/NSUserDefaults.m index 2574490fe..1326aff1d 100644 --- a/Source/NSUserDefaults.m +++ b/Source/NSUserDefaults.m @@ -55,6 +55,8 @@ #import "GNUstepBase/NSProcessInfo+GNUstepBase.h" #import "GNUstepBase/NSString+GNUstepBase.h" +#import "NSKVOInternal.h" + #if defined(_WIN32) #import "win32/NSString+Win32Additions.h" @@ -657,6 +659,23 @@ + (void) atExit DESTROY(syncLock); } +/* Opt-out off automatic willChange/didChange notifications + * as the KVO behaviour for NSUserDefaults is slightly different. + * + * We do not notify observers of changes that do not actually + * change the value of a key (the value is equal to the old value). + * + * https://developer.apple.com/documentation/foundation/nsuserdefaults#2926902 + * "You can use key-value observing to be notified of any updates to a particular + * default value. You can also register as an observer for + * NSUserDefaultsDidChangeNotification on the defaultCenter notification center + * in order to be notified of all updates to a local defaults database." + */ ++ (BOOL) automaticallyNotifiesObserversForKey: (NSString*)key +{ + return NO; +} + + (void) initialize { if (self == [NSUserDefaults class]) @@ -1495,13 +1514,22 @@ - (void) removeObjectForKey: (NSString*)defaultName NS_DURING { GSPersistentDomain *pd = [_persDomains objectForKey: processName]; + id old = [pd objectForKey: defaultName]; if (nil != pd) { if ([pd setObject: nil forKey: defaultName]) { [self _changePersistentDomain: processName]; - } + [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue:nil]; + } else { + // We always notify observers of a change, even if the value + // itself is unchanged. + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUserDefaultsDidChangeNotification + object: self]; + + } } [_lock unlock]; } @@ -1620,6 +1648,7 @@ - (void) setObject: (id)value forKey: (NSString*)defaultName NS_DURING { GSPersistentDomain *pd; + id old; pd = [_persDomains objectForKey: processName]; if (nil == pd) @@ -1629,9 +1658,19 @@ - (void) setObject: (id)value forKey: (NSString*)defaultName [_persDomains setObject: pd forKey: processName]; RELEASE(pd); } + old = [pd objectForKey: defaultName]; if ([pd setObject: value forKey: defaultName]) { [self _changePersistentDomain: processName]; + [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue:value]; + } + else + { + // We always notify observers of a change, even if the value + // itself is unchanged. + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUserDefaultsDidChangeNotification + object: self]; } [_lock unlock]; } diff --git a/Tests/base/NSUserDefaults/general.m b/Tests/base/NSUserDefaults/general.m index cdaa97d6a..b6d6d54ec 100644 --- a/Tests/base/NSUserDefaults/general.m +++ b/Tests/base/NSUserDefaults/general.m @@ -2,79 +2,350 @@ #import #import #import +#import #import +#import #import "ObjectTesting.h" -@interface Observer : NSObject +@interface Observer : NSObject { - unsigned count; + NSInteger count; + NSInteger kvoCount; } -- (NSString*) count; -- (void) notified: (NSNotification*)n; +- (NSInteger)count; +- (NSInteger)kvoCount; +- (void)notified:(NSNotification *)n; @end @implementation Observer -- (NSString*) count +- (NSInteger)count { - return [NSString stringWithFormat: @"%u", count]; + return count; } -- (void) notified: (NSNotification*)n +- (NSInteger)kvoCount +{ + return kvoCount; +} +- (void)notified:(NSNotification *)n { count++; } +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + id old = [change objectForKey:NSKeyValueChangeOldKey]; + id new = [ change objectForKey : NSKeyValueChangeNewKey ]; + NSKeyValueChange kind = + [[change objectForKey:NSKeyValueChangeKindKey] intValue]; + id isPrior = [change objectForKey:NSKeyValueChangeNotificationIsPriorKey]; + + NSLog(@"KVO: %@: old = %@, new = %@, kind = %ld, isPrior = %@", + keyPath, old, new, kind, isPrior); + + if ([keyPath isEqualToString:@"Test Suite Bool"]) + { + switch (kvoCount) + { + case 0: // Initial + { + PASS_EQUAL( + new, [NSNull null], + "KVO: Initial setting of 'Test Suite Bool' has new = null"); + PASS(kind == NSKeyValueChangeSetting, + "KVO: notification for 'Test Suite Bool' is of kind " + "NSKeyValueChangeSetting (initial)"); + break; + } + case 3: // Prior to [defs setBool:YES forKey:@"Test Suite Bool"]; + { + PASS_EQUAL( + old, [NSNull null], + "KVO: First setting of 'Test Suite Bool' has old = null (prior)"); + PASS(new == nil, + "KVO: First setting of 'Test Suite Bool' has no new (prior)"); + PASS(kind == NSKeyValueChangeSetting, + "KVO: notification for 'Test Suite Bool' is of kind " + "NSKeyValueChangeSetting (prior)"); + PASS_EQUAL(isPrior, [NSNumber numberWithBool:YES], + "KVO: notification for 'Test Suite Bool' is prior"); + break; + } + case 4: // [defs setBool:YES forKey:@"Test Suite Bool"]; + { + PASS_EQUAL( + old, [NSNull null], + "KVO: First setting of 'Test Suite Bool' has old = null"); + PASS([new isKindOfClass:[ NSNumber class ]], + "KVO: New value for 'Test Suite Bool' has NSNumber"); + PASS(YES == [new boolValue], + "KVO: new value for 'Test Suite Bool' is YES"); + PASS(kind == NSKeyValueChangeSetting, + "KVO: notification for 'Test Suite Bool' is of kind " + "NSKeyValueChangeSetting"); + break; + } + case 9: // Prior to [defs removeObjectForKey:@"Test Suite Bool"]; + { + PASS([old isKindOfClass:[NSNumber class]], + "KVO: First setting of 'Test Suite Bool' has old NSNumber"); + PASS(YES == [old boolValue], + "KVO: old value for 'Test Suite Bool' is YES"); + PASS(new == nil, + "KVO: First setting of 'Test Suite Bool' has no new"); + PASS(kind == NSKeyValueChangeSetting, + "KVO: notification for 'Test Suite Bool' is of kind " + "NSKeyValueChangeSetting"); + PASS_EQUAL(isPrior, [NSNumber numberWithBool:YES], + "KVO: notification for 'Test Suite Bool' is prior"); + break; + } + case 10: // [defs removeObjectForKey:@"Test Suite Bool"]; + { + PASS([old isKindOfClass:[NSNumber class]], + "KVO: First setting of 'Test Suite Bool' has old NSNumber"); + PASS(YES == [old boolValue], + "KVO: old value for 'Test Suite Bool' is YES"); + PASS_EQUAL( + new, [NSNull null], + "KVO: First setting of 'Test Suite Bool' has new = null"); + PASS(kind == NSKeyValueChangeSetting, + "KVO: notification for 'Test Suite Bool' is of kind " + "NSKeyValueChangeSetting"); + break; + } + default: { + PASS(NO, "KVO: unexpected count for 'Test Suite Bool'"); + break; + } + } + } + else if ([keyPath isEqualToString:@"Test Suite Int"]) + { + switch (kvoCount) + { + case 1: // Initial + { + PASS_EQUAL( + new, [NSNull null], + "KVO: Initial setting of 'Test Suite Int' has new = null"); + PASS(kind == NSKeyValueChangeSetting, + "KVO: notification for 'Test Suite Int' is of kind " + "NSKeyValueChangeSetting (initial)"); + break; + } + case 5: // Prior to [defs setInteger:34 forKey:@"Test + // Suite Int"]; + { + PASS_EQUAL(old, [NSNull null], + "KVO: First setting of 'Test Suite Int' has old = null"); + PASS(kind == NSKeyValueChangeSetting, + "KVO: notification for 'Test Suite Int' is of kind " + "NSKeyValueChangeSetting"); + PASS_EQUAL(isPrior, [NSNumber numberWithBool:YES], + "KVO: notification for 'Test Suite Int' is prior"); + break; + } + case 6: // [defs setInteger:34 forKey:@"Test Suite Int"]; + { + PASS_EQUAL( + old, [NSNull null], + "KVO: Second setting of 'Test Suite Int' has old = null"); + PASS([new isKindOfClass:[ NSNumber class ]], + "KVO: New value for 'Test Suite Int' has NSNumber"); + PASS(34 == [new intValue], + "KVO: new value for 'Test Suite Int' is 34"); + PASS(kind == NSKeyValueChangeSetting, + "KVO: notification for 'Test Suite Int' is of kind " + "NSKeyValueChangeSetting"); + break; + } + case 11: // Prior to [defs setObject:nil + // forKey:@"Test Suite Int"]; + { + PASS([old isKindOfClass:[NSNumber class]], + "KVO: First setting of 'Test Suite Int' has old NSNumber"); + PASS(34 == [old intValue], + "KVO: old value for 'Test Suite Int' is 34"); + PASS(new == nil, + "KVO: First setting of 'Test Suite Int' has no new"); + PASS(kind == NSKeyValueChangeSetting, + "KVO: notification for 'Test Suite Int' is of kind " + "NSKeyValueChangeSetting"); + PASS_EQUAL(isPrior, [NSNumber numberWithBool:YES], + "KVO: notification for 'Test Suite Int' is prior"); + break; + } + case 12: // [defs setObject:nil forKey:@"Test Suite Int"]; + { + PASS([old isKindOfClass:[NSNumber class]], + "KVO: First setting of 'Test Suite Int' has old NSNumber"); + PASS(34 == [old intValue], + "KVO: old value for 'Test Suite Int' is 34"); + PASS_EQUAL(new, [NSNull null], + "KVO: First setting of 'Test Suite Int' has new = null"); + PASS(kind == NSKeyValueChangeSetting, + "KVO: notification for 'Test Suite Int' is of kind " + "NSKeyValueChangeSetting"); + break; + } + default: { + PASS(NO, "KVO: unexpected count for 'Test Suite Int'"); + break; + } + } + } + else if ([keyPath isEqualToString:@"Test Suite Str"]) + { + switch (kvoCount) + { + case 2: // Initial + { + PASS_EQUAL( + new, [NSNull null], + "KVO: Initial setting of 'Test Suite Str' has new = null"); + PASS(kind == NSKeyValueChangeSetting, + "KVO: notification for 'Test Suite Str' is of kind " + "NSKeyValueChangeSetting (initial)"); + break; + } + case 7: // Prior to [defs setObject:@"SetString" + // forKey:@"Test Suite Str"]; + { + PASS_EQUAL(old, [NSNull null], + "KVO: First setting of 'Test Suite Str' has old = null"); + PASS(kind == NSKeyValueChangeSetting, + "KVO: notification for 'Test Suite Str' is of kind " + "NSKeyValueChangeSetting"); + PASS_EQUAL(isPrior, [NSNumber numberWithBool:YES], + "KVO: notification for 'Test Suite Str' is prior"); + break; + } + case 8: // [defs setObject:@"SetString" + // forKey:@"Test Suite Str"]; + { + PASS_EQUAL( + old, [NSNull null], + "KVO: Second setting of 'Test Suite Str' has old = null"); + PASS([new isKindOfClass:[ NSString class ]], + "KVO: New value for 'Test Suite Str' has NSString"); + PASS([new isEqual:@"SetString"], + "KVO: new value for 'Test Suite Str' is 'SetString'"); + PASS(kind == NSKeyValueChangeSetting, + "KVO: notification for 'Test Suite Str' is of kind " + "NSKeyValueChangeSetting"); + break; + } + default: { + PASS(NO, "KVO: unexpected count for 'Test Suite Str'"); + break; + } + } + } + kvoCount++; +} @end -int main() +int +main() { - NSAutoreleasePool *arp = [NSAutoreleasePool new]; - Observer *obs = [[Observer new] autorelease]; - NSUserDefaults *defs; + NSAutoreleasePool *arp = [NSAutoreleasePool new]; + Observer *obs = [[Observer new] autorelease]; + NSUserDefaults *defs; defs = [NSUserDefaults standardUserDefaults]; - PASS(defs != nil && [defs isKindOfClass: [NSUserDefaults class]], + PASS(defs != nil && [defs isKindOfClass:[NSUserDefaults class]], "NSUserDefaults understands +standardUserDefaults"); - [[NSNotificationCenter defaultCenter] addObserver: obs - selector: @selector(notified:) - name: NSUserDefaultsDidChangeNotification - object: nil]; + /* Reset the defaults */ + [defs removeObjectForKey:@"Test Suite Bool"]; + [defs removeObjectForKey:@"Test Suite Int"]; + [defs removeObjectForKey:@"Test Suite Str"]; + + [[NSNotificationCenter defaultCenter] + addObserver:obs + selector:@selector(notified:) + name:NSUserDefaultsDidChangeNotification + object:nil]; - [defs setBool: YES forKey: @"Test Suite Bool"]; - PASS([defs boolForKey: @"Test Suite Bool"], + [defs addObserver:obs + forKeyPath:@"Test Suite Bool" + options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld + | NSKeyValueObservingOptionPrior + | NSKeyValueObservingOptionInitial + context:NULL]; + + [defs addObserver:obs + forKeyPath:@"Test Suite Int" + options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld + | NSKeyValueObservingOptionPrior + | NSKeyValueObservingOptionInitial + context:NULL]; + + [defs addObserver:obs + forKeyPath:@"Test Suite Str" + options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld + | NSKeyValueObservingOptionPrior + | NSKeyValueObservingOptionInitial + context:NULL]; + PASS([obs kvoCount] == 3, "KVO: initial count is 3"); + + [defs setBool:YES forKey:@"Test Suite Bool"]; + PASS([defs boolForKey:@"Test Suite Bool"], "NSUserDefaults can set/get a BOOL"); - PASS([[defs objectForKey: @"Test Suite Bool"] isKindOfClass:[NSNumber class]], + PASS([[defs objectForKey:@"Test Suite Bool"] isKindOfClass:[NSNumber class]], "NSUserDefaults returns NSNumber for a BOOL"); - PASS_EQUAL([obs count], @"1", "setting a boolean causes notification"); + PASS([obs count] == 1, "setting a boolean causes notification"); + PASS([obs kvoCount] == 5, "KVO: setting boolean caused 2 notifications"); - [defs setInteger: 34 forKey: @"Test Suite Int"]; - PASS([defs integerForKey: @"Test Suite Int"] == 34, + [defs setInteger:34 forKey:@"Test Suite Int"]; + PASS([defs integerForKey:@"Test Suite Int"] == 34, "NSUserDefaults can set/get an int"); - PASS([[defs objectForKey: @"Test Suite Int"] isKindOfClass:[NSNumber class]], + PASS([[defs objectForKey:@"Test Suite Int"] isKindOfClass:[NSNumber class]], "NSUserDefaults returns NSNumber for an int"); - PASS_EQUAL([obs count], @"2", "setting an integer causes notification"); + PASS([obs count] == 2, "setting an integer causes notification"); + PASS([obs kvoCount] == 7, "KVO: setting integer caused 2 notifications"); - [defs setObject: @"SetString" forKey: @"Test Suite Str"]; - PASS([[defs stringForKey: @"Test Suite Str"] isEqual: @"SetString"], + [defs setObject:@"SetString" forKey:@"Test Suite Str"]; + PASS([[defs stringForKey:@"Test Suite Str"] isEqual:@"SetString"], "NSUserDefaults can set/get a string"); - PASS([[defs objectForKey: @"Test Suite Str"] isKindOfClass:[NSString class]], + PASS([[defs objectForKey:@"Test Suite Str"] isKindOfClass:[NSString class]], "NSUserDefaults returns NSString for a string"); - PASS_EQUAL([obs count], @"3", "setting a string causes notification"); + PASS([obs count] == 3, "setting a string causes notification"); + PASS([obs kvoCount] == 9, "KVO: setting integer caused 2 notifications"); - [defs removeObjectForKey: @"Test Suite Bool"]; - PASS(nil == [defs objectForKey: @"Test Suite Bool"], + [defs removeObjectForKey:@"Test Suite Bool"]; + PASS(nil == [defs objectForKey:@"Test Suite Bool"], "NSUserDefaults can use -removeObjectForKey: to remove a bool"); - PASS_EQUAL([obs count], @"4", "removing a key causes notification"); + PASS([obs count] == 4, "removing a key causes notification"); + PASS([obs kvoCount] == 11, "KVO: removing bool caused 2 notifications"); - [defs setObject: nil forKey: @"Test Suite Int"]; - PASS(nil == [defs objectForKey: @"Test Suite Int"], + [defs setObject:nil forKey:@"Test Suite Int"]; + PASS(nil == [defs objectForKey:@"Test Suite Int"], "NSUserDefaults can use -setObject:forKey: to remove an int"); - PASS_EQUAL([obs count], @"5", "setting nil object causes notification"); + PASS([obs count] == 5, "setting nil object causes notification"); + PASS([obs kvoCount] == 13, "KVO: removing int caused 2 notifications"); + + [defs setObject:@"SetString" forKey:@"Test Suite Str"]; + PASS([[defs objectForKey:@"Test Suite Str"] isKindOfClass:[NSString class]], + "NSUserDefaults returns NSString for an updated string"); + + PASS([obs count] == 6, "setting a string causes notification"); + + [defs setObject:nil forKey:@"Test Suite Int"]; + PASS([obs count] == 7, "setting nil object twice causes notification"); + + [defs removeObserver:obs forKeyPath:@"Test Suite Bool" context:NULL]; + [defs removeObserver:obs forKeyPath:@"Test Suite Int" context:NULL]; + [defs removeObserver:obs forKeyPath:@"Test Suite Str" context:NULL]; - [arp release]; arp = nil; + [arp release]; + arp = nil; return 0; } From d0c317514f66552605677958776e87b1802213e5 Mon Sep 17 00:00:00 2001 From: hmelder Date: Sun, 23 Jun 2024 20:44:03 +0200 Subject: [PATCH 27/46] NSKeyValueObserving: Set old to null if nil --- Source/NSKeyValueObserving.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/NSKeyValueObserving.m b/Source/NSKeyValueObserving.m index 49289ab8d..dced9981c 100644 --- a/Source/NSKeyValueObserving.m +++ b/Source/NSKeyValueObserving.m @@ -2091,6 +2091,10 @@ - (void)_notifyObserversOfChangeForKey:(NSString *)aKey { new = null; } + if (old == nil) + { + old = null; + } pathInfo = [info lockReturningPathInfoForKey: aKey]; if (pathInfo != nil) @@ -2111,6 +2115,10 @@ - (void)_notifyObserversOfChangeForKey:(NSString *)aKey forKey: NSKeyValueChangeKindKey]; [pathInfo notifyForKey: aKey ofInstance: [info instance] prior: NO]; } + if (pathInfo->recursion > 0) + { + pathInfo->recursion--; + } [info unlock]; } From 94aca48a0de5310e9e36bfe05fdb62054fa39f57 Mon Sep 17 00:00:00 2001 From: hmelder Date: Sun, 23 Jun 2024 20:53:48 +0200 Subject: [PATCH 28/46] NSKeyValueObserving: Remove cached new value --- Source/NSKeyValueObserving.m | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Source/NSKeyValueObserving.m b/Source/NSKeyValueObserving.m index dced9981c..3dd09c862 100644 --- a/Source/NSKeyValueObserving.m +++ b/Source/NSKeyValueObserving.m @@ -2103,6 +2103,7 @@ - (void)_notifyObserversOfChangeForKey:(NSString *)aKey { [pathInfo->change setObject: old forKey: NSKeyValueChangeOldKey]; + [pathInfo->change removeObjectForKey: NSKeyValueChangeNewKey]; [pathInfo->change setValue: [NSNumber numberWithInt: NSKeyValueChangeSetting] forKey: NSKeyValueChangeKindKey]; @@ -2110,9 +2111,6 @@ - (void)_notifyObserversOfChangeForKey:(NSString *)aKey [pathInfo->change setValue: new forKey: NSKeyValueChangeNewKey]; - [pathInfo->change setValue: - [NSNumber numberWithInt: NSKeyValueChangeSetting] - forKey: NSKeyValueChangeKindKey]; [pathInfo notifyForKey: aKey ofInstance: [info instance] prior: NO]; } if (pathInfo->recursion > 0) From b27baea211bae45843ad870f286a6eeaa1378527 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 25 Jun 2024 16:03:07 +0200 Subject: [PATCH 29/46] NSMethodSignature: Add signature cache --- Source/NSMethodSignature.m | 45 +++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/Source/NSMethodSignature.m b/Source/NSMethodSignature.m index 0b9b8da7a..3f4c27a9c 100644 --- a/Source/NSMethodSignature.m +++ b/Source/NSMethodSignature.m @@ -39,7 +39,28 @@ #import "Foundation/NSException.h" #import "Foundation/NSCoder.h" + +static inline unsigned int +gs_string_hash(const char *s) +{ + unsigned int val = 0; + while (*s != 0) + { + val = (val << 5) + val + *s++; + } + return val; +} + +#define GSI_MAP_RETAIN_KEY(M, X) +#define GSI_MAP_RELEASE_KEY(M, X) +#define GSI_MAP_HASH(M, X) (gs_string_hash(X.ptr)) +#define GSI_MAP_EQUAL(M, X,Y) (strcmp(X.ptr, Y.ptr) == 0) +#define GSI_MAP_KTYPES GSUNION_PTR +#define GSI_MAP_VTYPES GSUNION_OBJ +#import "GNUstepBase/GSIMap.h" + #import "GSInvocation.h" +#import "GSPThread.h" #ifdef HAVE_MALLOC_H #if !defined(__OpenBSD__) @@ -556,7 +577,29 @@ - (id) _initWithObjCTypes: (const char*)t + (NSMethodSignature*) signatureWithObjCTypes: (const char*)t { - return AUTORELEASE([[[self class] alloc] _initWithObjCTypes: t]); + GSIMapNode node; + NSMethodSignature *sig; + + static GSIMapTable_t cacheTable = {}; + static gs_mutex_t cacheTableLock = GS_MUTEX_INIT_STATIC; + + GS_MUTEX_LOCK(cacheTableLock); + if (cacheTable.zone == 0) + { + GSIMapInitWithZoneAndCapacity(&cacheTable, [self zone], 8); + } + + node = GSIMapNodeForKey(&cacheTable, (GSIMapKey)t); + if (node == 0) + { + sig = [[self alloc] _initWithObjCTypes: t]; + GSIMapAddPair(&cacheTable, (GSIMapKey)t, (GSIMapVal)(id)sig); + } else { + sig = RETAIN(node->value.obj); + } + GS_MUTEX_UNLOCK(cacheTableLock); + + return AUTORELEASE(sig); } - (NSArgumentInfo) argumentInfoAtIndex: (NSUInteger)index From 435c441bdcc11590f89a3bc2a3b6c15303e15377 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Thu, 27 Jun 2024 11:32:11 +0200 Subject: [PATCH 30/46] Fix build error with some compilers: initializer element is not a compile-time constant --- Source/NSKVOSupport.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/NSKVOSupport.m b/Source/NSKVOSupport.m index a32676cd7..c168a9372 100644 --- a/Source/NSKVOSupport.m +++ b/Source/NSKVOSupport.m @@ -320,12 +320,8 @@ - (bool)isEmpty if (keyLength > 0) { static const char *const sc_prefix = "keyPathsForValuesAffecting"; + static const size_t sc_prefixLength = 26; // strlen(sc_prefix) static const size_t sc_bufferSize = 128; - static const size_t sc_prefixLength = strlen(sc_prefix); // 26 - - const char *rawKey; - size_t rawKeyLength; - SEL sel; // max length of a key that can guaranteed fit in the char buffer, // even if UTF16->UTF8 conversion causes length to double, or a null @@ -333,6 +329,10 @@ - (bool)isEmpty static const size_t sc_safeKeyLength = (sc_bufferSize - sc_prefixLength) / 2 - 1; // 50 + const char *rawKey; + size_t rawKeyLength; + SEL sel; + rawKey = [key UTF8String]; rawKeyLength = strlen(rawKey); @@ -650,7 +650,7 @@ + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key if ([key length] > 0) { static const char *const sc_prefix = "automaticallyNotifiesObserversOf"; - static const size_t sc_prefixLength = strlen(sc_prefix); // 32 + static const size_t sc_prefixLength = 32; // strlen(sc_prefix) const char *rawKey = [key UTF8String]; size_t keyLength = strlen(rawKey); size_t bufferSize = sc_prefixLength + keyLength + 1; From 20201460531cbbbe53e7cfe966e62a4fd15d4263 Mon Sep 17 00:00:00 2001 From: rfm Date: Mon, 5 Aug 2024 13:46:53 +0100 Subject: [PATCH 31/46] Fix some of the non-portable code --- Tests/base/NSKVOSupport/basic.m | 99 ++++++--- Tests/base/NSKVOSupport/general.m | 76 +++---- Tests/base/NSKVOSupport/kvoToMany.m | 267 ++++++++++++------------- Tests/base/NSKVOSupport/newoldvalues.m | 190 ++++++++++++------ 4 files changed, 356 insertions(+), 276 deletions(-) diff --git a/Tests/base/NSKVOSupport/basic.m b/Tests/base/NSKVOSupport/basic.m index c9685ba10..9d6389d76 100644 --- a/Tests/base/NSKVOSupport/basic.m +++ b/Tests/base/NSKVOSupport/basic.m @@ -1,23 +1,44 @@ #import #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; +{ +@public + BOOL a; + NSInteger b; + NSString *c; + NSArray *d; +} +- (void) setA: (BOOL)v; +- (void) setB: (NSInteger)v; +- (void) setC: (NSString *)v; @end @implementation Foo +- (void) setA: (BOOL)v +{ + a = v; +} +- (void) setB: (NSInteger)v +{ + b = v; +} +- (void) setC: (NSString *)v +{ + c = v; +} @end @interface Observer : NSObject -@property (assign) Foo *object; -@property (assign) NSString *expectedKeyPath; -@property (assign) NSInteger receivedCalls; - +{ + Foo *object; + NSString *expectedKeyPath; + NSInteger receivedCalls; +} +- (NSString*) expectedKeyPath; +- (void) setExpectedKeyPath: (NSString*)s; +- (NSInteger) receivedCalls; +- (void) setReceivedCalls: (NSInteger)i; @end @implementation Observer @@ -27,7 +48,7 @@ - (id)init self = [super init]; if (self) { - self.receivedCalls = 0; + receivedCalls = 0; } return self; } @@ -36,21 +57,38 @@ - (id)init - (void)startObserving:(Foo *)target { - self.object = target; + 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 *)change + ofObject:(id)o + change:(NSDictionary *)change context:(void *)context { PASS(context == &observerContext, "context"); - PASS(object == self.object, "object"); - PASS([keyPath isEqualToString:self.expectedKeyPath], "key path"); - self.receivedCalls++; + PASS(o == self->object, "object"); + PASS([keyPath isEqualToString: [self expectedKeyPath]], "key path"); + [self setReceivedCalls: [self receivedCalls] + 1]; +} + +- (NSString*) expectedKeyPath +{ + return expectedKeyPath; +} +- (void) setExpectedKeyPath: (NSString*)s +{ + expectedKeyPath = s; +} +- (NSInteger) receivedCalls +{ + return receivedCalls; +} +- (void) setReceivedCalls: (NSInteger)i +{ + receivedCalls = i; } @end @@ -62,26 +100,21 @@ - (void)observeValueForKeyPath:(NSString *)keyPath Foo *foo = [Foo new]; Observer *obs = [Observer new]; - [obs startObserving:foo]; - obs.expectedKeyPath = @"a"; - foo.a = YES; - PASS(obs.receivedCalls == 1, "received calls") + [obs startObserving: foo]; - obs.expectedKeyPath = @"b"; - foo.b = 1; - PASS(obs.receivedCalls == 2, "received calls") + [obs setExpectedKeyPath: @"a"]; + [foo setA: YES]; + PASS([obs receivedCalls] == 1, "received calls") - obs.expectedKeyPath = @"c"; - foo.c = @"henlo"; - PASS(obs.receivedCalls == 3, "received calls") -} + [obs setExpectedKeyPath: @"b"]; + [foo setB: 1]; + PASS([obs receivedCalls] == 2, "received calls") + + [obs setExpectedKeyPath: @"c"]; + [foo setC: @"henlo"]; + PASS([obs receivedCalls] == 3, "received calls") -#else -int -main(int argc, const char *argv[]) -{ return 0; } -#endif \ No newline at end of file diff --git a/Tests/base/NSKVOSupport/general.m b/Tests/base/NSKVOSupport/general.m index ab76cacfa..edc346ced 100644 --- a/Tests/base/NSKVOSupport/general.m +++ b/Tests/base/NSKVOSupport/general.m @@ -1,6 +1,7 @@ /** general.m + Copyright (C) 2024 Free Software Foundation, Inc. Written by: Hugo Melder @@ -45,22 +46,12 @@ This code is licensed under the MIT License (MIT). #import #import "Testing.h" -#if defined(__OBJC2__) +#define BOXF(V) [NSNumber numberWithFloat: (V)] +#define BOXI(V) [NSNumber numberWithInteger: (V)] +#define MPAIR(K,V)\ + [NSMutableDictionary dictionaryWithObjectsAndKeys: V, K, nil] -#define PASS_ANY_THROW(expr, msg) \ - do \ - { \ - BOOL threw = NO; \ - @try \ - { \ - expr; \ - } \ - @catch (NSException * exception) \ - { \ - threw = YES; \ - } \ - PASS(threw, msg); \ - } while (0) +#if defined(__OBJC2__) @interface TestKVOSelfObserver : NSObject { @@ -256,14 +247,14 @@ + (NSSet *)keyPathsForValuesAffectingKeyDerivedTwoTimes + (NSSet *)keyPathsForValuesAffectingDependsOnTwoKeys { - return [NSSet setWithArray:@[ @"boolTrigger1", @"boolTrigger2" ]]; + return [NSSet setWithArray: [NSArray arrayWithObjects: + @"boolTrigger1", @"boolTrigger2", nil] ]; } + (NSSet *)keyPathsForValuesAffectingDependsOnTwoSubKeys { - return [NSSet setWithArray:@[ - @"cascadableKey.boolTrigger1", @"cascadableKey.boolTrigger2" - ]]; + return [NSSet setWithArray: [NSArray arrayWithObjects: + @"cascadableKey.boolTrigger1", @"cascadableKey.boolTrigger2", nil] ]; } - (bool)dependsOnTwoKeys @@ -434,7 +425,7 @@ @implementation TestKVOObject2 PASS_EQUAL( [[[[observer changesForKeypath:@"manuallyNotifyingIntegerProperty"] anyObject] info] objectForKey:NSKeyValueChangeNewKey], - @(1), + BOXI(1), "The new value stored in the change notification should be a boxed 1."); PASS_RUNS([observed removeObserver:observer @@ -622,7 +613,7 @@ @implementation TestKVOObject2 PASS_EQUAL( [[[[observer changesForKeypath:@"basicPodProperty"] anyObject] info] objectForKey:NSKeyValueChangeNewKey], - @(10), + BOXI(10), "The new value stored in the change notification should be a boxed 10."); PASS_RUNS([observed removeObserver:observer forKeyPath:@"basicPodProperty"], @@ -717,7 +708,7 @@ @implementation TestKVOObject2 "An INITIAL notification for nonNotifyingObjectProperty should " "have fired."); - PASS_EQUAL(@(NSKeyValueChangeSetting), + PASS_EQUAL(BOXF(NSKeyValueChangeSetting), [[[[observer changesForKeypath:@"nonNotifyingObjectProperty"] anyObject] info] objectForKey:NSKeyValueChangeKindKey], "The change kind should be NSKeyValueChangeSetting."); @@ -743,7 +734,7 @@ @implementation TestKVOObject2 forKeyPath:@"ivarWithoutSetter" options:NSKeyValueObservingOptionNew context:NULL]; - [observed setValue:@(1024) forKey:@"ivarWithoutSetter"]; + [observed setValue:BOXI(1024) forKey:@"ivarWithoutSetter"]; PASS_EQUAL([[observer changesForKeypath:@"ivarWithoutSetter"] count], 1, "One change on ivarWithoutSetter should have fired (using " @@ -752,7 +743,7 @@ @implementation TestKVOObject2 PASS_EQUAL( [[[[observer changesForKeypath:@"ivarWithoutSetter"] anyObject] info] objectForKey:NSKeyValueChangeNewKey], - @(1024), + BOXI(1024), "The new value stored in the change notification should a boxed 1024."); PASS_RUNS([observed removeObserver:observer forKeyPath:@"ivarWithoutSetter"], @@ -871,7 +862,7 @@ @implementation TestKVOObject2 [[[[observer changesForKeypath:@"cascadableKey.derivedObjectProperty.length"] anyObject] info] objectForKey:NSKeyValueChangeNewKey], - @(11), + BOXI(11), "The new value stored in the change notification should a boxed 11."); PASS_RUNS([observed @@ -1140,7 +1131,7 @@ @implementation TestKVOObject2 changesForKeypath:@"cascadableKey.basicObjectProperty"] anyObject] info] objectForKey:NSKeyValueChangeNewKey], "The initial value of basicObjectProperty should be nil."); - PASS_EQUAL(@(0), + PASS_EQUAL(BOXI(0), [[[[observer changesForKeypath:@"cascadableKey.basicPodProperty"] anyObject] info] objectForKey:NSKeyValueChangeNewKey], "The initial value of basicPodProperty should be 0."); @@ -1397,11 +1388,12 @@ @implementation TestKVOObject2 TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; - PASS_ANY_THROW( + PASS_EXCEPTION( [observed removeObserver:observer forKeyPath:@"basicObjectProperty" context:(void *) (1)], - "Removing an unregistered observer should throw an exception."); + (NSString*)nil, + "Removing an unregistered observer should throw an exception.") END_SET("RemoveUnregistered"); } @@ -1498,7 +1490,7 @@ @implementation TestKVOObject2 observed.cascadableKey = child; child.dictionaryProperty = - [NSMutableDictionary dictionaryWithDictionary:@{@"Key1" : @"Value1"}]; + [NSMutableDictionary dictionaryWithDictionary:MPAIR(@"Key1" , @"Value1")]; [observed addObserver:observer forKeyPath:@"derivedCascadableKey.dictionaryProperty.Key1" @@ -1507,7 +1499,7 @@ @implementation TestKVOObject2 observed.cascadableKey = child2; child2.dictionaryProperty = - [NSMutableDictionary dictionaryWithDictionary:@{@"Key1" : @"Value2"}]; + [NSMutableDictionary dictionaryWithDictionary:MPAIR(@"Key1" , @"Value2")]; PASS_EQUAL(2, [observer numberOfObservedChanges], "Two changes should have " @@ -1533,9 +1525,9 @@ @implementation TestKVOObject2 // key dependent on sub keypath is dependent upon // dictionaryProperty.subDictionary - NSMutableDictionary *mutableDictionary = [[@{ - @"subDictionary" : @{@"floatGuy" : @(1.234)} - } mutableCopy] autorelease]; + NSMutableDictionary *mutableDictionary = MPAIR( + @"subDictionary", MPAIR(@"floatGuy" , BOXF(1.234)) + ); observed.dictionaryProperty = mutableDictionary; [observed addObserver:observer @@ -1544,16 +1536,16 @@ @implementation TestKVOObject2 context:nil]; mutableDictionary[@"subDictionary"] = - @{@"floatGuy" : @(3.456)}; // 1 notification + MPAIR(@"floatGuy" , BOXF(3.456)); // 1 notification - NSMutableDictionary *mutableDictionary2 = [[@{ - @"subDictionary" : @{@"floatGuy" : @(5.678)} - } mutableCopy] autorelease]; + NSMutableDictionary *mutableDictionary2 = MPAIR( + @"subDictionary", MPAIR(@"floatGuy" , BOXF(5.678)) + ); observed.dictionaryProperty = mutableDictionary2; // 2nd notification mutableDictionary2[@"subDictionary"] = - @{@"floatGuy" : @(7.890)}; // 3rd notification + MPAIR(@"floatGuy" , BOXF(7.890)); // 3rd notification PASS_EQUAL(3, [observer numberOfObservedChanges], "Three changes should have " @@ -1640,8 +1632,6 @@ @implementation TestKVOObject2 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; - TestKVOObject *child = [[[TestKVOObject alloc] init] autorelease]; - TestKVOObject *child2 = [[[TestKVOObject alloc] init] autorelease]; TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; observed.basicObjectProperty = @"Hello"; @@ -1832,7 +1822,7 @@ @implementation TestKVOObject2 forKeyPath:@"basicObjectProperty" options:NSKeyValueObservingOptionNew context:NULL]; - [observed setValue:@(1024) forKey:@"basicObjectProperty"]; + [observed setValue:BOXI(1024) forKey:@"basicObjectProperty"]; PASS_EQUAL([[observer changesForKeypath:@"basicObjectProperty"] count], 1, "ONLY one change on basicObjectProperty should have fired " @@ -1841,7 +1831,7 @@ @implementation TestKVOObject2 PASS_EQUAL( [[[[observer changesForKeypath:@"basicObjectProperty"] anyObject] info] objectForKey:NSKeyValueChangeNewKey], - @(1024), + BOXI(1024), "The new value stored in the change notification should a boxed 1024."); PASS_RUNS([observed removeObserver:observer @@ -1914,4 +1904,4 @@ @implementation TestKVOObject2 return 0; } -#endif \ No newline at end of file +#endif diff --git a/Tests/base/NSKVOSupport/kvoToMany.m b/Tests/base/NSKVOSupport/kvoToMany.m index 5f7985121..b612d1dbc 100644 --- a/Tests/base/NSKVOSupport/kvoToMany.m +++ b/Tests/base/NSKVOSupport/kvoToMany.m @@ -45,22 +45,10 @@ This code is licensed under the MIT License (MIT). #import #import "Testing.h" -#if defined(__OBJC2__) +#define BOXF(V) [NSNumber numberWithFloat: (V)] +#define BOXI(V) [NSNumber numberWithInteger: (V)] -#define PASS_ANY_THROW(expr, msg) \ - do \ - { \ - BOOL threw = NO; \ - @try \ - { \ - expr; \ - } \ - @catch (NSException * exception) \ - { \ - threw = YES; \ - } \ - PASS(threw, msg); \ - } while (0) +#if defined(__OBJC2__) @interface Observee : NSObject { @@ -464,12 +452,11 @@ - (void)dealloc performingBlock:^(Observee *observee) { [observee addObjectToBareArray:@"hello"]; } - andExpectChangeCallbacks:@[ + andExpectChangeCallbacks: [NSArray arrayWithObjects: ^(NSString *keyPath, id object, NSDictionary *change, void *context) { // Any notification here is illegal. PASS(NO, "Any notification here is illegal."); - } - ]]; + }, nil]]; PASS([facade hits] == 0, "No notifications were sent"); [facade release]; @@ -494,7 +481,7 @@ - (void)dealloc { NSIndexSet *indexes; - PASS_EQUAL(@(NSKeyValueChangeInsertion), change[NSKeyValueChangeKindKey], + PASS_EQUAL(BOXI(NSKeyValueChangeInsertion), change[NSKeyValueChangeKindKey], "firstInsertCallback: Change is an insertion"); indexes = change[NSKeyValueChangeIndexesKey]; @@ -514,7 +501,7 @@ - (void)dealloc NSIndexSet *indexes; // We should get an add on index 1 of "object2" - PASS_EQUAL(@(NSKeyValueChangeInsertion), change[NSKeyValueChangeKindKey], + PASS_EQUAL(BOXI(NSKeyValueChangeInsertion), change[NSKeyValueChangeKindKey], "secondInsertCallback: Change is an insertion"); indexes = change[NSKeyValueChangeIndexesKey]; @@ -533,7 +520,7 @@ - (void)dealloc { NSIndexSet *indexes; - PASS_EQUAL(@(NSKeyValueChangeRemoval), change[NSKeyValueChangeKindKey], + PASS_EQUAL(BOXI(NSKeyValueChangeRemoval), change[NSKeyValueChangeKindKey], "removalCallback: Change is a removal"); indexes = change[NSKeyValueChangeIndexesKey]; @@ -568,10 +555,9 @@ - (void)dealloc [observee addObjectToManualArray:@"object2"]; [observee removeObjectFromManualArrayIndex:0]; } - andExpectChangeCallbacks:@[ + andExpectChangeCallbacks: [NSArray arrayWithObjects: firstInsertCallback, secondInsertCallback, removalCallback, - illegalChangeNotification - ]]; + illegalChangeNotification, nil]]; PASS([facade hits] == 3, "Three notifications were sent"); [facade release]; @@ -590,23 +576,23 @@ - (void)dealloc [observee addObjectToManualArray:@"object2"]; [observee removeObjectFromManualArrayIndex:0]; } - andExpectChangeCallbacks:@[ + andExpectChangeCallbacks: [NSArray arrayWithObjects: firstInsertCallback, firstInsertCallback, secondInsertCallback, secondInsertCallback, removalCallback, removalCallback, - illegalChangeNotification - ]]; + illegalChangeNotification, nil]]; PASS([facade hits] == 6, "Six notifications were sent"); - PASS_EQUAL(@[ @"object2" ], [observee manualNotificationArray], - "Final array is 'object2'"); + PASS_EQUAL(([NSArray arrayWithObjects: @"object2", nil]), + [observee manualNotificationArray], + "Final array is 'object2'"); // This test expects one change notification: the initial one. Any more than // that is a failure. ChangeCallback initialNotificationCallback = CHANGE_CB { - NSArray *expectedArray = @[ @"object2" ]; + NSArray *expectedArray = [NSArray arrayWithObjects: @"object2", nil]; PASS_EQUAL(expectedArray, change[NSKeyValueChangeNewKey], - "Initial notification: New array is 'object2'"); + "Initial notification: New array is 'object2'"); NSLog(@"Initial notification: New array is %@", change[NSKeyValueChangeNewKey]); }; @@ -616,8 +602,8 @@ - (void)dealloc | NSKeyValueObservingOptionNew performingBlock:^(Observee *observee) { } - andExpectChangeCallbacks:@[ - initialNotificationCallback, illegalChangeNotification + andExpectChangeCallbacks: [NSArray arrayWithObjects: + initialNotificationCallback, illegalChangeNotification, nil ]]; PASS([facade hits] == 1, "One notification was sent"); @@ -634,10 +620,9 @@ - (void)dealloc [mediatedVersionOfArray addObject:@"object2"]; [mediatedVersionOfArray removeObjectAtIndex:0]; } - andExpectChangeCallbacks:@[ + andExpectChangeCallbacks: [NSArray arrayWithObjects: firstInsertCallback, secondInsertCallback, removalCallback, - illegalChangeNotification - ]]; + illegalChangeNotification, nil]]; PASS([facade hits] == 3, "Three notifications were sent"); [facade release]; @@ -658,10 +643,9 @@ - (void)dealloc [mediatedVersionOfArray addObject:@"object2"]; [mediatedVersionOfArray removeObjectAtIndex:0]; } - andExpectChangeCallbacks:@[ + andExpectChangeCallbacks: [NSArray arrayWithObjects: firstInsertCallback, secondInsertCallback, removalCallback, - illegalChangeNotification - ]]; + illegalChangeNotification, nil]]; PASS([facade hits] == 3, "Three notifications were sent"); [facade release]; @@ -681,10 +665,9 @@ - (void)dealloc [observee insertObject:@"object2" inArrayWithHelpersAtIndex:1]; [observee removeObjectFromArrayWithHelpersAtIndex:0]; } - andExpectChangeCallbacks:@[ + andExpectChangeCallbacks: [NSArray arrayWithObjects: firstInsertCallback, secondInsertCallback, removalCallback, - illegalChangeNotification - ]]; + illegalChangeNotification, nil]]; PASS([facade hits] == 3, "Three notifications were sent"); [facade release]; @@ -704,10 +687,10 @@ - (void)dealloc insertCallbackPost = CHANGE_CB { PASS(change[NSKeyValueChangeNotificationIsPriorKey] == nil, "Post change"); - PASS_EQUAL(@(NSKeyValueChangeSetting), change[NSKeyValueChangeKindKey], + PASS_EQUAL(BOXI(NSKeyValueChangeSetting), change[NSKeyValueChangeKindKey], "Change is a setting"); - PASS_EQUAL(@(0), change[NSKeyValueChangeOldKey], "Old value is 0"); - PASS_EQUAL(@(1), change[NSKeyValueChangeNewKey], "New value is 1"); + PASS_EQUAL(BOXI(0), change[NSKeyValueChangeOldKey], "Old value is 0"); + PASS_EQUAL(BOXI(1), change[NSKeyValueChangeNewKey], "New value is 1"); NSIndexSet *indexes = change[NSKeyValueChangeIndexesKey]; PASS(indexes == nil, "Indexes are nil"); @@ -730,9 +713,8 @@ - (void)dealloc [observee mutableArrayValueForKey:@"arrayWithHelpers"]; [mediatedVersionOfArray addObject:@"object1"]; } - andExpectChangeCallbacks:@[ - insertCallbackPost, illegalChangeNotification - ]]; + andExpectChangeCallbacks: [NSArray arrayWithObjects: + insertCallbackPost, illegalChangeNotification, nil]]; PASS([facade hits] == 1, "One notification was sent"); [facade release]; @@ -750,9 +732,8 @@ - (void)dealloc // dispatch one notification per change. [observee insertObject:@"object1" inArrayWithHelpersAtIndex:0]; } - andExpectChangeCallbacks:@[ - insertCallbackPost, illegalChangeNotification - ]]; + andExpectChangeCallbacks: [NSArray arrayWithObjects: + insertCallbackPost, illegalChangeNotification, nil]]; PASS([facade hits] == 1, "One notification was sent"); [facade release]; @@ -772,12 +753,12 @@ - (void)dealloc insertCallbackPost = CHANGE_CB { PASS(change[NSKeyValueChangeNotificationIsPriorKey] == nil, "Post change"); - PASS_EQUAL(@(NSKeyValueChangeSetting), change[NSKeyValueChangeKindKey], + PASS_EQUAL(BOXI(NSKeyValueChangeSetting), change[NSKeyValueChangeKindKey], "Change is a setting"); - NSArray *expectedOld = @[ @"Value" ]; + NSArray *expectedOld = [NSArray arrayWithObjects: @"Value", nil]; PASS_EQUAL(expectedOld, change[NSKeyValueChangeOldKey], "Old value is correct"); - NSArray *expectedNew = @[ @"Value", @"Value" ]; + NSArray *expectedNew = [NSArray arrayWithObjects: @"Value", @"Value", nil]; PASS_EQUAL(expectedNew, change[NSKeyValueChangeNewKey], "New value is correct"); NSIndexSet *indexes = change[NSKeyValueChangeIndexesKey]; @@ -802,9 +783,8 @@ - (void)dealloc [observee insertObject:[DummyObject makeDummy] inArrayWithHelpersAtIndex:0]; } - andExpectChangeCallbacks:@[ - insertCallbackPost, illegalChangeNotification - ]]; + andExpectChangeCallbacks: [NSArray arrayWithObjects: + insertCallbackPost, illegalChangeNotification, nil]]; PASS([facade hits] == 1, "One notification was sent"); [facade release]; @@ -846,7 +826,7 @@ - (void)dealloc performingBlock:^(Observee *observee) { [observee addObjectToManualArray:@"object1"]; } - andExpectChangeCallbacks:@[ onlyNewCallback, illegalChangeNotification ]]; + andExpectChangeCallbacks: [NSArray arrayWithObjects: onlyNewCallback, illegalChangeNotification, nil]]; [observee removeObserver:firstFacade.observer forKeyPath:@"manualNotificationArray"]; @@ -865,19 +845,22 @@ - (void)dealloc { START_SET("NSArrayShouldNotBeObservable"); - NSArray *test = @[ @1, @2, @3 ]; + NSArray *test = [NSArray arrayWithObjects: BOXI(1), BOXI(2), BOXI(3), nil]; TestObserver *observer = [TestObserver new]; - PASS_ANY_THROW([test addObserver:observer + PASS_EXCEPTION([test addObserver:observer forKeyPath:@"count" options:0 context:nil], - "NSArray is not observable"); + (NSString*)nil, + "NSArray is not observable"); // These would throw anyways because there should be no observer for the key // path, but test anyways - PASS_ANY_THROW([test removeObserver:observer forKeyPath:@"count"], - "Check removing non-existent observer"); - PASS_ANY_THROW([test removeObserver:observer forKeyPath:@"count" context:nil], + PASS_EXCEPTION([test removeObserver:observer forKeyPath:@"count"], + (NSString*)nil, + "Check removing non-existent observer"); + PASS_EXCEPTION([test removeObserver:observer forKeyPath:@"count" context:nil], + (NSString*)nil, "Check removing non-existent observer"); [observer release]; @@ -890,14 +873,15 @@ - (void)dealloc { START_SET("NSArrayShouldThrowWhenTryingToObserveIndexesOutOfRange"); - NSArray *test = @[ [Observee new], [Observee new] ]; + NSArray *test = [NSArray arrayWithObjects: [Observee new], [Observee new], nil]; TestObserver *observer = [TestObserver new]; - PASS_ANY_THROW([test addObserver:observer + PASS_EXCEPTION([test addObserver:observer toObjectsAtIndexes:[NSIndexSet indexSetWithIndex:4] forKeyPath:@"bareArray" options:0 context:nil], - "Observe index out of range"); + (NSString*)nil, + "Observe index out of range"); [observer release]; @@ -913,7 +897,7 @@ - (void)dealloc Observee *observee2 = [Observee new]; Observee *observee3 = [Observee new]; - NSArray *observeeArray = @[ observee1, observee2, observee3 ]; + NSArray *observeeArray = [NSArray arrayWithObjects: observee1, observee2, observee3, nil]; TestObserver *observer = [TestObserver new]; PASS_RUNS([observeeArray addObserver:observer @@ -979,20 +963,23 @@ - (void)dealloc { START_SET("NSSetShouldNotBeObservable"); - NSSet *test = [NSSet setWithObjects:@1, @2, @3, nil]; + NSSet *test = [NSSet setWithObjects:BOXI(1), BOXI(2), BOXI(3), nil]; TestObserver *observer = [TestObserver new]; - PASS_ANY_THROW([test addObserver:observer + PASS_EXCEPTION([test addObserver:observer forKeyPath:@"count" options:0 context:nil], - "NSSet is not observable"); + (NSString*)nil, + "NSSet is not observable"); // These would throw anyways because there should be no observer for the key // path, but test anyways - PASS_ANY_THROW([test removeObserver:observer forKeyPath:@"count"], - "Check removing non-existent observer"); - PASS_ANY_THROW([test removeObserver:observer forKeyPath:@"count" context:nil], - "Check removing non-existent observer"); + PASS_EXCEPTION([test removeObserver:observer forKeyPath:@"count"], + (NSString*)nil, + "Check removing non-existent observer"); + PASS_EXCEPTION([test removeObserver:observer forKeyPath:@"count" context:nil], + (NSString*)nil, + "Check removing non-existent observer"); [observer release]; @@ -1006,80 +993,80 @@ - (void)dealloc __block BOOL setSetChanged = NO; - // Union with @({@1, @2, @3}) to get @({@1, @2, @3}) + // Union with @({@(1), @(2), @(3)}) to get @({@(1), @(2), @(3)}) ChangeCallback unionCallback = CHANGE_CB { - PASS_EQUAL(@(NSKeyValueChangeInsertion), change[NSKeyValueChangeKindKey], + PASS_EQUAL(BOXI(NSKeyValueChangeInsertion), change[NSKeyValueChangeKindKey], "Union change is an insertion"); - NSSet *expected = [NSSet setWithObjects:@1, @2, @3, nil]; + NSSet *expected = [NSSet setWithObjects:BOXI(1), BOXI(2), BOXI(3), nil]; PASS_EQUAL(change[NSKeyValueChangeNewKey], expected, "Union new key is correct"); PASS(change[NSKeyValueChangeOldKey] == nil, "Union old key is nil"); }; - // Minus with @({@1}) to get @({@2, @3}) + // Minus with @({@(1)}) to get @({@(2), @(3)}) ChangeCallback minusCallback = CHANGE_CB { - PASS_EQUAL(change[NSKeyValueChangeKindKey], @(NSKeyValueChangeRemoval), + PASS_EQUAL(change[NSKeyValueChangeKindKey], BOXI(NSKeyValueChangeRemoval), "Minus change is a removal"); - PASS_EQUAL(change[NSKeyValueChangeOldKey], [NSSet setWithObject:@1], + PASS_EQUAL(change[NSKeyValueChangeOldKey], [NSSet setWithObject:BOXI(1)], "Minus old key is correct"); PASS(change[NSKeyValueChangeNewKey] == nil, "Minus new key is nil"); }; - // Add @1 to @({@2, @3}) to get @({@1, @2, @3}) + // Add @(1) to @({@(2), @(3)}) to get @({@(1), @(2), @(3)}) ChangeCallback addCallback = CHANGE_CB { - PASS_EQUAL(@(NSKeyValueChangeInsertion), change[NSKeyValueChangeKindKey], + PASS_EQUAL(BOXI(NSKeyValueChangeInsertion), change[NSKeyValueChangeKindKey], "Add change is an insertion"); NSLog(@"Change %@", change); - PASS_EQUAL([NSSet setWithObject:@1], change[NSKeyValueChangeNewKey], + PASS_EQUAL([NSSet setWithObject:BOXI(1)], change[NSKeyValueChangeNewKey], "Add new key is correct"); PASS(change[NSKeyValueChangeOldKey] == nil, "Add old key is nil"); }; - // Remove @1 from @({@1, @2, @3}) to get @({@2, @3}) + // Remove @(1) from @({@(1), @(2), @(3)}) to get @({@(2), @(3)}) ChangeCallback removeCallback = CHANGE_CB { - PASS_EQUAL(@(NSKeyValueChangeRemoval), change[NSKeyValueChangeKindKey], + PASS_EQUAL(BOXI(NSKeyValueChangeRemoval), change[NSKeyValueChangeKindKey], "Remove change is a removal"); - PASS_EQUAL([NSSet setWithObject:@1], change[NSKeyValueChangeOldKey], + PASS_EQUAL([NSSet setWithObject:BOXI(1)], change[NSKeyValueChangeOldKey], "Remove old key is correct"); PASS(change[NSKeyValueChangeNewKey] == nil, "Remove new key is nil"); }; - // Intersect with @({@2}) to get @({2}) + // Intersect with @({@(2)}) to get @({2}) ChangeCallback intersectCallback = CHANGE_CB { - PASS_EQUAL(@(NSKeyValueChangeRemoval), change[NSKeyValueChangeKindKey], + PASS_EQUAL(BOXI(NSKeyValueChangeRemoval), change[NSKeyValueChangeKindKey], "Intersect change is a removal"); - NSSet *expected = [NSSet setWithObject:@3]; + NSSet *expected = [NSSet setWithObject:BOXI(3)]; PASS_EQUAL(expected, change[NSKeyValueChangeOldKey], "Intersect old key is correct"); PASS(change[NSKeyValueChangeNewKey] == nil, "Intersect new key is nil"); }; - // Set with @({@3}) to get @({@3}) + // Set with @({@(3)}) to get @({@(3)}) ChangeCallback setCallback = CHANGE_CB { if (setSetChanged) { - PASS_EQUAL(@(NSKeyValueChangeReplacement), + PASS_EQUAL(BOXI(NSKeyValueChangeReplacement), change[NSKeyValueChangeKindKey], "Set change is a replacement"); - PASS_EQUAL([NSSet setWithObject:@2], change[NSKeyValueChangeOldKey], + PASS_EQUAL([NSSet setWithObject:BOXI(2)], change[NSKeyValueChangeOldKey], "Set old key is correct"); - PASS_EQUAL([NSSet setWithObject:@3], change[NSKeyValueChangeNewKey], + PASS_EQUAL([NSSet setWithObject:BOXI(3)], change[NSKeyValueChangeNewKey], "Set new key is correct"); } // setXxx method is not automatically swizzled for observation else { - PASS_EQUAL(@(NSKeyValueChangeSetting), change[NSKeyValueChangeKindKey], + PASS_EQUAL(BOXI(NSKeyValueChangeSetting), change[NSKeyValueChangeKindKey], "Set change is a setting"); - PASS_EQUAL([NSSet setWithObject:@3], change[NSKeyValueChangeOldKey], + PASS_EQUAL([NSSet setWithObject:BOXI(3)], change[NSKeyValueChangeOldKey], "Set old key is correct"); - PASS_EQUAL([NSSet setWithObject:@3], change[NSKeyValueChangeNewKey], + PASS_EQUAL([NSSet setWithObject:BOXI(3)], change[NSKeyValueChangeNewKey], "Set new key is correct"); } }; @@ -1099,16 +1086,16 @@ - (void)dealloc // This set is assisted by setter functions, and should also // dispatch one notification per change. [observee - addSetWithHelpers:[NSSet setWithObjects:@1, @2, @3, nil]]; - [observee removeSetWithHelpers:[NSSet setWithObject:@1]]; - [observee addSetWithHelpersObject:@1]; - [observee removeSetWithHelpersObject:@1]; - [observee intersectSetWithHelpers:[NSSet setWithObject:@2]]; - [observee setSetWithHelpers:[NSSet setWithObject:@3]]; + addSetWithHelpers:[NSSet setWithObjects:BOXI(1), BOXI(2), BOXI(3), nil]]; + [observee removeSetWithHelpers:[NSSet setWithObject:BOXI(1)]]; + [observee addSetWithHelpersObject:BOXI(1)]; + [observee removeSetWithHelpersObject:BOXI(1)]; + [observee intersectSetWithHelpers:[NSSet setWithObject:BOXI(2)]]; + [observee setSetWithHelpers:[NSSet setWithObject:BOXI(3)]]; } - andExpectChangeCallbacks:@[ + andExpectChangeCallbacks: [NSArray arrayWithObjects: unionCallback, minusCallback, addCallback, removeCallback, - intersectCallback, setCallback, illegalChangeNotification + intersectCallback, setCallback, illegalChangeNotification, nil ]]; PASS([facade hits] == 6, "All six notifications were sent (setWithHelpers)"); @@ -1128,17 +1115,16 @@ - (void)dealloc // The proxy set is a NSKeyValueIvarMutableSet NSMutableSet *proxySet = [observee mutableSetValueForKey:@"kvcMediatedSet"]; - [proxySet unionSet:[NSSet setWithObjects:@1, @2, @3, nil]]; - [proxySet minusSet:[NSSet setWithObject:@1]]; - [proxySet addObject:@1]; - [proxySet removeObject:@1]; - [proxySet intersectSet:[NSSet setWithObject:@2]]; - [proxySet setSet:[NSSet setWithObject:@3]]; + [proxySet unionSet:[NSSet setWithObjects:BOXI(1), BOXI(2), BOXI(3), nil]]; + [proxySet minusSet:[NSSet setWithObject:BOXI(1)]]; + [proxySet addObject:BOXI(1)]; + [proxySet removeObject:BOXI(1)]; + [proxySet intersectSet:[NSSet setWithObject:BOXI(2)]]; + [proxySet setSet:[NSSet setWithObject:BOXI(3)]]; } - andExpectChangeCallbacks:@[ + andExpectChangeCallbacks: [NSArray arrayWithObjects: unionCallback, minusCallback, addCallback, removeCallback, - intersectCallback, setCallback, illegalChangeNotification - ]]; + intersectCallback, setCallback, illegalChangeNotification, nil]]; PASS([facade hits] == 6, "All six notifications were sent (kvcMediatedSet)"); [observee release]; @@ -1152,17 +1138,16 @@ - (void)dealloc | NSKeyValueObservingOptionOld performingBlock:^(Observee *observee) { // Manually should dispatch one notification per change - [observee manualUnionSet:[NSSet setWithObjects:@1, @2, @3, nil]]; - [observee manualMinusSet:[NSSet setWithObject:@1]]; - [observee manualSetAddObject:@1]; - [observee manualSetRemoveObject:@1]; - [observee manualIntersectSet:[NSSet setWithObject:@2]]; - [observee manualSetSet:[NSSet setWithObject:@3]]; + [observee manualUnionSet:[NSSet setWithObjects:BOXI(1), BOXI(2), BOXI(3), nil]]; + [observee manualMinusSet:[NSSet setWithObject:BOXI(1)]]; + [observee manualSetAddObject:BOXI(1)]; + [observee manualSetRemoveObject:BOXI(1)]; + [observee manualIntersectSet:[NSSet setWithObject:BOXI(2)]]; + [observee manualSetSet:[NSSet setWithObject:BOXI(3)]]; } - andExpectChangeCallbacks:@[ + andExpectChangeCallbacks: [NSArray arrayWithObjects: unionCallback, minusCallback, addCallback, removeCallback, - intersectCallback, setCallback, illegalChangeNotification - ]]; + intersectCallback, setCallback, illegalChangeNotification, nil]]; PASS([facade hits] == 6, "All six notifications were sent (manualNotificationSet)"); @@ -1176,17 +1161,16 @@ - (void)dealloc // The proxy set is a NSKeyValueIvarMutableSet NSMutableSet *proxySet = [observee mutableSetValueForKey:@"proxySet"]; - [proxySet unionSet:[NSSet setWithObjects:@1, @2, @3, nil]]; - [proxySet minusSet:[NSSet setWithObject:@1]]; - [proxySet addObject:@1]; - [proxySet removeObject:@1]; - [proxySet intersectSet:[NSSet setWithObject:@2]]; - [proxySet setSet:[NSSet setWithObject:@3]]; + [proxySet unionSet:[NSSet setWithObjects:BOXI(1), BOXI(2), BOXI(3), nil]]; + [proxySet minusSet:[NSSet setWithObject:BOXI(1)]]; + [proxySet addObject:BOXI(1)]; + [proxySet removeObject:BOXI(1)]; + [proxySet intersectSet:[NSSet setWithObject:BOXI(2)]]; + [proxySet setSet:[NSSet setWithObject:BOXI(3)]]; } - andExpectChangeCallbacks:@[ + andExpectChangeCallbacks: [NSArray arrayWithObjects: unionCallback, minusCallback, addCallback, removeCallback, - intersectCallback, setCallback, illegalChangeNotification - ]]; + intersectCallback, setCallback, illegalChangeNotification, nil]]; PASS([facade hits] == 6, "All six notifications were sent (proxySet)"); /* Indirect slow proxy via NSInvocation to test NSKeyValueSlowMutableSet */ @@ -1198,17 +1182,16 @@ - (void)dealloc performingBlock:^(Observee *observee) { NSMutableSet *proxySet = [observee mutableSetValueForKey:@"proxyRoSet"]; - [proxySet unionSet:[NSSet setWithObjects:@1, @2, @3, nil]]; - [proxySet minusSet:[NSSet setWithObject:@1]]; - [proxySet addObject:@1]; - [proxySet removeObject:@1]; - [proxySet intersectSet:[NSSet setWithObject:@2]]; - [proxySet setSet:[NSSet setWithObject:@3]]; + [proxySet unionSet:[NSSet setWithObjects:BOXI(1), BOXI(2), BOXI(3), nil]]; + [proxySet minusSet:[NSSet setWithObject:BOXI(1)]]; + [proxySet addObject:BOXI(1)]; + [proxySet removeObject:BOXI(1)]; + [proxySet intersectSet:[NSSet setWithObject:BOXI(2)]]; + [proxySet setSet:[NSSet setWithObject:BOXI(3)]]; } - andExpectChangeCallbacks:@[ + andExpectChangeCallbacks: [NSArray arrayWithObjects: unionCallback, minusCallback, addCallback, removeCallback, - intersectCallback, setCallback, illegalChangeNotification - ]]; + intersectCallback, setCallback, illegalChangeNotification, nil]]; PASS([facade hits] == 6, "All six notifications were sent (proxySet)"); [observee release]; @@ -1256,4 +1239,4 @@ - (void)dealloc return 0; } -#endif \ No newline at end of file +#endif diff --git a/Tests/base/NSKVOSupport/newoldvalues.m b/Tests/base/NSKVOSupport/newoldvalues.m index 7e9254556..d30b472c1 100644 --- a/Tests/base/NSKVOSupport/newoldvalues.m +++ b/Tests/base/NSKVOSupport/newoldvalues.m @@ -1,76 +1,156 @@ #import #import "ObjectTesting.h" -#if defined(__OBJC2__) - @class Bar; @interface Foo : NSObject -@property (assign) Bar *globalBar; -@property (assign) NSInteger a; -@property (readonly) NSInteger b; +{ + Bar *globalBar; + NSInteger a; +} @end @interface Bar : NSObject -@property (assign) NSInteger x; -@property (strong, nonatomic) Foo *firstFoo; -@property (strong, nonatomic) Foo *secondFoo; +{ + NSInteger x; + Foo *firstFoo; + Foo *secondFoo; +} +- (NSInteger) x; @end @implementation Foo -+ (NSSet *)keyPathsForValuesAffectingB ++ (NSSet *) keyPathsForValuesAffectingB { - return [NSSet setWithArray:@[ @"a", @"globalBar.x" ]]; + return [NSSet setWithArray: [NSArray arrayWithObjects: + @"a", @"globalBar.x", nil]]; } -- (NSInteger)b +- (NSInteger) a +{ + return a; +} +- (void) setA: (NSInteger)v +{ + a = v; +} +- (NSInteger) b +{ + return [self a] + [globalBar x]; +} +- (Bar*) globalBar +{ + return globalBar; +} +- (void) setGlobalBar: (Bar*)v { - return self.a + self.globalBar.x; + globalBar = v; } @end @implementation Bar +- (Foo*) firstFoo +{ + return firstFoo; +} +- (void) setFirstFoo: (Foo*)v +{ + firstFoo = v; +} +- (Foo*) secondFoo +{ + return secondFoo; +} +- (void) setSecondFoo: (Foo*)v +{ + secondFoo = v; +} +- (NSInteger) x +{ + return x; +} +- (void) setX: (NSInteger)v +{ + x = v; +} + - (id)init { self = [super init]; if (self) { - self.firstFoo = [Foo new]; - self.firstFoo.globalBar = self; - self.secondFoo = [Foo new]; - self.secondFoo.globalBar = self; + [self setFirstFoo: [Foo new]]; + [[self firstFoo] setGlobalBar: self]; + [self setSecondFoo: [Foo new]]; + [[self secondFoo] setGlobalBar: self]; } return self; } + @end @interface Observer : NSObject -@property (assign) Foo *object; -@property (assign) NSInteger expectedOldValue; -@property (assign) NSInteger expectedNewValue; -@property (assign) NSInteger receivedCalls; +{ + Foo *object; + NSInteger expectedOldValue; + NSInteger expectedNewValue; + NSInteger receivedCalls; +} @end @implementation Observer +- (NSInteger) expectedOldValue +{ + return expectedOldValue; +} +- (void) setExpectedOldValue: (NSInteger)v +{ + expectedOldValue = v; +} +- (NSInteger) expectedNewValue +{ + return expectedNewValue; +} +- (void) setExpectedNewValue: (NSInteger)v +{ + expectedNewValue = v; +} +- (Foo*) object +{ + return object; +} +- (void) setObject: (Foo*)v +{ + object = v; +} +- (NSInteger) receivedCalls +{ + return receivedCalls; +} +- (void) setReceivedCalls: (NSInteger)v +{ + receivedCalls = v; +} + - (id)init { self = [super init]; if (self) { - self.receivedCalls = 0; + [self setReceivedCalls: 0]; } return self; } static char observerContext; -- (void)startObserving:(Foo *)target +- (void) startObserving:(Foo *)target { - self.object = target; + [self setObject: target]; [target addObserver:self forKeyPath:@"b" @@ -79,21 +159,21 @@ - (void)startObserving:(Foo *)target } - (void)observeValueForKeyPath:(NSString *)keyPath - ofObject:(id)object - change:(NSDictionary *)change + ofObject:(id)o + change:(NSDictionary *)change context:(void *)context { PASS(context == &observerContext, "context is correct"); - PASS(object == self.object, "object is correct"); + PASS(o == [self object], "object is correct"); - id newValue = change[NSKeyValueChangeNewKey]; - id oldValue = change[NSKeyValueChangeOldKey]; + id newValue = [change objectForKey: NSKeyValueChangeNewKey]; + id oldValue = [change objectForKey: NSKeyValueChangeOldKey]; PASS([oldValue integerValue] == self.expectedOldValue, "new value in change dict"); PASS([newValue integerValue] == self.expectedNewValue, "old value in change dict"); - self.receivedCalls++; + [self setReceivedCalls: [self receivedCalls] + 1]; } @end @@ -104,45 +184,39 @@ - (void)observeValueForKeyPath:(NSString *)keyPath NSAutoreleasePool *arp = [NSAutoreleasePool new]; Bar *bar = [Bar new]; - bar.x = 0; - bar.firstFoo.a = 1; - bar.secondFoo.a = 2; + [bar setX: 0]; + [[bar firstFoo] setA: 1]; + [[bar secondFoo] setA: 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; + [obs1 startObserving: [bar firstFoo]]; + [obs2 startObserving: [bar secondFoo]]; + + [obs1 setExpectedOldValue: 1]; + [obs1 setExpectedNewValue: 2]; + [obs2 setExpectedOldValue: 2]; + [obs2 setExpectedNewValue: 3]; + [bar setX: 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 setExpectedOldValue: 2]; + [obs1 setExpectedNewValue: 2]; + [obs2 setExpectedOldValue: 3]; + [obs2 setExpectedNewValue: 3]; + [bar setX: 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"); + [obs1 setExpectedOldValue: 2]; + [obs1 setExpectedNewValue: 3]; + [[bar firstFoo] setA: 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 \ No newline at end of file From 7003909fbb155b7f73bb0c5eab7e1721e374720c Mon Sep 17 00:00:00 2001 From: rfm Date: Mon, 5 Aug 2024 14:38:51 +0100 Subject: [PATCH 32/46] tests not working for build with olf compiler yet. --- Tests/base/NSKVOSupport/newoldvalues.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tests/base/NSKVOSupport/newoldvalues.m b/Tests/base/NSKVOSupport/newoldvalues.m index d30b472c1..b05c38f97 100644 --- a/Tests/base/NSKVOSupport/newoldvalues.m +++ b/Tests/base/NSKVOSupport/newoldvalues.m @@ -1,6 +1,8 @@ #import #import "ObjectTesting.h" +#if defined(__OBJC2__) + @class Bar; @interface Foo : NSObject @@ -220,3 +222,10 @@ - (void)observeValueForKeyPath:(NSString *)keyPath return 0; } +#else +int +main(int argc, char *argv[]) +{ + return 0; +} +#endif From a37d9f9a0a18eb9932241d5050ab820e836f7483 Mon Sep 17 00:00:00 2001 From: hmelder Date: Mon, 23 Sep 2024 16:07:45 +0200 Subject: [PATCH 33/46] NSKVOSupport: Remove ObjC2 features and mark tests failing on GCC as hopeful --- Tests/base/NSKVOSupport/general.m | 461 +++++++++++++++++++------ Tests/base/NSKVOSupport/newoldvalues.m | 26 +- 2 files changed, 378 insertions(+), 109 deletions(-) diff --git a/Tests/base/NSKVOSupport/general.m b/Tests/base/NSKVOSupport/general.m index edc346ced..9368642da 100644 --- a/Tests/base/NSKVOSupport/general.m +++ b/Tests/base/NSKVOSupport/general.m @@ -21,9 +21,6 @@ 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 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, @@ -50,8 +47,17 @@ This code is licensed under the MIT License (MIT). #define BOXI(V) [NSNumber numberWithInteger: (V)] #define MPAIR(K,V)\ [NSMutableDictionary dictionaryWithObjectsAndKeys: V, K, nil] +#define BOXBOOL(V) [NSNumber numberWithBool: (V)] -#if defined(__OBJC2__) +#if defined (__OBJC2__) +#define FLAKY_ON_GCC_START +#define FLAKY_ON_GCC_END +#else +#define FLAKY_ON_GCC_START \ + testHopeful = YES; +#define FLAKY_ON_GCC_END \ + testHopeful = NO; +#endif @interface TestKVOSelfObserver : NSObject { @@ -61,7 +67,8 @@ @interface TestKVOSelfObserver : NSObject @implementation TestKVOSelfObserver - (id)init { - if (self = [super init]) + self = [super init]; + if (self) { [self addObserver:self forKeyPath:@"dummy" options:0 context:nil]; } @@ -74,12 +81,27 @@ - (void)dealloc } @end -@interface TestKVOChange : NSObject -@property (nonatomic, copy) NSString *keypath; -@property (nonatomic, assign /*weak but no arc*/) id object; -@property (nonatomic, copy) NSDictionary *info; -@property (nonatomic, assign) void *context; +@interface TestKVOChange : NSObject { + NSString *_keypath; + id _object; + NSDictionary *_info; + void *_context; +} + +- (NSString *)keypath; +- (void)setKeypath:(NSString *)newKeypath; + +- (id)object; +- (void)setObject:(id)newObject; + +- (NSDictionary *)info; +- (void)setInfo:(NSDictionary *)newInfo; + +- (void *)context; +- (void)setContext:(void *)newContext; + @end + @implementation TestKVOChange + (id)changeWithKeypath:(NSString *)keypath object:(id)object @@ -87,12 +109,64 @@ + (id)changeWithKeypath:(NSString *)keypath context:(void *)context { TestKVOChange *change = [[[self alloc] init] autorelease]; - change.keypath = keypath; - change.object = object; - change.info = info; - change.context = context; + [change setKeypath: keypath]; + [change setObject: object]; + [change setInfo: info]; + [change setContext: context]; return change; } + +- (NSString *)keypath { + return _keypath; +} + +- (void)setKeypath:(NSString *)newKeypath +{ + if (_keypath != newKeypath) + { + [_keypath release]; + _keypath = [newKeypath copy]; + } +} + +- (id)object +{ + return _object; +} + +- (void)setObject:(id)newObject +{ + ASSIGN(_object, newObject); +} + +- (NSDictionary *)info +{ + return _info; +} + +- (void)setInfo:(NSDictionary *)newInfo +{ + ASSIGN(_info, [newInfo copy]); +} + +- (void *)context +{ + return _context; +} + +- (void)setContext:(void *)newContext +{ + _context = newContext; +} + +- (void)dealloc +{ + [_object release]; + [_keypath release]; + [_info release]; + [super dealloc]; +} + @end @interface TestKVOObserver : NSObject @@ -110,7 +184,8 @@ - (NSInteger)numberOfObservedChanges; @implementation TestKVOObserver - (id)init { - if (self = [super init]) + self = [super init]; + if (self) { _changedKeypaths = [NSMutableDictionary dictionary]; } @@ -123,11 +198,11 @@ - (void)observeValueForKeyPath:(NSString *)keypath { @synchronized(self) { - NSMutableSet *changeSet = _changedKeypaths[keypath]; + NSMutableSet *changeSet = [_changedKeypaths objectForKey:keypath]; if (!changeSet) { changeSet = [NSMutableSet set]; - _changedKeypaths[keypath] = changeSet; + [_changedKeypaths setObject: changeSet forKey: keypath]; } [changeSet addObject:[TestKVOChange changeWithKeypath:keypath object:object @@ -139,7 +214,7 @@ - (NSSet *)changesForKeypath:(NSString *)keypath { @synchronized(self) { - return [_changedKeypaths[keypath] copy]; + return [[_changedKeypaths objectForKey:keypath] copy]; } } - (void)clear @@ -168,6 +243,7 @@ - (NSInteger)numberOfObservedChanges int a, b, c; }; +/* @interface TestKVOObject : NSObject { NSString *_internal_derivedObjectProperty; @@ -197,10 +273,70 @@ @interface TestKVOObject : NSObject @property (nonatomic, retain) id boolTrigger2; @property (nonatomic, readonly) bool dependsOnTwoKeys; +- (void)incrementManualIntegerProperty; +@end +*/ + +@interface TestKVOObject : NSObject { + NSString *_internal_derivedObjectProperty; + NSString *_internal_keyDerivedTwoTimes; + int _manuallyNotifyingIntegerProperty; + int _ivarWithoutSetter; + + NSString *_nonNotifyingObjectProperty; + NSString *_basicObjectProperty; + uint32_t _basicPodProperty; + struct TestKVOStruct _structProperty; + TestKVOObject *_cascadableKey; + id _recursiveDependent1; + id _recursiveDependent2; + NSMutableDictionary *_dictionaryProperty; + id _boolTrigger1; + id _boolTrigger2; +} + +- (NSString *)nonNotifyingObjectProperty; +- (void)setNonNotifyingObjectProperty:(NSString *)newValue; + +- (NSString *)basicObjectProperty; +- (void)setBasicObjectProperty:(NSString *)newValue; + +- (uint32_t)basicPodProperty; +- (void)setBasicPodProperty:(uint32_t)newValue; + +- (struct TestKVOStruct)structProperty; +- (void)setStructProperty:(struct TestKVOStruct)newValue; + +- (NSString *)derivedObjectProperty; + +- (TestKVOObject *)cascadableKey; +- (void)setCascadableKey:(TestKVOObject *)newValue; + +- (TestKVOObject *)derivedCascadableKey; + +- (id)recursiveDependent1; +- (void)setRecursiveDependent1:(id)newValue; + +- (id)recursiveDependent2; +- (void)setRecursiveDependent2:(id)newValue; + +- (NSMutableDictionary *)dictionaryProperty; +- (void)setDictionaryProperty:(NSMutableDictionary *)newValue; + +- (id)boolTrigger1; +- (void)setBoolTrigger1:(id)newValue; + +- (id)boolTrigger2; +- (void)setBoolTrigger2:(id)newValue; + +- (bool)dependsOnTwoKeys; + // This modifies the internal integer property and notifies about it. - (void)incrementManualIntegerProperty; + @end + @implementation TestKVOObject - (void)dealloc { @@ -270,7 +406,7 @@ - (bool)dependsOnTwoSubKeys - (id)keyDependentOnSubKeypath { - return _dictionaryProperty[@"subDictionary"]; + return [_dictionaryProperty objectForKey:@"subDictionary"]; } + (BOOL)automaticallyNotifiesObserversOfManuallyNotifyingIntegerProperty @@ -314,18 +450,109 @@ - (void)incrementManualIntegerProperty _manuallyNotifyingIntegerProperty++; [self didChangeValueForKey:@"manuallyNotifyingIntegerProperty"]; } + +// Accessors + +- (NSString *)nonNotifyingObjectProperty { + return _nonNotifyingObjectProperty; +} + +- (void)setNonNotifyingObjectProperty:(NSString *)newValue { + ASSIGN(_nonNotifyingObjectProperty, newValue); +} + +- (NSString *)basicObjectProperty { + return _basicObjectProperty; +} + +- (uint32_t)basicPodProperty { + return _basicPodProperty; +} + +- (void)setBasicPodProperty:(uint32_t)newValue { + _basicPodProperty = newValue; +} + +- (struct TestKVOStruct)structProperty { + return _structProperty; +} + +- (void)setStructProperty:(struct TestKVOStruct)newValue { + _structProperty = newValue; +} + +- (TestKVOObject *)cascadableKey { + return _cascadableKey; +} + +- (void)setCascadableKey:(TestKVOObject *)newValue { + ASSIGN(_cascadableKey, newValue); +} + +- (id)recursiveDependent1 { + return _recursiveDependent1; +} + +- (void)setRecursiveDependent1:(id)newValue { + ASSIGN(_recursiveDependent1, newValue); +} + +- (id)recursiveDependent2 { + return _recursiveDependent2; +} + +- (void)setRecursiveDependent2:(id)newValue { + ASSIGN(_recursiveDependent2, newValue); +} + +- (NSMutableDictionary *)dictionaryProperty { + return _dictionaryProperty; +} + +- (void)setDictionaryProperty:(NSMutableDictionary *)newValue { + ASSIGN(_dictionaryProperty, newValue); +} + +- (id)boolTrigger1 { + return _boolTrigger1; +} + +- (void)setBoolTrigger1:(id)newValue { + ASSIGN(_boolTrigger1, newValue); +} + +- (id)boolTrigger2 { + return _boolTrigger2; +} + +- (void)setBoolTrigger2:(id)newValue { + ASSIGN(_boolTrigger2, newValue); +} + @end @interface TestKVOObject2 : NSObject -@property (nonatomic, assign) float someFloat; +{ + float _someFloat; +} + @end @implementation TestKVOObject2 +- (float)someFloat +{ + return _someFloat; +} +- (void)setSomeFloat:(float)newValue +{ + _someFloat = newValue; +} @end static void BasicChangeNotification() { START_SET("BasicChangeNotification"); + FLAKY_ON_GCC_START TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; @@ -361,6 +588,7 @@ @implementation TestKVOObject2 forKeyPath:@"basicObjectProperty"], "remove observer should not throw"); + FLAKY_ON_GCC_END END_SET("BasicChangeNotification"); } @@ -468,6 +696,7 @@ @implementation TestKVOObject2 CascadingNotificationWithEmptyLeaf() { START_SET("CascadingNotificationWithEmptyLeaf"); + FLAKY_ON_GCC_START TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; @@ -491,8 +720,8 @@ @implementation TestKVOObject2 anyObject] info] objectForKey:NSKeyValueChangeOldKey], [NSNull null], "The old value stored in the change notification should be null."); - - [observer clear]; + + [observer clear]; TestKVOObject *subObject2 = [[[TestKVOObject alloc] init] autorelease]; subObject2.basicObjectProperty = @"Hello"; @@ -521,6 +750,7 @@ @implementation TestKVOObject2 forKeyPath:@"cascadableKey.basicObjectProperty"], "remove observer should not throw"); + FLAKY_ON_GCC_END END_SET("CascadingNotificationWithEmptyLeaf"); } @@ -560,6 +790,7 @@ @implementation TestKVOObject2 DependentKeyNotification() { START_SET("DependentKeyNotification"); + FLAKY_ON_GCC_START TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; @@ -570,22 +801,28 @@ @implementation TestKVOObject2 context:NULL]; observed.basicObjectProperty = @"Hello"; - PASS_EQUAL([[observer changesForKeypath:@"basicObjectProperty"] count], 0, - "No changes on basicObjectProperty should have fired (we did not " - "register for it)."); - PASS_EQUAL([[observer changesForKeypath:@"derivedObjectProperty"] count], 1, - "One change on derivedObjectProperty should have fired."); + NSSet *basicChanges = [observer changesForKeypath:@"basicObjectProperty"]; + NSSet *derivedChanges = [observer changesForKeypath:@"derivedObjectProperty"]; + + PASS(nil != derivedChanges, "derivedChanges should not be nil."); + PASS([basicChanges count] == 0, + "No changes on basicObjectProperty should have fired (we did not " + "register for it)."); + PASS([derivedChanges count] == 1, "One change on derivedObjectProperty should have fired."); + PASS_RUNS([observed removeObserver:observer forKeyPath:@"derivedObjectProperty"], "remove observer should not throw"); - PASS_EQUAL([[[[observer changesForKeypath:@"derivedObjectProperty"] anyObject] - info] objectForKey:NSKeyValueChangeNewKey], + derivedChanges = [observer changesForKeypath:@"derivedObjectProperty"]; + PASS(nil != derivedChanges, "derivedChanges should not be nil."); + PASS_EQUAL([[[derivedChanges anyObject] info] objectForKey:NSKeyValueChangeNewKey], @"!!!Hello!!!", "The new value stored in the change notification should be " "!!!Hello!!! (the derived object)."); + FLAKY_ON_GCC_END END_SET("DependentKeyNotification"); } @@ -666,6 +903,7 @@ @implementation TestKVOObject2 DisabledNotification() { // No notification for non-notifying keypaths. START_SET("DisabledNotification"); + FLAKY_ON_GCC_START NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; @@ -677,14 +915,17 @@ @implementation TestKVOObject2 context:NULL]; observed.nonNotifyingObjectProperty = @"Whatever"; - PASS_EQUAL([[observer changesForKeypath:@"nonNotifyingObjectProperty"] count], - 0, "No changes for nonNotifyingObjectProperty should have fired."); + NSSet *changes = [observer changesForKeypath:@"nonNotifyingObjectProperty"]; + + PASS([changes count] == 0, + "No changes on nonNotifyingObjectProperty should have fired."); PASS_RUNS([observed removeObserver:observer forKeyPath:@"nonNotifyingObjectProperty"], "remove observer should not throw"); PASS_RUNS([pool release], "release should not throw"); + FLAKY_ON_GCC_END END_SET("DisabledNotification"); } @@ -692,6 +933,7 @@ @implementation TestKVOObject2 DisabledInitialNotification() { // Initial notification for non-notifying keypaths. START_SET("DisabledInitialNotification"); + FLAKY_ON_GCC_START NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; @@ -703,14 +945,17 @@ @implementation TestKVOObject2 context:NULL]; observed.nonNotifyingObjectProperty = @"Whatever"; - PASS_EQUAL([[observer changesForKeypath:@"nonNotifyingObjectProperty"] count], - 1, - "An INITIAL notification for nonNotifyingObjectProperty should " - "have fired."); + NSSet *changes = [observer changesForKeypath:@"nonNotifyingObjectProperty"]; + + + + PASS(nil != changes, "changes should not be nil."); + PASS([changes count] == 1, + "An INITIAL notification for nonNotifyingObjectProperty should " + "have fired."); PASS_EQUAL(BOXF(NSKeyValueChangeSetting), - [[[[observer changesForKeypath:@"nonNotifyingObjectProperty"] - anyObject] info] objectForKey:NSKeyValueChangeKindKey], + [[[changes anyObject] info] objectForKey:NSKeyValueChangeKindKey], "The change kind should be NSKeyValueChangeSetting."); PASS_RUNS([observed removeObserver:observer @@ -718,6 +963,7 @@ @implementation TestKVOObject2 "remove observer should not throw"); PASS_RUNS([pool release], "release should not throw"); + FLAKY_ON_GCC_END END_SET("DisabledInitialNotification"); } @@ -758,6 +1004,7 @@ @implementation TestKVOObject2 { // Basic notification on a dictionary, which does not have properties or // ivars. START_SET("DictionaryNotification"); + FLAKY_ON_GCC_START NSMutableDictionary *observed = [NSMutableDictionary dictionary]; TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; @@ -778,11 +1025,16 @@ @implementation TestKVOObject2 [observed setValue:@"Whatever2" forKeyPath:@"arbitraryValue"]; [observed setValue:@"Whatever2" forKeyPath:@"subKey.basicObjectProperty"]; - PASS_EQUAL( - [[observer changesForKeypath:@"arbitraryValue"] count], 2, + NSSet *changes = [observer changesForKeypath:@"arbitraryValue"]; + + PASS(nil != changes, "changes should not be nil."); + PASS([changes count] == 2, "On a NSMutableDictionary, a change notification for arbitraryValue."); - PASS_EQUAL([[observer changesForKeypath:@"subKey.basicObjectProperty"] count], - 1, + + changes = [observer changesForKeypath:@"subKey.basicObjectProperty"]; + + PASS(nil != changes, "changes should not be nil."); + PASS([changes count] == 1, "On a NSMutableDictionary, a change notification for " "subKey.basicObjectProperty."); @@ -792,6 +1044,7 @@ @implementation TestKVOObject2 forKeyPath:@"subKey.basicObjectProperty"], "remove observer should not throw"); + FLAKY_ON_GCC_END END_SET("DictionaryNotification"); } @@ -799,6 +1052,7 @@ @implementation TestKVOObject2 BasicDeregistration() { // Deregistration test START_SET("BasicDeregistration"); + FLAKY_ON_GCC_START TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; @@ -830,10 +1084,11 @@ @implementation TestKVOObject2 subObject.basicObjectProperty = @"Hello"; - PASS_EQUAL( - [[observer changesForKeypath:@"cascadableKey.basicObjectProperty"] count], - 0, "No changes on cascadableKey.basicObjectProperty should have fired."); + NSSet *changes = [observer changesForKeypath:@"cascadableKey.basicObjectProperty"]; + + PASS([changes count] == 0, "No changes on cascadableKey.basicObjectProperty should have fired."); + FLAKY_ON_GCC_END END_SET("BasicDeregistration"); } @@ -854,14 +1109,12 @@ @implementation TestKVOObject2 subObject.basicObjectProperty = @"Hello"; observed.cascadableKey = subObject; - PASS_EQUAL([[observer - changesForKeypath:@"cascadableKey.derivedObjectProperty.length"] - count], - 1, "One change on cascade.derived.length should have fired."); + NSSet *changes = [observer changesForKeypath:@"cascadableKey.derivedObjectProperty.length"]; + + PASS(nil != changes, "changes should not be nil."); + PASS([changes count] == 1, "One change on cascade.derived.length should have fired."); PASS_EQUAL( - [[[[observer - changesForKeypath:@"cascadableKey.derivedObjectProperty.length"] - anyObject] info] objectForKey:NSKeyValueChangeNewKey], + [[[changes anyObject] info] objectForKey:NSKeyValueChangeNewKey], BOXI(11), "The new value stored in the change notification should a boxed 11."); @@ -1023,6 +1276,7 @@ @implementation TestKVOObject2 SubpathWithMultipleReplacement() { // Test key value replacement and re-registration (3) START_SET("SubpathWithMultipleReplacement"); + FLAKY_ON_GCC_START TestKVOObject *observed = [[TestKVOObject alloc] init]; TestKVOObserver *observer = [[TestKVOObserver alloc] init]; @@ -1050,6 +1304,7 @@ @implementation TestKVOObject2 [observer release]; [observed release]; + FLAKY_ON_GCC_END END_SET("SubpathWithMultipleReplacement"); } @@ -1158,6 +1413,7 @@ @implementation TestKVOObject2 CyclicDependency() { // Make sure that dependency loops don't cause crashes. START_SET("CyclicDependency"); + FLAKY_ON_GCC_START TestKVOObject *observed = [[TestKVOObject alloc] init]; TestKVOObserver *observer = [[TestKVOObserver alloc] init]; @@ -1173,7 +1429,7 @@ @implementation TestKVOObject2 "add observer should not throw"); observed.recursiveDependent1 = @"x"; observed.recursiveDependent2 = @"y"; - PASS_EQUAL(4, [observer numberOfObservedChanges], + PASS(4 == [observer numberOfObservedChanges], "Four changes should have " "been observed."); PASS_RUNS([observed removeObserver:observer @@ -1186,6 +1442,7 @@ @implementation TestKVOObject2 [observer release]; [observed release]; + FLAKY_ON_GCC_END END_SET("CyclicDependency"); } @@ -1193,6 +1450,7 @@ @implementation TestKVOObject2 ObserveAllProperties() { START_SET("ObserveAllProperties"); + FLAKY_ON_GCC_START TestKVOObject *observed = [[TestKVOObject alloc] init]; TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; @@ -1232,7 +1490,7 @@ @implementation TestKVOObject2 subObject.basicObjectProperty = @"Hello"; observed.cascadableKey = subObject; // 2 here - PASS_EQUAL([observer numberOfObservedChanges], 6, + PASS([observer numberOfObservedChanges] == 6, "There should have been 6 observed changes on the observer."); PASS_RUNS([observed removeObserver:observer @@ -1252,6 +1510,7 @@ @implementation TestKVOObject2 "remove observer for keyPath cascadableKey.basicObjectProperty " "should not throw"); + FLAKY_ON_GCC_END END_SET("ObserveAllProperties"); } @@ -1259,6 +1518,7 @@ @implementation TestKVOObject2 RemoveWithoutContext() { // Test removal without specifying context. START_SET("RemoveWithoutContext"); + FLAKY_ON_GCC_START TestKVOObject *observed = [[TestKVOObject alloc] init]; TestKVOObserver *observer = [[TestKVOObserver alloc] init]; @@ -1278,7 +1538,7 @@ @implementation TestKVOObject2 observed.basicObjectProperty = @""; - PASS_EQUAL([observer numberOfObservedChanges], 1, + PASS([observer numberOfObservedChanges] == 1, "There should be only one change notification despite " "registering two with contexts."); @@ -1289,6 +1549,7 @@ @implementation TestKVOObject2 [observer release]; [observed release]; + FLAKY_ON_GCC_END END_SET("RemoveWithoutContext"); } @@ -1296,6 +1557,7 @@ @implementation TestKVOObject2 RemoveWithDuplicateContext() { // Test adding duplicate contexts START_SET("RemoveWithDuplicateContext"); + FLAKY_ON_GCC_START TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; @@ -1311,7 +1573,7 @@ @implementation TestKVOObject2 observed.basicObjectProperty = @""; - PASS_EQUAL([observer numberOfObservedChanges], 2, + PASS([observer numberOfObservedChanges] == 2, "There should be two observed changes, despite the identical " "registration."); @@ -1323,7 +1585,7 @@ @implementation TestKVOObject2 observed.basicObjectProperty = @""; - PASS_EQUAL([observer numberOfObservedChanges], 3, + PASS([observer numberOfObservedChanges] == 3, "There should be one additional observed change; the removal " "should have only effected one."); @@ -1332,6 +1594,7 @@ @implementation TestKVOObject2 context:(void *) (1)], "removing observer forKeyPath=basicObjectProperty does not throw"); + FLAKY_ON_GCC_END END_SET("RemoveWithDuplicateContext"); } @@ -1339,6 +1602,7 @@ @implementation TestKVOObject2 RemoveOneOfTwoObservers() { // Test adding duplicate contexts START_SET("RemoveOneOfTwoObservers"); + FLAKY_ON_GCC_START TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; @@ -1355,9 +1619,9 @@ @implementation TestKVOObject2 observed.basicObjectProperty = @""; - PASS_EQUAL([observer numberOfObservedChanges], 1, + PASS([observer numberOfObservedChanges] == 1, "There should be one observed change per observer."); - PASS_EQUAL([observer2 numberOfObservedChanges], 1, + PASS([observer2 numberOfObservedChanges] == 1, "There should be one observed change per observer."); PASS_RUNS([observed removeObserver:observer2 @@ -1366,17 +1630,18 @@ @implementation TestKVOObject2 observed.basicObjectProperty = @""; - PASS_EQUAL([observer numberOfObservedChanges], 2, + PASS([observer numberOfObservedChanges] == 2, "There should be one additional observed change; the removal " "should have only removed the second observer."); - PASS_EQUAL([observer2 numberOfObservedChanges], 1, + PASS([observer2 numberOfObservedChanges] == 1, "Observer2 should have only observed one change."); PASS_RUNS([observed removeObserver:observer forKeyPath:@"basicObjectProperty"], "removing observer should not throw"); + FLAKY_ON_GCC_END END_SET("RemoveOneOfTwoObservers"); } @@ -1384,6 +1649,7 @@ @implementation TestKVOObject2 RemoveUnregistered() { // Test removing an urnegistered observer START_SET("RemoveUnregistered"); + FLAKY_ON_GCC_START TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; TestKVOObserver *observer = [[[TestKVOObserver alloc] init] autorelease]; @@ -1395,6 +1661,7 @@ @implementation TestKVOObject2 (NSString*)nil, "Removing an unregistered observer should throw an exception.") + FLAKY_ON_GCC_END END_SET("RemoveUnregistered"); } @@ -1425,7 +1692,7 @@ @implementation TestKVOObject2 options:0 context:nil]; observed.cascadableKey = child; - PASS_EQUAL([observer numberOfObservedChanges], 1, + PASS([observer numberOfObservedChanges] == 1, "One change should have " "been observed."); @@ -1463,7 +1730,7 @@ @implementation TestKVOObject2 observed.cascadableKey = child; observed.cascadableKey = child; - PASS_EQUAL([observer numberOfObservedChanges], 2, + PASS([observer numberOfObservedChanges] == 2, "Two changes should have " "been observed."); @@ -1481,6 +1748,7 @@ @implementation TestKVOObject2 SubpathOnDerivedKey() { START_SET("SubpathOnDerivedKey"); + FLAKY_ON_GCC_START NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; @@ -1501,7 +1769,7 @@ @implementation TestKVOObject2 child2.dictionaryProperty = [NSMutableDictionary dictionaryWithDictionary:MPAIR(@"Key1" , @"Value2")]; - PASS_EQUAL(2, [observer numberOfObservedChanges], + PASS(2 == [observer numberOfObservedChanges], "Two changes should have " "been observed."); @@ -1511,6 +1779,7 @@ @implementation TestKVOObject2 "remove observer should not throw"); PASS_RUNS([pool release], "release pool should not throw"); + FLAKY_ON_GCC_END END_SET("SubpathOnDerivedKey"); } @@ -1518,6 +1787,7 @@ @implementation TestKVOObject2 SubpathWithDerivedKeyBasedOnSubpath() { START_SET("SubpathWithDerivedKeyBasedOnSubpath"); + FLAKY_ON_GCC_START NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; @@ -1535,8 +1805,7 @@ @implementation TestKVOObject2 options:0 context:nil]; - mutableDictionary[@"subDictionary"] = - MPAIR(@"floatGuy" , BOXF(3.456)); // 1 notification + [mutableDictionary setObject: MPAIR(@"floatGuy" , BOXF(3.456)) forKey: @"subDictionary"]; // 1 notification NSMutableDictionary *mutableDictionary2 = MPAIR( @"subDictionary", MPAIR(@"floatGuy" , BOXF(5.678)) @@ -1544,10 +1813,9 @@ @implementation TestKVOObject2 observed.dictionaryProperty = mutableDictionary2; // 2nd notification - mutableDictionary2[@"subDictionary"] = - MPAIR(@"floatGuy" , BOXF(7.890)); // 3rd notification + [mutableDictionary2 setObject: MPAIR(@"floatGuy" , BOXF(7.890)) forKey: @"subDictionary"]; // 3rd notification - PASS_EQUAL(3, [observer numberOfObservedChanges], + PASS(3 == [observer numberOfObservedChanges], "Three changes should have " "been observed."); @@ -1556,6 +1824,7 @@ @implementation TestKVOObject2 "remove observer should not throw"); PASS_RUNS([pool release], "release pool should not throw"); + FLAKY_ON_GCC_END END_SET("SubpathWithDerivedKeyBasedOnSubpath"); } @@ -1563,6 +1832,7 @@ @implementation TestKVOObject2 MultipleObservers() { START_SET("MultipleObservers"); + FLAKY_ON_GCC_START NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; @@ -1622,6 +1892,7 @@ @implementation TestKVOObject2 PASS_RUNS([pool release], "release pool should not throw"); + FLAKY_ON_GCC_END END_SET("MultipleObservers"); } @@ -1629,6 +1900,7 @@ @implementation TestKVOObject2 DerivedKeyDependentOnDerivedKey() { START_SET("DerivedKeyDependentOnDerivedKey"); + FLAKY_ON_GCC_START NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; @@ -1643,7 +1915,7 @@ @implementation TestKVOObject2 observed.basicObjectProperty = @"KVO"; - PASS_EQUAL(1, [observer numberOfObservedChanges], + PASS(1 == [observer numberOfObservedChanges], "One change should have " "been observed."); PASS_EQUAL([[[[observer changesForKeypath:@"keyDerivedTwoTimes"] anyObject] @@ -1654,7 +1926,7 @@ @implementation TestKVOObject2 observed.basicObjectProperty = @"$$$"; - PASS_EQUAL(1, [observer numberOfObservedChanges], + PASS(1 == [observer numberOfObservedChanges], "One change should have " "been observed."); PASS_EQUAL([[[[observer changesForKeypath:@"keyDerivedTwoTimes"] anyObject] @@ -1666,6 +1938,7 @@ @implementation TestKVOObject2 "should not throw"); PASS_RUNS([pool release], "release pool should not throw"); + FLAKY_ON_GCC_END END_SET("DerivedKeyDependentOnDerivedKey"); } @@ -1673,6 +1946,7 @@ @implementation TestKVOObject2 DerivedKeyDependentOnTwoKeys() { START_SET("DerivedKeyDependentOnTwoKeys"); + FLAKY_ON_GCC_START NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; @@ -1685,10 +1959,10 @@ @implementation TestKVOObject2 observed.boolTrigger1 = @"firstObject"; - PASS_EQUAL(1, [observer numberOfObservedChanges], + PASS(1 == [observer numberOfObservedChanges], "One change should have " "been observed."); - PASS_EQUAL(@NO, + PASS_EQUAL(BOXBOOL(NO), [[[[observer changesForKeypath:@"dependsOnTwoKeys"] anyObject] info] objectForKey:NSKeyValueChangeNewKey], "The new value " @@ -1697,9 +1971,9 @@ @implementation TestKVOObject2 [observer clear]; observed.boolTrigger2 = @"secondObject"; - PASS_EQUAL(1, [observer numberOfObservedChanges], + PASS(1 == [observer numberOfObservedChanges], "One change should have been observed."); - PASS_EQUAL(@YES, + PASS_EQUAL(BOXBOOL(YES), [[[[observer changesForKeypath:@"dependsOnTwoKeys"] anyObject] info] objectForKey:NSKeyValueChangeNewKey], "The new value should be YES."); @@ -1708,6 +1982,7 @@ @implementation TestKVOObject2 "remove observer should not throw"); PASS_RUNS([pool release], "release pool should not throw"); + FLAKY_ON_GCC_END END_SET("DerivedKeyDependentOnTwoKeys"); } @@ -1715,6 +1990,7 @@ @implementation TestKVOObject2 DerivedKeyDependentOnTwoSubKeys() { START_SET("DerivedKeyDependentOnTwoSubKeys"); + FLAKY_ON_GCC_START NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; TestKVOObject *observed = [[[TestKVOObject alloc] init] autorelease]; @@ -1727,9 +2003,9 @@ @implementation TestKVOObject2 context:nil]; observed.cascadableKey = child; - PASS_EQUAL(1, [observer numberOfObservedChanges], + PASS(1 == [observer numberOfObservedChanges], "One change should have been observed."); - PASS_EQUAL(@NO, + PASS_EQUAL(BOXBOOL(NO), [[[[observer changesForKeypath:@"dependsOnTwoSubKeys"] anyObject] info] objectForKey:NSKeyValueChangeNewKey], "new value should be NO"); @@ -1737,9 +2013,9 @@ @implementation TestKVOObject2 [observer clear]; child.boolTrigger1 = @"firstObject"; - PASS_EQUAL(1, [observer numberOfObservedChanges], + PASS(1 == [observer numberOfObservedChanges], "One change should have been observed."); - PASS_EQUAL(@NO, + PASS_EQUAL(BOXBOOL(NO), [[[[observer changesForKeypath:@"dependsOnTwoSubKeys"] anyObject] info] objectForKey:NSKeyValueChangeNewKey], "new value should be NO"); @@ -1747,9 +2023,9 @@ @implementation TestKVOObject2 [observer clear]; child.boolTrigger2 = @"secondObject"; - PASS_EQUAL(1, [observer numberOfObservedChanges], + PASS(1 == [observer numberOfObservedChanges], "One change should have been observed."); - PASS_EQUAL(@YES, + PASS_EQUAL(BOXBOOL(YES), [[[[observer changesForKeypath:@"dependsOnTwoSubKeys"] anyObject] info] objectForKey:NSKeyValueChangeNewKey], "new value should be YES"); @@ -1759,6 +2035,7 @@ @implementation TestKVOObject2 "remove observer should not throw"); PASS_RUNS([pool release], "release pool should not throw"); + FLAKY_ON_GCC_END END_SET("DerivedKeyDependentOnTwoSubKeys"); } @@ -1789,17 +2066,17 @@ @implementation TestKVOObject2 NSDictionary *baseInfo = [[[observer changesForKeypath:@"cascadableKey"] anyObject] info]; PASS(nil != baseInfo, "There should be a change notification."); - PASS_EQUAL(oldObj, baseInfo[NSKeyValueChangeOldKey], + PASS_EQUAL(oldObj, [baseInfo objectForKey: NSKeyValueChangeOldKey], "The old value should be the old object."); - PASS_EQUAL(newObj, baseInfo[NSKeyValueChangeNewKey], + PASS_EQUAL(newObj, [baseInfo objectForKey: NSKeyValueChangeNewKey], "The new value should be the new object."); NSDictionary *subInfo = [[[observer changesForKeypath:@"cascadableKey.basicObjectProperty"] anyObject] info]; PASS(nil != subInfo, "There should be a change notification."); - PASS_EQUAL(@"Original", subInfo[NSKeyValueChangeOldKey], + PASS_EQUAL(@"Original", [subInfo objectForKey: NSKeyValueChangeOldKey], "The old value should be the old object's basicObjectProperty."); - PASS_EQUAL(@"NewObj", subInfo[NSKeyValueChangeNewKey], + PASS_EQUAL(@"NewObj", [subInfo objectForKey: NSKeyValueChangeNewKey], "The new value should be the new object's basicObjectProperty."); PASS_RUNS([observed removeObserver:observer forKeyPath:@"cascadableKey"], @@ -1889,19 +2166,3 @@ @implementation TestKVOObject2 DESTROY(arp); return 0; } - -#else -int -main(int argc, char *argv[]) -{ - NSAutoreleasePool *pool = [NSAutoreleasePool new]; - - NSLog(@"This test requires an Objective-C 2.0 runtime and is not supported " - @"on this platform."); - - DESTROY(pool); - - return 0; -} - -#endif diff --git a/Tests/base/NSKVOSupport/newoldvalues.m b/Tests/base/NSKVOSupport/newoldvalues.m index b05c38f97..c18636edd 100644 --- a/Tests/base/NSKVOSupport/newoldvalues.m +++ b/Tests/base/NSKVOSupport/newoldvalues.m @@ -1,7 +1,16 @@ #import #import "ObjectTesting.h" +#import "Testing.h" -#if defined(__OBJC2__) +#if defined (__OBJC2__) +#define FLAKY_ON_GCC_START +#define FLAKY_ON_GCC_END +#else +#define FLAKY_ON_GCC_START \ + testHopeful = YES; +#define FLAKY_ON_GCC_END \ + testHopeful = NO; +#endif @class Bar; @@ -185,6 +194,9 @@ - (void)observeValueForKeyPath:(NSString *)keyPath { NSAutoreleasePool *arp = [NSAutoreleasePool new]; + START_SET("newoldvalues"); + FLAKY_ON_GCC_START + Bar *bar = [Bar new]; [bar setX: 0]; [[bar firstFoo] setA: 1]; @@ -216,16 +228,12 @@ - (void)observeValueForKeyPath:(NSString *)keyPath [[bar firstFoo] setA: 2]; PASS([obs1 receivedCalls] == 3, "num observe calls"); PASS([obs2 receivedCalls] == 2, "num observe calls"); + + FLAKY_ON_GCC_END + END_SET("newoldvalues"); - DESTROY(arp); - return 0; -} + DESTROY(arp); -#else -int -main(int argc, char *argv[]) -{ return 0; } -#endif From bf4091684f54b91e658bd5ce55b2ec0f816eec11 Mon Sep 17 00:00:00 2001 From: hmelder Date: Mon, 14 Oct 2024 11:33:06 +0200 Subject: [PATCH 34/46] Call class method instead of private _keyPathsForValuesAffectingValueForKey --- Source/NSKVOSupport.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/NSKVOSupport.m b/Source/NSKVOSupport.m index c168a9372..cc3452f70 100644 --- a/Source/NSKVOSupport.m +++ b/Source/NSKVOSupport.m @@ -392,8 +392,8 @@ - (bool)isEmpty // Aggregate all keys whose values will affect us. if (dependents) { - NSSet *valueInfluencingKeys - = _keyPathsForValuesAffectingValueForKey([object class], key); + Class cls = [object class]; + NSSet *valueInfluencingKeys = [cls keyPathsForValuesAffectingValueForKey: key]; if (valueInfluencingKeys.count > 0) { // affectedKeyObservers is the list of observers that must be notified @@ -672,7 +672,7 @@ + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { - return _keyPathsForValuesAffectingValueForKey(self, key) ?: [NSSet set]; + return _keyPathsForValuesAffectingValueForKey(self, key) ?: [NSSet set]; // TODO(Hugo): Avoid constructing an empty set in every call } - (void)addObserver:(id)observer From 14a3af14be1c976b68fdb417abd8c7d564e02c68 Mon Sep 17 00:00:00 2001 From: hmelder Date: Mon, 14 Oct 2024 12:01:05 +0200 Subject: [PATCH 35/46] Move _keyPathsForValuesAffectingValueForKey body into class method and statically construct empty NSSet --- Source/NSKVOSupport.m | 139 +++++++++++++++++++++--------------------- 1 file changed, 71 insertions(+), 68 deletions(-) diff --git a/Source/NSKVOSupport.m b/Source/NSKVOSupport.m index cc3452f70..7b741548f 100644 --- a/Source/NSKVOSupport.m +++ b/Source/NSKVOSupport.m @@ -307,73 +307,6 @@ - (bool)isEmpty static void _removeKeyObserver(_NSKVOKeyObserver *keyObserver); -// Private helper that backs the default implementation of -// keyPathsForValuesAffectingValueForKey: Returns nil instead of constructing an -// empty set if no keyPaths are found Also used internally as a minor -// optimization, to avoid constructing an empty set when it is not needed -static NSSet * -_keyPathsForValuesAffectingValueForKey(Class self, NSString *key) -{ - // This function can be a KVO bottleneck, so it will prefer to use c string - // manipulation when safe - NSUInteger keyLength = [key length]; - if (keyLength > 0) - { - static const char *const sc_prefix = "keyPathsForValuesAffecting"; - static const size_t sc_prefixLength = 26; // strlen(sc_prefix) - static const size_t sc_bufferSize = 128; - - // max length of a key that can guaranteed fit in the char buffer, - // even if UTF16->UTF8 conversion causes length to double, or a null - // terminator is needed - static const size_t sc_safeKeyLength - = (sc_bufferSize - sc_prefixLength) / 2 - 1; // 50 - - const char *rawKey; - size_t rawKeyLength; - SEL sel; - - rawKey = [key UTF8String]; - rawKeyLength = strlen(rawKey); - - if (keyLength <= sc_safeKeyLength) - { - // fast path using c string manipulation, will cover most cases, as - // most keyPaths are short - char selectorName[sc_bufferSize] - = "keyPathsForValuesAffecting"; // 26 chars - selectorName[sc_prefixLength] = toupper(rawKey[0]); - // Copy the rest of the key, including the null terminator - memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], rawKeyLength); - sel = sel_registerName(selectorName); - } - else // Guaranteed path for long keyPaths - { - size_t keyLength; - size_t bufferSize; - char *selectorName; - - keyLength = strlen(rawKey); - bufferSize = sc_prefixLength + keyLength + 1; - selectorName = (char *) malloc(bufferSize); - memcpy(selectorName, sc_prefix, sc_prefixLength); - - selectorName[sc_prefixLength] = toupper(rawKey[0]); - // Copy the rest of the key, including the null terminator - memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], keyLength); - - sel = sel_registerName(selectorName); - free(selectorName); - } - - if ([self respondsToSelector:sel]) - { - return [self performSelector:sel]; - } - } - return nil; -} - // Add all observers with declared dependencies on this one: // * All keypaths that could trigger a change (keypaths for values affecting // us). @@ -672,7 +605,77 @@ + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { - return _keyPathsForValuesAffectingValueForKey(self, key) ?: [NSSet set]; // TODO(Hugo): Avoid constructing an empty set in every call + static NSSet *emptySet = nil; + static gs_mutex_t lock = GS_MUTEX_INIT_STATIC; + if (nil == emptySet) + { + GS_MUTEX_LOCK(lock); + if (nil == emptySet) + { + emptySet = [[NSSet alloc] init]; + [NSObject leakAt: &emptySet]; + } + GS_MUTEX_UNLOCK(lock); + } + + // This function can be a KVO bottleneck, so it will prefer to use c string + // manipulation when safe + NSUInteger keyLength = [key length]; + if (keyLength > 0) + { + static const char *const sc_prefix = "keyPathsForValuesAffecting"; + static const size_t sc_prefixLength = 26; // strlen(sc_prefix) + static const size_t sc_bufferSize = 128; + + // max length of a key that can guaranteed fit in the char buffer, + // even if UTF16->UTF8 conversion causes length to double, or a null + // terminator is needed + static const size_t sc_safeKeyLength + = (sc_bufferSize - sc_prefixLength) / 2 - 1; // 50 + + const char *rawKey; + size_t rawKeyLength; + SEL sel; + + rawKey = [key UTF8String]; + rawKeyLength = strlen(rawKey); + + if (keyLength <= sc_safeKeyLength) + { + // fast path using c string manipulation, will cover most cases, as + // most keyPaths are short + char selectorName[sc_bufferSize] + = "keyPathsForValuesAffecting"; // 26 chars + selectorName[sc_prefixLength] = toupper(rawKey[0]); + // Copy the rest of the key, including the null terminator + memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], rawKeyLength); + sel = sel_registerName(selectorName); + } + else // Guaranteed path for long keyPaths + { + size_t keyLength; + size_t bufferSize; + char *selectorName; + + keyLength = strlen(rawKey); + bufferSize = sc_prefixLength + keyLength + 1; + selectorName = (char *) malloc(bufferSize); + memcpy(selectorName, sc_prefix, sc_prefixLength); + + selectorName[sc_prefixLength] = toupper(rawKey[0]); + // Copy the rest of the key, including the null terminator + memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], keyLength); + + sel = sel_registerName(selectorName); + free(selectorName); + } + + if ([self respondsToSelector:sel]) + { + return [self performSelector:sel]; + } + } + return emptySet; } - (void)addObserver:(id)observer From e6c9108923670f579ecd88f346155fc57ab2fb7c Mon Sep 17 00:00:00 2001 From: hmelder Date: Mon, 14 Oct 2024 12:11:49 +0200 Subject: [PATCH 36/46] Remove @synchronize --- Tests/base/NSKVOSupport/general.m | 42 +++++++++++++++++-------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/Tests/base/NSKVOSupport/general.m b/Tests/base/NSKVOSupport/general.m index 9368642da..c215bde32 100644 --- a/Tests/base/NSKVOSupport/general.m +++ b/Tests/base/NSKVOSupport/general.m @@ -172,6 +172,7 @@ - (void)dealloc @interface TestKVOObserver : NSObject { NSMutableDictionary *_changedKeypaths; + NSLock *_lock; } - (void)observeValueForKeyPath:(NSString *)keypath ofObject:(id)object @@ -187,7 +188,8 @@ - (id)init self = [super init]; if (self) { - _changedKeypaths = [NSMutableDictionary dictionary]; + _changedKeypaths = [NSMutableDictionary new]; + _lock = [NSLock new]; } return self; } @@ -196,7 +198,6 @@ - (void)observeValueForKeyPath:(NSString *)keypath change:(NSDictionary *)change context:(void *)context { - @synchronized(self) { NSMutableSet *changeSet = [_changedKeypaths objectForKey:keypath]; if (!changeSet) @@ -212,29 +213,32 @@ - (void)observeValueForKeyPath:(NSString *)keypath } - (NSSet *)changesForKeypath:(NSString *)keypath { - @synchronized(self) - { - return [[_changedKeypaths objectForKey:keypath] copy]; - } + [_lock lock]; + NSSet *paths = [[_changedKeypaths objectForKey:keypath] copy]; + [_lock unlock]; + return paths; } - (void)clear { - @synchronized(self) - { - [_changedKeypaths removeAllObjects]; - } + [_lock lock]; + [_changedKeypaths removeAllObjects]; + [_lock unlock]; } - (NSInteger)numberOfObservedChanges { - @synchronized(self) - { - NSInteger accumulator = 0; - for (NSString *keypath in [_changedKeypaths allKeys]) - { - accumulator += [[_changedKeypaths objectForKey:keypath] count]; - } - return accumulator; - } + [_lock lock]; + NSInteger accumulator = 0; + for (NSString *keypath in [_changedKeypaths allKeys]) + { + accumulator += [[_changedKeypaths objectForKey:keypath] count]; + } + [_lock unlock]; + return accumulator; +} + +- (void) dealloc { + [_lock release]; + [_changedKeypaths release]; } @end From ef9781f1be9848f7b952bb14e259b365ed891bde Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 15 Oct 2024 12:03:57 +0200 Subject: [PATCH 37/46] NSUserDefaults: Change notification should contain old value from other domains aswell --- Source/NSUserDefaults.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/NSUserDefaults.m b/Source/NSUserDefaults.m index 1326aff1d..f1c477baf 100644 --- a/Source/NSUserDefaults.m +++ b/Source/NSUserDefaults.m @@ -1658,7 +1658,8 @@ - (void) setObject: (id)value forKey: (NSString*)defaultName [_persDomains setObject: pd forKey: processName]; RELEASE(pd); } - old = [pd objectForKey: defaultName]; + // Make sure to search all domains and not only the process domain + old = [self objectForKey: defaultName]; if ([pd setObject: value forKey: defaultName]) { [self _changePersistentDomain: processName]; From 4143b53fdb86e998d25d7a90e55317c0b208c436 Mon Sep 17 00:00:00 2001 From: hmelder Date: Thu, 24 Oct 2024 10:30:40 +0200 Subject: [PATCH 38/46] NSUserDefaults: Fetch new value from all domains --- Source/NSUserDefaults.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Source/NSUserDefaults.m b/Source/NSUserDefaults.m index f1c477baf..30dd514e6 100644 --- a/Source/NSUserDefaults.m +++ b/Source/NSUserDefaults.m @@ -1662,8 +1662,14 @@ - (void) setObject: (id)value forKey: (NSString*)defaultName old = [self objectForKey: defaultName]; if ([pd setObject: value forKey: defaultName]) { + id new; + + // New value must be fetched from all domains, as there might be + // a registered default if value is nil, or the value is + // superseded by GSPrimary or NSArgumentDomain + new = [self objectForKey: defaultName]; [self _changePersistentDomain: processName]; - [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue:value]; + [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue:new]; } else { From 95f6740c0d682c450c4dd814f69b7b540bf49422 Mon Sep 17 00:00:00 2001 From: hmelder Date: Thu, 24 Oct 2024 10:31:24 +0200 Subject: [PATCH 39/46] NSKVOInternal: Fixup filename in header --- Source/NSKVOInternal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/NSKVOInternal.h b/Source/NSKVOInternal.h index d767f8a6e..b32c874c0 100644 --- a/Source/NSKVOInternal.h +++ b/Source/NSKVOInternal.h @@ -1,5 +1,5 @@ /** - NSKVOSwizzling.m + NSKVOInternal.h Copyright (C) 2024 Free Software Foundation, Inc. From 38b3f2bcb66c20a60b0c66e501983598195df094 Mon Sep 17 00:00:00 2001 From: hmelder Date: Fri, 25 Oct 2024 11:22:15 +0200 Subject: [PATCH 40/46] NSUserDefaults: Go through search list instead of only one domain in KVO change --- Source/NSUserDefaults.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/NSUserDefaults.m b/Source/NSUserDefaults.m index 30dd514e6..8f6ddc95a 100644 --- a/Source/NSUserDefaults.m +++ b/Source/NSUserDefaults.m @@ -1514,14 +1514,16 @@ - (void) removeObjectForKey: (NSString*)defaultName NS_DURING { GSPersistentDomain *pd = [_persDomains objectForKey: processName]; - id old = [pd objectForKey: defaultName]; + id old = [self objectForKey: defaultName]; if (nil != pd) { if ([pd setObject: nil forKey: defaultName]) { + id new; [self _changePersistentDomain: processName]; - [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue:nil]; + new = [self objectForKey: defaultName]; + [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue: new]; } else { // We always notify observers of a change, even if the value // itself is unchanged. From 2c0d86bd2000e8122891e6bced00e330ac6504d4 Mon Sep 17 00:00:00 2001 From: hmelder Date: Fri, 25 Oct 2024 12:35:29 +0200 Subject: [PATCH 41/46] Making indentation a bit less worse --- Source/NSUserDefaults.m | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Source/NSUserDefaults.m b/Source/NSUserDefaults.m index 8f6ddc95a..0e7f797d2 100644 --- a/Source/NSUserDefaults.m +++ b/Source/NSUserDefaults.m @@ -1518,13 +1518,14 @@ - (void) removeObjectForKey: (NSString*)defaultName if (nil != pd) { - if ([pd setObject: nil forKey: defaultName]) + if ([pd setObject: nil forKey: defaultName]) { - id new; - [self _changePersistentDomain: processName]; - new = [self objectForKey: defaultName]; - [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue: new]; - } else { + id new; + [self _changePersistentDomain: processName]; + new = [self objectForKey: defaultName]; + [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue: new]; + } + else { // We always notify observers of a change, even if the value // itself is unchanged. [[NSNotificationCenter defaultCenter] From ba0ec1dca73cbb6e010fd781a39798b529e0ce50 Mon Sep 17 00:00:00 2001 From: hmelder Date: Fri, 25 Oct 2024 13:46:11 +0200 Subject: [PATCH 42/46] Add NSUserDefaults KVO tests --- Tests/base/NSKVOSupport/userdefaults.m | 177 +++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 Tests/base/NSKVOSupport/userdefaults.m diff --git a/Tests/base/NSKVOSupport/userdefaults.m b/Tests/base/NSKVOSupport/userdefaults.m new file mode 100644 index 000000000..60206f3d6 --- /dev/null +++ b/Tests/base/NSKVOSupport/userdefaults.m @@ -0,0 +1,177 @@ +#import +#import +#import +#import +#import +#import + +#import + +/* NSUserDefaults KeyValueObserving Tests + * + * Behaviour was validated on macOS 15.0.1 (24A348) + */ + +@interface Observer : NSObject +{ +@public + NSInteger called; + NSString *lastKeyPath; + id lastObject; + NSDictionary *lastChange; +} +@end + +@implementation Observer + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + NSLog(@"KVO notification: keyPath: %@ ofObject: %@ change: %@ context: %p", + keyPath, object, change, context); + called++; + ASSIGN(lastKeyPath, keyPath); + ASSIGN(lastObject, object); + ASSIGN(lastChange, change); +} + +- (void)dealloc +{ + RELEASE(lastKeyPath); + RELEASE(lastObject); + RELEASE(lastChange); + [super dealloc]; +} + +@end + +// NSUserDefaults Domain Search List: +// NSArgumentDomain +// Application Domain +// NSGlobalDomain +// NSRegistrationDomain +// +// Terminology: +// - Entry: An entry is a key value pair. +// - Object and Value: both used interchangeably in the NSUserDefaults API to +// describe the value associated with a given key. +// +// Note that -removeObjectForKey: and -setObject:ForKey: emit only a +// KVO notification when the value has actually changed, meaning +// -objectForKey: would return a different value than before. +// +// Example: +// Assume that a key with the same value is registered in both NSArgumentDomain +// and the application domain. If we remove the value with -removeObjectForKey:, +// we set the value for the key in the application domain to nil, but we stil +// have an entry in the NSArgumentDomain. Thus -objectForKey will return the +// same value as before and no change notification is emitted. +int +main(int argc, char *argv[]) +{ + NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; + Observer *obs = [Observer new]; + NSString *key1 = @"key1"; + NSString *value1 = @"value1"; + NSString *key2 = @"key2"; + NSString *value2 = @"value2"; + NSString *value2Alt = @"value2Alt"; + + [defs addObserver:obs + forKeyPath:key1 + options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld + context:NULL]; + [defs addObserver:obs + forKeyPath:key2 + options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld + context:NULL]; + + // Check if we receive KVO notifications when setting default key in the + // standard application domain + [defs setObject:value1 forKey:key1]; + PASS(obs->called == 1, "KVO notification received"); + PASS(obs->lastObject != nil, "object is not nil"); + PASS(obs->lastChange != nil, "change is not nil"); + PASS_EQUAL([obs->lastChange objectForKey:@"kind"], + [NSNumber numberWithInteger:1], "value for 'kind' is 1"); + PASS_EQUAL([obs->lastChange objectForKey:@"old"], nil, + "value for 'old' is nil"); + PASS_EQUAL([obs->lastChange objectForKey:@"new"], value1, + "value for 'new' is 'value1'"); + + [defs removeObjectForKey:key1]; + PASS(obs->called == 2, "KVO notification received"); + PASS(obs->lastObject != nil, "object is not nil"); + PASS(obs->lastChange != nil, "change is not nil"); + PASS_EQUAL([obs->lastChange objectForKey:@"kind"], + [NSNumber numberWithInteger:1], "value for 'kind' is 1"); + PASS_EQUAL([obs->lastChange objectForKey:@"old"], value1, + "value for 'old' is value1"); + PASS_EQUAL([obs->lastChange objectForKey:@"new"], nil, + "value for 'new' is nil"); + + // Test setting two different values for the same key in application domain + // and registration domain. When removing the value in the application domain, + // the value for 'new' in the change dictionary is not nil, but rather the + // value from the registration domain. + [defs setObject:value2 forKey:key2]; + PASS(obs->called == 3, "KVO notification received"); + PASS(obs->lastObject != nil, "object is not nil"); + PASS(obs->lastChange != nil, "change is not nil"); + PASS_EQUAL([obs->lastChange objectForKey:@"kind"], + [NSNumber numberWithInteger:1], "value for 'kind' is 1"); + PASS_EQUAL([obs->lastChange objectForKey:@"old"], nil, + "value for 'old' is nil"); + PASS_EQUAL([obs->lastChange objectForKey:@"new"], value2, + "value for 'new' is 'value2'"); + + // Set default key in registration domain that is _different_ to the key + // registered in the application domain. This will trigger a change + // notification, when the entry is removed from the application domain. + NSDictionary *registrationDict = [NSDictionary dictionaryWithObject:value2Alt + forKey:key2]; + // -registerDefaults: does not emit a KVO notification + [defs registerDefaults:registrationDict]; + + [defs removeObjectForKey:key2]; + PASS(obs->called == 4, "KVO notification received"); + PASS(obs->lastObject != nil, "object is not nil"); + PASS(obs->lastChange != nil, "change is not nil"); + PASS_EQUAL([obs->lastChange objectForKey:@"kind"], + [NSNumber numberWithInteger:1], "value for 'kind' is 1"); + PASS_EQUAL([obs->lastChange objectForKey:@"old"], value2, + "value for 'old' is nil"); + // this must not be null in this case + PASS_EQUAL([obs->lastChange objectForKey:@"new"], value2Alt, + "value for 'new' is 'value2Alt'"); + + // Set default key in registration domain that is _equal_ to the key + // registered in the application domain. This will _not_ trigger a change + // notification, when the entry is removed from the application domain. + registrationDict = [NSDictionary dictionaryWithObject:value1 forKey:key1]; + [defs registerDefaults:registrationDict]; + + [defs setObject:value1 forKey:key1]; + PASS(obs->called == 5, "KVO notification received"); + PASS(obs->lastObject != nil, "object is not nil"); + PASS(obs->lastChange != nil, "change is not nil"); + PASS_EQUAL([obs->lastChange objectForKey:@"kind"], + [NSNumber numberWithInteger:1], "value for 'kind' is 1"); + PASS_EQUAL([obs->lastChange objectForKey:@"old"], nil, + "value for 'old' is nil"); + PASS_EQUAL([obs->lastChange objectForKey:@"new"], value1, + "value for 'new' is 'value1'"); + + // Remove the entry from the application domain. + [defs removeObjectForKey:key1]; + PASS(obs->called == 5, + "KVO notification was not emitted when other domain has the same entry"); + + [defs removeObserver:obs forKeyPath:key1]; + [defs removeObserver:obs forKeyPath:key2]; + + [obs release]; + return 0; +} From 41e9c98ea14c540b68d25a76b924274aafb72609 Mon Sep 17 00:00:00 2001 From: hmelder Date: Fri, 25 Oct 2024 14:54:17 +0200 Subject: [PATCH 43/46] NSKVOSupport: NSUserDefaults test small fixes --- Tests/base/NSKVOSupport/userdefaults.m | 36 ++++++++++---------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/Tests/base/NSKVOSupport/userdefaults.m b/Tests/base/NSKVOSupport/userdefaults.m index 60206f3d6..711c98e0f 100644 --- a/Tests/base/NSKVOSupport/userdefaults.m +++ b/Tests/base/NSKVOSupport/userdefaults.m @@ -1,6 +1,7 @@ #import #import #import +#import #import #import #import @@ -29,8 +30,6 @@ - (void)observeValueForKeyPath:(NSString *)keyPath change:(NSDictionary *)change context:(void *)context { - NSLog(@"KVO notification: keyPath: %@ ofObject: %@ change: %@ context: %p", - keyPath, object, change, context); called++; ASSIGN(lastKeyPath, keyPath); ASSIGN(lastObject, object); @@ -96,8 +95,8 @@ - (void)dealloc PASS(obs->lastChange != nil, "change is not nil"); PASS_EQUAL([obs->lastChange objectForKey:@"kind"], [NSNumber numberWithInteger:1], "value for 'kind' is 1"); - PASS_EQUAL([obs->lastChange objectForKey:@"old"], nil, - "value for 'old' is nil"); + PASS_EQUAL([obs->lastChange objectForKey:@"old"], [NSNull null], + "value for 'old' is [NSNull null]"); PASS_EQUAL([obs->lastChange objectForKey:@"new"], value1, "value for 'new' is 'value1'"); @@ -109,8 +108,8 @@ - (void)dealloc [NSNumber numberWithInteger:1], "value for 'kind' is 1"); PASS_EQUAL([obs->lastChange objectForKey:@"old"], value1, "value for 'old' is value1"); - PASS_EQUAL([obs->lastChange objectForKey:@"new"], nil, - "value for 'new' is nil"); + PASS_EQUAL([obs->lastChange objectForKey:@"new"], [NSNull null], + "value for 'new' is [NSNull null]"); // Test setting two different values for the same key in application domain // and registration domain. When removing the value in the application domain, @@ -122,8 +121,8 @@ - (void)dealloc PASS(obs->lastChange != nil, "change is not nil"); PASS_EQUAL([obs->lastChange objectForKey:@"kind"], [NSNumber numberWithInteger:1], "value for 'kind' is 1"); - PASS_EQUAL([obs->lastChange objectForKey:@"old"], nil, - "value for 'old' is nil"); + PASS_EQUAL([obs->lastChange objectForKey:@"old"], [NSNull null], + "value for 'old' is [NSNull null]"); PASS_EQUAL([obs->lastChange objectForKey:@"new"], value2, "value for 'new' is 'value2'"); @@ -132,7 +131,6 @@ - (void)dealloc // notification, when the entry is removed from the application domain. NSDictionary *registrationDict = [NSDictionary dictionaryWithObject:value2Alt forKey:key2]; - // -registerDefaults: does not emit a KVO notification [defs registerDefaults:registrationDict]; [defs removeObjectForKey:key2]; @@ -142,7 +140,7 @@ - (void)dealloc PASS_EQUAL([obs->lastChange objectForKey:@"kind"], [NSNumber numberWithInteger:1], "value for 'kind' is 1"); PASS_EQUAL([obs->lastChange objectForKey:@"old"], value2, - "value for 'old' is nil"); + "value for 'old' is value2"); // this must not be null in this case PASS_EQUAL([obs->lastChange objectForKey:@"new"], value2Alt, "value for 'new' is 'value2Alt'"); @@ -153,21 +151,15 @@ - (void)dealloc registrationDict = [NSDictionary dictionaryWithObject:value1 forKey:key1]; [defs registerDefaults:registrationDict]; - [defs setObject:value1 forKey:key1]; - PASS(obs->called == 5, "KVO notification received"); - PASS(obs->lastObject != nil, "object is not nil"); - PASS(obs->lastChange != nil, "change is not nil"); - PASS_EQUAL([obs->lastChange objectForKey:@"kind"], - [NSNumber numberWithInteger:1], "value for 'kind' is 1"); - PASS_EQUAL([obs->lastChange objectForKey:@"old"], nil, - "value for 'old' is nil"); - PASS_EQUAL([obs->lastChange objectForKey:@"new"], value1, - "value for 'new' is 'value1'"); + // Does not emit a KVO notification as value is not changed + [defs setObject:value1 forKey:key1]; + PASS(obs->called == 4, + "KVO notification was not emitted as other domain has the same entry"); // Remove the entry from the application domain. [defs removeObjectForKey:key1]; - PASS(obs->called == 5, - "KVO notification was not emitted when other domain has the same entry"); + PASS(obs->called == 4, + "KVO notification was not emitted as other domain has the same entry"); [defs removeObserver:obs forKeyPath:key1]; [defs removeObserver:obs forKeyPath:key2]; From 22239728850cd15e213df521f220f54668634ffd Mon Sep 17 00:00:00 2001 From: hmelder Date: Fri, 25 Oct 2024 15:03:48 +0200 Subject: [PATCH 44/46] Add autoreleasepool --- Tests/base/NSKVOSupport/userdefaults.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/base/NSKVOSupport/userdefaults.m b/Tests/base/NSKVOSupport/userdefaults.m index 711c98e0f..5bcdb168b 100644 --- a/Tests/base/NSKVOSupport/userdefaults.m +++ b/Tests/base/NSKVOSupport/userdefaults.m @@ -3,6 +3,7 @@ #import #import #import +#import #import #import @@ -70,6 +71,7 @@ - (void)dealloc int main(int argc, char *argv[]) { + NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; Observer *obs = [Observer new]; NSString *key1 = @"key1"; @@ -164,6 +166,8 @@ - (void)dealloc [defs removeObserver:obs forKeyPath:key1]; [defs removeObserver:obs forKeyPath:key2]; + [pool drain]; [obs release]; + return 0; } From d3d15296a63fb3802e9da2d872b82dd0693890c3 Mon Sep 17 00:00:00 2001 From: hmelder Date: Fri, 25 Oct 2024 15:05:51 +0200 Subject: [PATCH 45/46] NSUserDefaults: Only emit change notifications if value changed --- Source/NSUserDefaults.m | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Source/NSUserDefaults.m b/Source/NSUserDefaults.m index 0e7f797d2..847885199 100644 --- a/Source/NSUserDefaults.m +++ b/Source/NSUserDefaults.m @@ -1519,12 +1519,17 @@ - (void) removeObjectForKey: (NSString*)defaultName if (nil != pd) { if ([pd setObject: nil forKey: defaultName]) + { + id new; + [self _changePersistentDomain: processName]; + new = [self objectForKey: defaultName]; + // Emit only a KVO notification when the value has actually changed, + // meaning -objectForKey: would return a different value than before. + if ([new hash] != [old hash]) { - id new; - [self _changePersistentDomain: processName]; - new = [self objectForKey: defaultName]; - [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue: new]; + [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue: new]; } + } else { // We always notify observers of a change, even if the value // itself is unchanged. @@ -1672,7 +1677,12 @@ - (void) setObject: (id)value forKey: (NSString*)defaultName // superseded by GSPrimary or NSArgumentDomain new = [self objectForKey: defaultName]; [self _changePersistentDomain: processName]; - [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue:new]; + + // Emit only a KVO notification when the value has actually changed + if ([new hash] != [old hash]) + { + [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue:new]; + } } else { From aef78730ee5c405a66738133800131819cbcd611 Mon Sep 17 00:00:00 2001 From: rfm Date: Thu, 7 Nov 2024 11:14:07 +0000 Subject: [PATCH 46/46] Avoid compiler warnings and tidy some of the whitespace/formatting --- Source/NSKVOInternal.h | 4 +- Source/NSKVOSupport.m | 345 +++++++++++++++++-------------- Source/NSKVOSwizzling.m | 441 +++++++++++++++++++++------------------- Source/NSUserDefaults.m | 172 +++++++++------- 4 files changed, 524 insertions(+), 438 deletions(-) diff --git a/Source/NSKVOInternal.h b/Source/NSKVOInternal.h index b32c874c0..7748f0def 100644 --- a/Source/NSKVOInternal.h +++ b/Source/NSKVOInternal.h @@ -60,8 +60,8 @@ do \ { \ [NSException \ - raise:NSInvalidArgumentException \ - format:@"-[%s %s] is not supported. Key path: %@", \ + raise: NSInvalidArgumentException \ + format: @"-[%s %s] is not supported. Key path: %@", \ object_getClassName(self), sel_getName(_cmd), keyPath]; \ } while (false) diff --git a/Source/NSKVOSupport.m b/Source/NSKVOSupport.m index 7b741548f..9dd31562c 100644 --- a/Source/NSKVOSupport.m +++ b/Source/NSKVOSupport.m @@ -75,11 +75,11 @@ This code is licensed under the MIT License (MIT). @end @implementation _NSKVOKeyObserver -- (instancetype)initWithObject:(id)object - keypathObserver:(_NSKVOKeypathObserver *)keypathObserver - key:(NSString *)key - restOfKeypath:(NSString *)restOfKeypath - affectedObservers:(NSArray *)affectedObservers +- (instancetype)initWithObject: (id)object + keypathObserver: (_NSKVOKeypathObserver *)keypathObserver + key: (NSString *)key + restOfKeypath: (NSString *)restOfKeypath + affectedObservers: (NSArray *)affectedObservers { if (self = [super init]) { @@ -108,7 +108,7 @@ - (BOOL)isRemoved return _isRemoved; } -- (void)setIsRemoved:(BOOL)removed +- (void)setIsRemoved: (BOOL)removed { _isRemoved = removed; } @@ -124,11 +124,11 @@ - (void)setIsRemoved:(BOOL)removed @end @implementation _NSKVOKeypathObserver -- (instancetype)initWithObject:(id)object - observer:(id)observer - keyPath:(NSString *)keypath - options:(NSKeyValueObservingOptions)options - context:(void *)context +- (instancetype) initWithObject: (id)object + observer: (id)observer + keyPath: (NSString *)keypath + options: (NSKeyValueObservingOptions)options + context: (void *)context { if (self = [super init]) { @@ -141,24 +141,24 @@ - (instancetype)initWithObject:(id)object return self; } -- (void)dealloc +- (void) dealloc { [_keypath release]; [_pendingChange release]; [super dealloc]; } -- (id)observer +- (id) observer { return _observer; } -- (BOOL)pushWillChange +- (BOOL) pushWillChange { return atomic_fetch_add(&_changeDepth, 1) == 0; } -- (BOOL)popDidChange +- (BOOL) popDidChange { return atomic_fetch_sub(&_changeDepth, 1) == 1; } @@ -167,7 +167,7 @@ - (BOOL)popDidChange #pragma region Object - level Observation Info @implementation _NSKVOObservationInfo -- (instancetype)init +- (instancetype) init { if (self = [super init]) { @@ -177,7 +177,7 @@ - (instancetype)init return self; } -- (void)dealloc +- (void) dealloc { if (![self isEmpty]) { @@ -207,7 +207,7 @@ - (void)dealloc [super dealloc]; } -- (void)pushDependencyStack +- (void) pushDependencyStack { GS_MUTEX_LOCK(_lock); if (_dependencyDepth == 0) @@ -218,7 +218,7 @@ - (void)pushDependencyStack GS_MUTEX_UNLOCK(_lock); } -- (BOOL)lockDependentKeypath:(NSString *)keypath +- (BOOL) lockDependentKeypath: (NSString *)keypath { GS_MUTEX_LOCK(_lock); if ([_existingDependentKeys containsObject:keypath]) @@ -231,7 +231,7 @@ - (BOOL)lockDependentKeypath:(NSString *)keypath return YES; } -- (void)popDependencyStack +- (void) popDependencyStack { GS_MUTEX_LOCK(_lock); --_dependencyDepth; @@ -243,10 +243,11 @@ - (void)popDependencyStack GS_MUTEX_UNLOCK(_lock); } -- (void)addObserver:(_NSKVOKeyObserver *)observer +- (void) addObserver: (_NSKVOKeyObserver *)observer { NSString *key = observer.key; NSMutableArray *observersForKey = nil; + GS_MUTEX_LOCK(_lock); observersForKey = [_keyObserverMap objectForKey:key]; if (!observersForKey) @@ -258,11 +259,14 @@ - (void)addObserver:(_NSKVOKeyObserver *)observer GS_MUTEX_UNLOCK(_lock); } -- (void)removeObserver:(_NSKVOKeyObserver *)observer +- (void) removeObserver: (_NSKVOKeyObserver *)observer { + NSString *key; + NSMutableArray *observersForKey; + GS_MUTEX_LOCK(_lock); - NSString *key = observer.key; - NSMutableArray *observersForKey = [_keyObserverMap objectForKey:key]; + key = observer.key; + observersForKey = [_keyObserverMap objectForKey:key]; [observersForKey removeObject:observer]; observer.isRemoved = true; if (observersForKey.count == 0) @@ -272,18 +276,22 @@ - (void)removeObserver:(_NSKVOKeyObserver *)observer GS_MUTEX_UNLOCK(_lock); } -- (NSArray *)observersForKey:(NSString *)key +- (NSArray *) observersForKey: (NSString *)key { + NSArray *result; + GS_MUTEX_LOCK(_lock); - NSArray *result = [[[_keyObserverMap objectForKey:key] copy] autorelease]; + result = [[[_keyObserverMap objectForKey:key] copy] autorelease]; GS_MUTEX_UNLOCK(_lock); return result; } -- (bool)isEmpty +- (bool) isEmpty { + BOOL result; + GS_MUTEX_LOCK(_lock); - BOOL result = _keyObserverMap.count == 0; + result = (_keyObserverMap.count == 0); GS_MUTEX_UNLOCK(_lock); return result; } @@ -329,25 +337,27 @@ - (bool)isEmpty NSSet *valueInfluencingKeys = [cls keyPathsForValuesAffectingValueForKey: key]; if (valueInfluencingKeys.count > 0) { - // affectedKeyObservers is the list of observers that must be notified - // of changes. If we have descendants, we have to add ourselves to the - // growing list of affected keys. If not, we must pass it along - // unmodified. (This is a minor optimization: we don't need to signal - // for our own reconstruction - // if we have no subpath observers.) - NSArray *affectedKeyObservers - = (keyObserver.restOfKeypath - ? ([keyObserver.affectedObservers - arrayByAddingObject:keyObserver] - ?: [NSArray arrayWithObject:keyObserver]) - : keyObserver.affectedObservers); + NSArray *affectedKeyObservers; + NSMutableArray *dependentObservers; + + /* affectedKeyObservers is the list of observers that must be notified + * of changes. If we have descendants, we have to add ourselves to the + * growing list of affected keys. If not, we must pass it along + * unmodified. (This is a minor optimization: we don't need to signal + * for our own reconstruction + * if we have no subpath observers.) + */ + affectedKeyObservers = (keyObserver.restOfKeypath + ? ([keyObserver.affectedObservers arrayByAddingObject:keyObserver] + ?: [NSArray arrayWithObject:keyObserver]) + : keyObserver.affectedObservers); [observationInfo pushDependencyStack]; - [observationInfo - lockDependentKeypath:keyObserver.key]; // Don't allow our own key to - // be recreated. + /* Don't allow our own key to be recreated. + */ + [observationInfo lockDependentKeypath:keyObserver.key]; - NSMutableArray *dependentObservers = + dependentObservers = [NSMutableArray arrayWithCapacity:[valueInfluencingKeys count]]; for (NSString *dependentKeypath in valueInfluencingKeys) { @@ -409,28 +419,31 @@ - (bool)isEmpty static void _addKeyObserver(_NSKVOKeyObserver *keyObserver) { - id object = keyObserver.object; + _NSKVOObservationInfo *observationInfo; + id object = keyObserver.object; + _NSKVOEnsureKeyWillNotify(object, keyObserver.key); - _NSKVOObservationInfo *observationInfo + observationInfo = (__bridge _NSKVOObservationInfo *) [object observationInfo] - ?: _createObservationInfoForObject(object); + ?: _createObservationInfoForObject(object); [observationInfo addObserver:keyObserver]; } static _NSKVOKeyObserver * _addKeypathObserver(id object, NSString *keypath, - _NSKVOKeypathObserver *keyPathObserver, - NSArray *affectedObservers) + _NSKVOKeypathObserver *keyPathObserver, NSArray *affectedObservers) { + _NSKVOKeyObserver *keyObserver; + NSString *key; + NSString *restOfKeypath; + if (!object) { return nil; } - NSString *key = nil; - NSString *restOfKeypath; key = _NSKVCSplitKeypath(keypath, &restOfKeypath); - _NSKVOKeyObserver *keyObserver = + keyObserver = [[[_NSKVOKeyObserver alloc] initWithObject:object keypathObserver:keyPathObserver key:key @@ -450,7 +463,7 @@ - (bool)isEmpty #pragma region Observer / Key Deregistration static void _removeNestedObserversAndOptionallyDependents(_NSKVOKeyObserver *keyObserver, - bool dependents) + bool dependents) { if (keyObserver.restOfKeypathObserver) { @@ -496,12 +509,14 @@ - (bool)isEmpty static void _removeKeyObserver(_NSKVOKeyObserver *keyObserver) { + _NSKVOObservationInfo *observationInfo; + if (!keyObserver) { return; } - _NSKVOObservationInfo *observationInfo + observationInfo = (_NSKVOObservationInfo *) [keyObserver.object observationInfo]; [keyObserver retain]; @@ -520,29 +535,31 @@ - (bool)isEmpty static void _removeKeypathObserver(id object, NSString *keypath, id observer, void *context) { - NSString *key = nil; - NSString *restOfKeypath; + NSString *key; + NSString *restOfKeypath; + _NSKVOObservationInfo *observationInfo; + key = _NSKVCSplitKeypath(keypath, &restOfKeypath); - _NSKVOObservationInfo *observationInfo - = (_NSKVOObservationInfo *) [object observationInfo]; + observationInfo = (_NSKVOObservationInfo *) [object observationInfo]; for (_NSKVOKeyObserver *keyObserver in [observationInfo observersForKey:key]) { _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + if (keypathObserver.observer == observer - && keypathObserver.object == object && - [keypathObserver.keypath isEqual:keypath] - && (!context || keypathObserver.context == context)) + && keypathObserver.object == object + && [keypathObserver.keypath isEqual:keypath] + && (!context || keypathObserver.context == context)) { _removeKeyObserver(keyObserver); return; } } - [NSException raise:NSInvalidArgumentException - format:@"Cannot remove observer %@ for keypath \"%@\" from %@ as " - @"it is not a registered observer.", - observer, keypath, object]; + [NSException raise: NSInvalidArgumentException + format: @"Cannot remove observer %@ for keypath \"%@\" from %@" + @" as it is not a registered observer.", + observer, keypath, object]; } #pragma endregion @@ -551,49 +568,51 @@ - (bool)isEmpty @implementation NSObject (NSKeyValueObserving) -- (void)observeValueForKeyPath:(NSString *)keyPath - ofObject:(id)object - change:(NSDictionary *)change - context:(void *)context +- (void) observeValueForKeyPath: (NSString *)keyPath + ofObject: (id)object + change: (NSDictionary *)change + context: (void *)context { - [NSException raise:NSInternalInconsistencyException - format:@"A key-value observation notification fired, but nobody " - @"responded to it: object %@, keypath %@, change %@.", - object, keyPath, change]; + [NSException raise: NSInternalInconsistencyException + format: @"A key-value observation notification fired, but nobody " + @"responded to it: object %@, keypath %@, change %@.", + object, keyPath, change]; } static void *s_kvoObservationInfoAssociationKey; // has no value; pointer used // as an association key. -- (void *)observationInfo +- (void *) observationInfo { return (__bridge void *) objc_getAssociatedObject(self, &s_kvoObservationInfoAssociationKey); } -- (void)setObservationInfo:(void *)observationInfo +- (void) setObservationInfo: (void *)observationInfo { objc_setAssociatedObject(self, &s_kvoObservationInfoAssociationKey, - (__bridge id) observationInfo, - OBJC_ASSOCIATION_RETAIN); + (__bridge id) observationInfo, + OBJC_ASSOCIATION_RETAIN); } -+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key ++ (BOOL) automaticallyNotifiesObserversForKey: (NSString *)key { if ([key length] > 0) { - static const char *const sc_prefix = "automaticallyNotifiesObserversOf"; - static const size_t sc_prefixLength = 32; // strlen(sc_prefix) - const char *rawKey = [key UTF8String]; - size_t keyLength = strlen(rawKey); - size_t bufferSize = sc_prefixLength + keyLength + 1; - char *selectorName = (char *) malloc(bufferSize); + static const char *const sc_prefix = "automaticallyNotifiesObserversOf"; + static const size_t sc_prefixLength = 32; // strlen(sc_prefix) + const char *rawKey = [key UTF8String]; + size_t keyLength = strlen(rawKey); + size_t bufferSize = sc_prefixLength + keyLength + 1; + char *selectorName = (char *) malloc(bufferSize); + SEL sel; + memcpy(selectorName, sc_prefix, sc_prefixLength); selectorName[sc_prefixLength] = toupper(rawKey[0]); memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], keyLength); // copy keyLength characters to include terminating // NULL from rawKey - SEL sel = sel_registerName(selectorName); + sel = sel_registerName(selectorName); free(selectorName); if ([self respondsToSelector:sel]) { @@ -603,10 +622,12 @@ + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key return YES; } -+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key ++ (NSSet *) keyPathsForValuesAffectingValueForKey: (NSString *)key { - static NSSet *emptySet = nil; - static gs_mutex_t lock = GS_MUTEX_INIT_STATIC; + static NSSet *emptySet = nil; + static gs_mutex_t lock = GS_MUTEX_INIT_STATIC; + NSUInteger keyLength; + if (nil == emptySet) { GS_MUTEX_LOCK(lock); @@ -620,7 +641,7 @@ + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key // This function can be a KVO bottleneck, so it will prefer to use c string // manipulation when safe - NSUInteger keyLength = [key length]; + keyLength = [key length]; if (keyLength > 0) { static const char *const sc_prefix = "keyPathsForValuesAffecting"; @@ -644,8 +665,10 @@ + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { // fast path using c string manipulation, will cover most cases, as // most keyPaths are short - char selectorName[sc_bufferSize] - = "keyPathsForValuesAffecting"; // 26 chars + char selectorName[sc_bufferSize]; + + strncpy(selectorName, "keyPathsForValuesAffecting", 26); + selectorName[sc_prefixLength] = toupper(rawKey[0]); // Copy the rest of the key, including the null terminator memcpy(&selectorName[sc_prefixLength + 1], &rawKey[1], rawKeyLength); @@ -678,10 +701,10 @@ + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key return emptySet; } -- (void)addObserver:(id)observer - forKeyPath:(NSString *)keyPath - options:(NSKeyValueObservingOptions)options - context:(void *)context +- (void) addObserver: (id)observer + forKeyPath: (NSString *)keyPath + options: (NSKeyValueObservingOptions)options + context: (void *)context { _NSKVOKeypathObserver *keypathObserver = [[[_NSKVOKeypathObserver alloc] initWithObject:self @@ -712,13 +735,14 @@ - (void)addObserver:(id)observer } } -- (void)removeObserver:(id)observer - forKeyPath:(NSString *)keyPath - context:(void *)context +- (void) removeObserver: (id)observer + forKeyPath: (NSString *)keyPath + context: (void *)context { + _NSKVOObservationInfo *observationInfo; + _removeKeypathObserver(self, keyPath, observer, context); - _NSKVOObservationInfo *observationInfo - = (__bridge _NSKVOObservationInfo *) [self observationInfo]; + observationInfo = (__bridge _NSKVOObservationInfo *) [self observationInfo]; if ([observationInfo isEmpty]) { // TODO: was nullptr prior @@ -726,7 +750,7 @@ - (void)removeObserver:(id)observer } } -- (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath +- (void) removeObserver: (id)observer forKeyPath:(NSString *)keyPath { [self removeObserver:observer forKeyPath:keyPath context:NULL]; } @@ -785,23 +809,28 @@ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath = (__bridge _NSKVOObservationInfo *) [notifyingObject observationInfo]; for (_NSKVOKeyObserver *keyObserver in [observationInfo observersForKey:key]) { + _NSKVOKeypathObserver *keypathObserver; + if (keyObserver.isRemoved) { continue; } // Skip any keypaths that are in the process of changing. - _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + keypathObserver = keyObserver.keypathObserver; if ([keypathObserver pushWillChange]) { + NSKeyValueObservingOptions options; + // Call into the lambda function, which will do the actual set-up for // pendingChanges block(keyObserver); - NSKeyValueObservingOptions options = keypathObserver.options; + options = keypathObserver.options; if (options & NSKeyValueObservingOptionPrior) { NSMutableDictionary *change = keypathObserver.pendingChange; + [change setObject:@(YES) forKey:NSKeyValueChangeNotificationIsPriorKey]; [keypathObserver.observer @@ -828,6 +857,8 @@ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath [observationInfo observersForKey:key]; for (_NSKVOKeyObserver *keyObserver in [observers reverseObjectEnumerator]) { + _NSKVOKeypathObserver *keypathObserver; + if (keyObserver.isRemoved) { continue; @@ -837,18 +868,24 @@ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath _addNestedObserversAndOptionallyDependents(keyObserver, false); // Skip any keypaths that are in the process of changing. - _NSKVOKeypathObserver *keypathObserver = keyObserver.keypathObserver; + keypathObserver = keyObserver.keypathObserver; if ([keypathObserver popDidChange]) { + id observer; + NSString *keypath; + id rootObject; + NSMutableDictionary *change; + void *context; + // Call into lambda, which will do set-up for finalizing changes // dictionary block(keyObserver); - id observer = keypathObserver.observer; - NSString *keypath = keypathObserver.keypath; - id rootObject = keypathObserver.object; - NSMutableDictionary *change = keypathObserver.pendingChange; - void *context = keypathObserver.context; + observer = keypathObserver.observer; + keypath = keypathObserver.keypath; + rootObject = keypathObserver.object; + change = keypathObserver.pendingChange; + context = keypathObserver.context; [observer observeValueForKeyPath:keypath ofObject:rootObject change:change @@ -858,7 +895,7 @@ - (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath } } -- (void)willChangeValueForKey:(NSString *)key +- (void) willChangeValueForKey: (NSString *)key { if ([self observationInfo]) { @@ -884,7 +921,7 @@ - (void)willChangeValueForKey:(NSString *)key } } -- (void)didChangeValueForKey:(NSString *)key +- (void) didChangeValueForKey: (NSString *)key { if ([self observationInfo]) { @@ -906,9 +943,9 @@ - (void)didChangeValueForKey:(NSString *)key } } -- (void)willChange:(NSKeyValueChange)changeKind - valuesAtIndexes:(NSIndexSet *)indexes - forKey:(NSString *)key +- (void) willChange: (NSKeyValueChange)changeKind + valuesAtIndexes: (NSIndexSet *)indexes + forKey: (NSString *)key { __block NSKeyValueChange kind = changeKind; if ([self observationInfo]) @@ -953,9 +990,9 @@ - (void)willChange:(NSKeyValueChange)changeKind } } -- (void)didChange:(NSKeyValueChange)changeKind - valuesAtIndexes:(NSIndexSet *)indexes - forKey:(NSString *)key +- (void) didChange: (NSKeyValueChange)changeKind + valuesAtIndexes: (NSIndexSet *)indexes + forKey: (NSString *)key { if ([self observationInfo]) { @@ -986,9 +1023,9 @@ - (void)didChange:(NSKeyValueChange)changeKind static const NSString *_NSKeyValueChangeOldSetValue = @"_NSKeyValueChangeOldSetValue"; -- (void)willChangeValueForKey:(NSString *)key - withSetMutation:(NSKeyValueSetMutationKind)mutationKind - usingObjects:(NSSet *)objects +- (void)willChangeValueForKey: (NSString *)key + withSetMutation: (NSKeyValueSetMutationKind)mutationKind + usingObjects: (NSSet *)objects { if ([self observationInfo]) { @@ -1046,9 +1083,9 @@ - (void)willChangeValueForKey:(NSString *)key } } -- (void)didChangeValueForKey:(NSString *)key - withSetMutation:(NSKeyValueSetMutationKind)mutationKind - usingObjects:(NSSet *)objects +- (void)didChangeValueForKey: (NSString *)key + withSetMutation: (NSKeyValueSetMutationKind)mutationKind + usingObjects: (NSSet *)objects { if ([self observationInfo]) { @@ -1086,9 +1123,9 @@ - (void)didChangeValueForKey:(NSString *)key @implementation NSObject (NSKeyValueObservingPrivate) -- (void)_notifyObserversOfChangeForKey:(NSString *)key - oldValue:(id)oldValue - newValue:(id)newValue +- (void)_notifyObserversOfChangeForKey: (NSString *)key + oldValue: (id)oldValue + newValue: (id)newValue { if ([self observationInfo]) { @@ -1129,31 +1166,31 @@ - (void)_notifyObserversOfChangeForKey:(NSString *)key @implementation NSArray (NSKeyValueObserving) -- (void)addObserver:(id)observer - forKeyPath:(NSString *)keyPath - options:(NSKeyValueObservingOptions)options - context:(void *)context +- (void)addObserver: (id)observer + forKeyPath: (NSString *)keyPath + options: (NSKeyValueObservingOptions)options + context: (void *)context { NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } -- (void)removeObserver:(id)observer - forKeyPath:(NSString *)keyPath - context:(void *)context +- (void)removeObserver: (id)observer + forKeyPath: (NSString *)keyPath + context: (void *)context { NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } -- (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath +- (void)removeObserver: (id)observer forKeyPath:(NSString *)keyPath { NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } -- (void)addObserver:(id)observer - toObjectsAtIndexes:(NSIndexSet *)indexes - forKeyPath:(NSString *)keyPath - options:(NSKeyValueObservingOptions)options - context:(void *)context +- (void)addObserver: (id)observer + toObjectsAtIndexes: (NSIndexSet *)indexes + forKeyPath: (NSString *)keyPath + options: (NSKeyValueObservingOptions)options + context: (void *)context { [indexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { [[self objectAtIndex:index] addObserver:observer @@ -1163,10 +1200,10 @@ - (void)addObserver:(id)observer }]; } -- (void)removeObserver:(id)observer - fromObjectsAtIndexes:(NSIndexSet *)indexes - forKeyPath:(NSString *)keyPath - context:(void *)context +- (void)removeObserver: (id)observer + fromObjectsAtIndexes: (NSIndexSet *)indexes + forKeyPath: (NSString *)keyPath + context: (void *)context { [indexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { [[self objectAtIndex:index] removeObserver:observer @@ -1175,9 +1212,9 @@ - (void)removeObserver:(id)observer }]; } -- (void)removeObserver:(NSObject *)observer - fromObjectsAtIndexes:(NSIndexSet *)indexes - forKeyPath:(NSString *)keyPath +- (void)removeObserver: (NSObject *)observer + fromObjectsAtIndexes: (NSIndexSet *)indexes + forKeyPath: (NSString *)keyPath { [self removeObserver:observer fromObjectsAtIndexes:indexes @@ -1194,22 +1231,22 @@ - (void)removeObserver:(NSObject *)observer @implementation NSSet (NSKeyValueObserving) -- (void)addObserver:(id)observer - forKeyPath:(NSString *)keyPath - options:(NSKeyValueObservingOptions)options - context:(void *)context +- (void)addObserver: (id)observer + forKeyPath: (NSString *)keyPath + options: (NSKeyValueObservingOptions)options + context: (void *)context { NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } -- (void)removeObserver:(id)observer - forKeyPath:(NSString *)keyPath - context:(void *)context +- (void)removeObserver: (id)observer + forKeyPath: (NSString *)keyPath + context: (void *)context { NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } -- (void)removeObserver:(id)observer forKeyPath:(NSString *)keyPath +- (void)removeObserver: (id)observer forKeyPath:(NSString *)keyPath { NS_COLLECTION_THROW_ILLEGAL_KVO(keyPath); } diff --git a/Source/NSKVOSwizzling.m b/Source/NSKVOSwizzling.m index 9e62bc7f1..5b9965baa 100644 --- a/Source/NSKVOSwizzling.m +++ b/Source/NSKVOSwizzling.m @@ -111,12 +111,11 @@ the KVO machinery (if implemented using manual subclassing) would delete all */ object_addMethod_np(object, @selector(setObject:forKey:), - (IMP) (NSKVO$setObject$forKey$), "v@:@@"); + (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@:"); + (IMP)(NSKVO$removeObjectForKey$), "v@:@"); + object_addMethod_np(object, @selector(_isKVOAware), + (IMP)(NSKVO$nilIMP), "v@:"); } #pragma region Method Implementations @@ -144,12 +143,12 @@ the KVO machinery (if implemented using manual subclassing) would delete all if (!selMappings) { selMappings = [NSMapTable - mapTableWithKeyOptions:NSPointerFunctionsOpaqueMemory - | NSPointerFunctionsOpaquePersonality - valueOptions:NSPointerFunctionsStrongMemory | - NSPointerFunctionsObjectPersonality]; + mapTableWithKeyOptions: NSPointerFunctionsOpaqueMemory + | NSPointerFunctionsOpaquePersonality + valueOptions: NSPointerFunctionsStrongMemory + | NSPointerFunctionsObjectPersonality]; objc_setAssociatedObject(cls, &s_selMapKey, (id) selMappings, - OBJC_ASSOCIATION_RETAIN); + OBJC_ASSOCIATION_RETAIN); } return selMappings; } @@ -159,21 +158,22 @@ the KVO machinery (if implemented using manual subclassing) would delete all _keyForSelector(id object, SEL selector) { return (NSString *) NSMapGet(_selectorMappingsForObject(object), - sel_getName(selector)); + 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); + /* 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 @@ -181,17 +181,19 @@ the KVO machinery (if implemented using manual subclassing) would delete all { 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]); + /* [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_list ap; + va_start(ap, _cmd); for (uintptr_t i = 0; i < nStackArgs; ++i) { @@ -199,40 +201,40 @@ the KVO machinery (if implemented using manual subclassing) would delete all } 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 willChangeValueForKey: key]; + { + struct objc_super super = {self, ABI_SUPER(self)}; + IMP imp = (id(*)(id, SEL, ...)) objc_msg_lookup_super(&super, _cmd); - [self didChangeValueForKey:key]; + // 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); } @@ -248,55 +250,63 @@ the KVO machinery (if implemented using manual subclassing) would delete all static void NSKVONotifying$insertObject$inXxxAtIndex$(id self, SEL _cmd, id object, - NSUInteger index) + NSUInteger index) { NSString *key = _keyForSelector(self, _cmd); - NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:index]; - - [self willChange:NSKeyValueChangeInsertion - valuesAtIndexes:indexes - forKey:key]; + NSIndexSet *indexes = [NSIndexSet indexSetWithIndex: index]; - 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]; + [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) + 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]; + [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); + NSIndexSet *indexes = [NSIndexSet indexSetWithIndex: index]; - [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:key]; + [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 @@ -304,116 +314,127 @@ the KVO machinery (if implemented using manual subclassing) would delete all { 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]; + [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) + NSUInteger index, id object) { NSString *key = _keyForSelector(self, _cmd); - NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:index]; + 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]; + [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) + 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]; + [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]; \ + [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]; \ + } \ + [self didChangeValueForKey: key withSetMutation: Kind usingObjects: set]; \ } #define GENERATE_NSKVOSetDispatchIndividual_IMPL(Kind) \ static inline void _NSKVOSetDispatchIndividual_##Kind(id self, SEL _cmd, \ - id obj) \ + id obj) \ { \ - NSSet *set = [NSSet setWithObject:obj]; \ + NSSet *set = [NSSet setWithObject: obj]; \ NSString *key = _keyForSelector(self, _cmd); \ - [self willChangeValueForKey:key withSetMutation:Kind usingObjects:set]; \ + [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]; \ + } \ + [self didChangeValueForKey: key withSetMutation: Kind usingObjects: set]; \ } GENERATE_NSKVOSetDispatchIndividual_IMPL(NSKeyValueUnionSetMutation); GENERATE_NSKVOSetDispatchIndividual_IMPL(NSKeyValueMinusSetMutation); -GENERATE_NSKVOSetDispatchIndividual_IMPL(NSKeyValueIntersectSetMutation); +//GENERATE_NSKVOSetDispatchIndividual_IMPL(NSKeyValueIntersectSetMutation); GENERATE_NSKVOSetDispatch_IMPL(NSKeyValueUnionSetMutation); GENERATE_NSKVOSetDispatch_IMPL(NSKeyValueMinusSetMutation); GENERATE_NSKVOSetDispatch_IMPL(NSKeyValueIntersectSetMutation); -// - (void)setObject:(id)object forKey:(NSString*)key +// - (void)setObject: (id)object forKey: (NSString*)key static void NSKVO$setObject$forKey$(id self, SEL _cmd, id object, NSString *key) { - [self willChangeValueForKey: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); + setObjectForKeyIMP imp; + + imp = (void (*)(id, SEL, id, NSString *)) objc_msg_lookup_super(&super, _cmd); imp(self, _cmd, object, key); - [self didChangeValueForKey:key]; +} + [self didChangeValueForKey: key]; } -// - (void)removeObjectForKey:(NSString*)key +// - (void)removeObjectForKey: (NSString*)key static void NSKVO$removeObjectForKey$(id self, SEL _cmd, NSString *key) { - [self willChangeValueForKey:key]; + [self willChangeValueForKey: key]; +{ struct objc_super super = {self, ABI_SUPER(self)}; - removeObjectForKeyIMP imp - = (void (*)(id, SEL, NSString *)) objc_msg_lookup_super(&super, _cmd); + removeObjectForKeyIMP imp; + + imp = (void (*)(id, SEL, NSString *)) objc_msg_lookup_super(&super, _cmd); imp(self, _cmd, key); - [self didChangeValueForKey:key]; +} + [self didChangeValueForKey: key]; } #pragma endregion @@ -424,9 +445,9 @@ static void funcName(id self, SEL _cmd, type val) \ NSString *key = _keyForSelector(self, _cmd); \ void (*imp)(id, SEL, type) \ = (void (*)(id, SEL, type)) objc_msg_lookup_super(&super, _cmd); \ - [self willChangeValueForKey:key]; \ + [self willChangeValueForKey: key]; \ imp(self, _cmd, val); \ - [self didChangeValueForKey:key]; \ + [self didChangeValueForKey: key]; \ } GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplDouble, double); @@ -443,9 +464,9 @@ static void funcName(id self, SEL _cmd, type val) \ GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplUnsignedShort, unsigned short); GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplUnsignedLong, unsigned long); GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplUnsignedLongLong, - unsigned long long); + unsigned long long); -GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplBool, bool); +// GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplBool, bool); GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplObject, id); GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplPointer, void *); @@ -473,7 +494,7 @@ static void funcName(id self, SEL _cmd, type val) \ buf[3 + len] = ':'; buf[3 + len + 1] = '\0'; sel = sel_getUid(buf); - if ([self respondsToSelector:sel]) + if ([self respondsToSelector: sel]) { return sel; } @@ -485,19 +506,23 @@ static void funcName(id self, SEL _cmd, type val) \ 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); + 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; } - NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:types]; - - const char *valueType = [sig getArgumentTypeAtIndex:2]; - IMP newImpl = NULL; + sig = [NSMethodSignature signatureWithObjCTypes: types]; + valueType = [sig getArgumentTypeAtIndex: 2]; switch (valueType[0]) { @@ -507,19 +532,19 @@ static void funcName(id self, SEL _cmd, type val) \ 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)]; + [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]; + [NSException raise: NSInvalidArgumentException + format: @"Class %s is not KVO compliant for key %@.", + class_getName(object_getClass(object)), key]; return; } @@ -530,15 +555,15 @@ static void funcName(id self, SEL _cmd, type val) \ static void replaceAndAssociateWithKey(id object, SEL sel, NSString *key, IMP imp) { - if ([object respondsToSelector:sel]) + 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)); + @"for %s on class %s; perhaps it is a forward?", + selName, object_getClassName(object)); return; } @@ -550,11 +575,13 @@ static void funcName(id self, SEL _cmd, type val) \ static SEL formatSelector(NSString *format, ...) { - va_list ap; + NSString *s; + SEL sel; + va_list ap; + va_start(ap, format); - SEL sel - = NSSelectorFromString([[[NSString alloc] initWithFormat:format - arguments:ap] autorelease]); + s = [[NSString alloc] initWithFormat: format arguments: ap]; + sel = NSSelectorFromString([s autorelease]); va_end(ap); return sel; } @@ -562,73 +589,75 @@ static void funcName(id self, SEL _cmd, type val) \ // invariant: rawKey has already been capitalized static inline void _NSKVOEnsureOrderedCollectionWillNotify(id object, NSString *key, - const char *rawKey) + const char *rawKey) { SEL insertOne = formatSelector(@"insertObject:in%sAtIndex:", rawKey); SEL insertMany = formatSelector(@"insert%s:atIndexes:", rawKey); - if ([object respondsToSelector:insertOne] || - [object respondsToSelector:insertMany]) + + if ([object respondsToSelector: insertOne] + || [object respondsToSelector: insertMany]) { replaceAndAssociateWithKey(object, insertOne, key, - (IMP) - NSKVONotifying$insertObject$inXxxAtIndex$); + (IMP)NSKVONotifying$insertObject$inXxxAtIndex$); replaceAndAssociateWithKey(object, insertMany, key, - (IMP) NSKVONotifying$insertXxx$atIndexes$); + (IMP)NSKVONotifying$insertXxx$atIndexes$); replaceAndAssociateWithKey( object, formatSelector(@"removeObjectFrom%sAtIndex:", rawKey), key, - (IMP) NSKVONotifying$removeObjectFromXxxAtIndex$); + (IMP)NSKVONotifying$removeObjectFromXxxAtIndex$); replaceAndAssociateWithKey(object, - formatSelector(@"remove%sAtIndexes:", rawKey), - key, (IMP) NSKVONotifying$removeXxxAtIndexes$); + formatSelector(@"remove%sAtIndexes:", rawKey), + key, (IMP)NSKVONotifying$removeXxxAtIndexes$); replaceAndAssociateWithKey( object, formatSelector(@"replaceObjectIn%sAtIndex:withObject:", rawKey), - key, (IMP) NSKVONotifying$replaceObjectInXxxAtIndex$withObject$); + key, (IMP)NSKVONotifying$replaceObjectInXxxAtIndex$withObject$); replaceAndAssociateWithKey( object, formatSelector(@"replace%sAtIndexes:with%s:", rawKey, rawKey), - key, (IMP) NSKVONotifying$replaceXxxAtIndexes$withXxx$); + key, (IMP)NSKVONotifying$replaceXxxAtIndexes$withXxx$); } } // invariant: rawKey has already been capitalized static inline void _NSKVOEnsureUnorderedCollectionWillNotify(id object, NSString *key, - const char *rawKey) + 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])) + + if (([object respondsToSelector: addOne] + || [object respondsToSelector: addMany]) + && ([object respondsToSelector: removeOne] + || [object respondsToSelector: removeMany])) { replaceAndAssociateWithKey( object, addOne, key, - (IMP) _NSKVOSetDispatchIndividual_NSKeyValueUnionSetMutation); + (IMP)_NSKVOSetDispatchIndividual_NSKeyValueUnionSetMutation); replaceAndAssociateWithKey( object, addMany, key, - (IMP) _NSKVOSetDispatch_NSKeyValueUnionSetMutation); + (IMP)_NSKVOSetDispatch_NSKeyValueUnionSetMutation); replaceAndAssociateWithKey( object, removeOne, key, - (IMP) _NSKVOSetDispatchIndividual_NSKeyValueMinusSetMutation); + (IMP)_NSKVOSetDispatchIndividual_NSKeyValueMinusSetMutation); replaceAndAssociateWithKey( object, removeMany, key, - (IMP) _NSKVOSetDispatch_NSKeyValueMinusSetMutation); + (IMP)_NSKVOSetDispatch_NSKeyValueMinusSetMutation); replaceAndAssociateWithKey( object, formatSelector(@"intersect%s:", rawKey), key, - (IMP) _NSKVOSetDispatch_NSKeyValueIntersectSetMutation); + (IMP)_NSKVOSetDispatch_NSKeyValueIntersectSetMutation); } } char * mutableBufferFromString(NSString *string) { - NSUInteger lengthInBytes = [string length] + 1; - char *rawKey = (char *) malloc(lengthInBytes); - [string getCString:rawKey - maxLength:lengthInBytes - encoding:NSASCIIStringEncoding]; + NSUInteger lengthInBytes = [string length] + 1; + char *rawKey = (char *) malloc(lengthInBytes); + + [string getCString: rawKey + maxLength: lengthInBytes + encoding: NSASCIIStringEncoding]; return rawKey; } @@ -636,6 +665,8 @@ static void funcName(id self, SEL _cmd, type val) \ 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)) @@ -644,12 +675,12 @@ static void funcName(id self, SEL _cmd, type val) \ } // A class is allowed to decline automatic swizzling for any/all of its keys. - if (![[object class] automaticallyNotifiesObserversForKey:key]) + if (![[object class] automaticallyNotifiesObserversForKey: key]) { return; } - char *rawKey = mutableBufferFromString(key); + rawKey = mutableBufferFromString(key); rawKey[0] = toupper(rawKey[0]); @synchronized(object) diff --git a/Source/NSUserDefaults.m b/Source/NSUserDefaults.m index 847885199..737030780 100644 --- a/Source/NSUserDefaults.m +++ b/Source/NSUserDefaults.m @@ -362,15 +362,15 @@ - (BOOL) synchronize; NSMutableArray *names = [NSMutableArray arrayWithCapacity: 10]; #if defined(_WIN32) - NSEnumerator *enumerator; - NSArray *languages; - NSString *locale; - BOOL ret; - + NSEnumerator *enumerator; + NSArray *languages; + NSString *locale; + BOOL ret; unsigned long numberOfLanguages = 0; unsigned long length = 7; unsigned long factor = sizeof(wchar_t); - wchar_t *buffer = malloc(length * factor); + wchar_t *buffer = malloc(length * factor); + if (!buffer) { return names; @@ -380,56 +380,61 @@ - (BOOL) synchronize; * two-letter language code, and CC is the two-letter country code. */ ret = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages, - buffer, &length); + buffer, &length); if (!ret) { length = 0; - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER && - GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages, - NULL, &length)) { - wchar_t *oldBuffer = buffer; - buffer = realloc(buffer, length * factor); - if (!buffer) - { - free(oldBuffer); - return names; - } + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER + && GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages, + NULL, &length)) + { + wchar_t *oldBuffer = buffer; - ret = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages, buffer, &length); - if (!ret) - { - free(buffer); - return names; - } - } + buffer = realloc(buffer, length * factor); + if (!buffer) + { + free(oldBuffer); + return names; + } + + ret = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, + &numberOfLanguages, buffer, &length); + if (!ret) + { + free(buffer); + return names; + } + } } - languages = [NSString arrayFromWCharList:buffer length:length]; + languages = [NSString arrayFromWCharList: buffer length: length]; enumerator = [languages objectEnumerator]; free(buffer); - while (nil != (locale = [enumerator nextObject])) - { + while (nil != (locale = [enumerator nextObject])) + { /* Replace "-" Separator with "_" */ - locale = [locale stringByReplacingOccurrencesOfString:@"-" withString:@"_"]; - [names addObjectsFromArray: GSLanguagesFromLocale(locale)]; - } + locale = [locale stringByReplacingOccurrencesOfString: @"-" + withString: @"_"]; + [names addObjectsFromArray: GSLanguagesFromLocale(locale)]; + } #elif defined(__ANDROID__) - // When running on Android, the process must be correctly initialized - // with GSInitializeProcessAndroid (See NSProcessInfo). - // - // If the minimum API level is 24 or higher, the user-prefered locales - // are retrieved from the Android system and passed as GSAndroidLocaleList - // process argument - NSArray *args = [[NSProcessInfo processInfo] arguments]; - NSEnumerator *enumerator = [args objectEnumerator]; - NSString *key = nil; - NSString *localeList = nil; + /* When running on Android, the process must be correctly initialized + * with GSInitializeProcessAndroid (See NSProcessInfo). + * + * If the minimum API level is 24 or higher, the user-prefered locales + * are retrieved from the Android system and passed as GSAndroidLocaleList + * process argument + */ + NSArray *args = [[NSProcessInfo processInfo] arguments]; + NSEnumerator *enumerator = [args objectEnumerator]; + NSString *key = nil; + NSString *localeList = nil; [enumerator nextObject]; // Skip process name. while (nil != (key = [enumerator nextObject])) { - if ([key isEqualToString:@"-GSAndroidLocaleList"]) + if ([key isEqualToString: @"-GSAndroidLocaleList"]) { localeList = [enumerator nextObject]; break; @@ -439,8 +444,8 @@ - (BOOL) synchronize; // The locale list is a comma-separated list of locales of form ll-CC if (localeList != nil) { - NSString *locale; - NSArray *locales = [localeList componentsSeparatedByString: @","]; + NSString *locale; + NSArray *locales = [localeList componentsSeparatedByString: @","]; enumerator = [locales objectEnumerator]; while (nil != (locale = [enumerator nextObject])) @@ -449,16 +454,19 @@ - (BOOL) synchronize; } } #else - // Add the languages listed in the LANGUAGE environment variable - // (a non-POSIX GNU extension) + /* Add the languages listed in the LANGUAGE environment variable + * (a non-POSIX GNU extension) + */ { - NSString *env = [[[NSProcessInfo processInfo] environment] - objectForKey: @"LANGUAGE"]; + NSString *env; + + env = [[[NSProcessInfo processInfo] environment] objectForKey: @"LANGUAGE"]; if (env != nil && [env length] > 0) { - NSArray *array = [env componentsSeparatedByString: @":"]; - NSEnumerator *enumerator = [array objectEnumerator]; - NSString *locale; + NSArray *array = [env componentsSeparatedByString: @":"]; + NSEnumerator *enumerator = [array objectEnumerator]; + NSString *locale; + while (nil != (locale = [enumerator nextObject])) { [names addObjectsFromArray: GSLanguagesFromLocale(locale)]; @@ -666,8 +674,8 @@ + (void) atExit * change the value of a key (the value is equal to the old value). * * https://developer.apple.com/documentation/foundation/nsuserdefaults#2926902 - * "You can use key-value observing to be notified of any updates to a particular - * default value. You can also register as an observer for + * "You can use key-value observing to be notified of any updates to + * a particular default value. You can also register as an observer for * NSUserDefaultsDidChangeNotification on the defaultCenter notification center * in order to be notified of all updates to a local defaults database." */ @@ -1514,30 +1522,36 @@ - (void) removeObjectForKey: (NSString*)defaultName NS_DURING { GSPersistentDomain *pd = [_persDomains objectForKey: processName]; - id old = [self objectForKey: defaultName]; + id old = [self objectForKey: defaultName]; if (nil != pd) { - if ([pd setObject: nil forKey: defaultName]) - { - id new; - [self _changePersistentDomain: processName]; - new = [self objectForKey: defaultName]; - // Emit only a KVO notification when the value has actually changed, - // meaning -objectForKey: would return a different value than before. - if ([new hash] != [old hash]) + if ([pd setObject: nil forKey: defaultName]) { - [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue: new]; + id new; + [self _changePersistentDomain: processName]; + new = [self objectForKey: defaultName]; + /* Emit only a KVO notification when the value has actually + * changed, meaning -objectForKey: would return a different + * value than before. + */ + if ([new hash] != [old hash]) + { + [self _notifyObserversOfChangeForKey: defaultName + oldValue: old + newValue: new]; + } } - } - else { - // We always notify observers of a change, even if the value - // itself is unchanged. - [[NSNotificationCenter defaultCenter] - postNotificationName: NSUserDefaultsDidChangeNotification - object: self]; + else + { + /* We always notify observers of a change, even if the value + * itself is unchanged. + */ + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUserDefaultsDidChangeNotification + object: self]; - } + } } [_lock unlock]; } @@ -1672,25 +1686,29 @@ - (void) setObject: (id)value forKey: (NSString*)defaultName { id new; - // New value must be fetched from all domains, as there might be - // a registered default if value is nil, or the value is - // superseded by GSPrimary or NSArgumentDomain + /* New value must be fetched from all domains, as there might be + * a registered default if value is nil, or the value is + * superseded by GSPrimary or NSArgumentDomain + */ new = [self objectForKey: defaultName]; [self _changePersistentDomain: processName]; // Emit only a KVO notification when the value has actually changed if ([new hash] != [old hash]) { - [self _notifyObserversOfChangeForKey: defaultName oldValue:old newValue:new]; + [self _notifyObserversOfChangeForKey: defaultName + oldValue: old + newValue: new]; } } else { - // We always notify observers of a change, even if the value - // itself is unchanged. + /* We always notify observers of a change, even if the value + * itself is unchanged. + */ [[NSNotificationCenter defaultCenter] postNotificationName: NSUserDefaultsDidChangeNotification - object: self]; + object: self]; } [_lock unlock]; }