Index: src/runtime/Makefile ================================================================== --- src/runtime/Makefile +++ src/runtime/Makefile @@ -9,10 +9,11 @@ LIB_MAJOR = ${OBJFWRT_LIB_MAJOR} LIB_MINOR = ${OBJFWRT_LIB_MINOR} LIB_PATCH = ${OBJFWRT_LIB_PATCH} SRCS = arc.m \ + association.m \ autorelease.m \ category.m \ class.m \ dtable.m \ exception.m \ Index: src/runtime/ObjFWRT.h ================================================================== --- src/runtime/ObjFWRT.h +++ src/runtime/ObjFWRT.h @@ -171,10 +171,26 @@ #else Class _Nonnull class; #endif }; +/** + * @brief A policy for object association, see @ref objc_setAssociatedObject. + */ +typedef enum objc_associationPolicy { + /** @brief Associate the object like an assigned property. */ + OBJC_ASSOCIATION_ASSIGN = 0, + /** @brief Associate the object like a retained, nonatomic property. */ + OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, + /** @brief Associate the object like a retained property. */ + OBJC_ASSOCIATION_RETAIN = OBJC_ASSOCIATION_RETAIN_NONATOMIC | 0x300, + /** @brief Associate the object like a copied, nonatomic property. */ + OBJC_ASSOCIATION_COPY_NONATOMIC = 3, + /** @brief Associate the object like a copied property. */ + OBJC_ASSOCIATION_COPY = OBJC_ASSOCIATION_COPY_NONATOMIC | 0x300 +} objc_associationPolicy; + #ifdef __cplusplus extern "C" { #endif /** @@ -644,10 +660,41 @@ * @param value The value the tagged pointer should have * @return A tagged pointer, or `nil` if it could not be created */ extern id _Nullable objc_createTaggedPointer(int class_, uintptr_t value); +/** + * @brief Sets an associated object on the specified object for the specified + * key. + * + * @param object The object on which to set an associated object + * @param key A unique pointer to use as the key for the association + * @param value The object to associate with the specified object + * @param policy The association policy, see @ref objc_associationPolicy + */ +extern void objc_setAssociatedObject(id _Nonnull object, + const void *_Nonnull key, id _Nullable value, + objc_associationPolicy policy); + +/** + * @brief Returns the associated object on the specified object for the + * specified key. + * + * @param object The object on which to get the associated object + * @param key The key of the association + * @return The associated object on the specified object for the specified key + */ +extern id _Nullable objc_getAssociatedObject(id _Nonnull object, + const void *_Nonnull key); + +/** + * @brief Removes all associated objects for the specified object. + * + * @param object The object on which to remove all associated objects + */ +extern void objc_removeAssociatedObjects(id _Nonnull object); + /* * Used by the compiler, but can also be called manually. * * These declarations are also required to prevent Clang's implicit * declarations which include __declspec(dllimport) on Windows. ADDED src/runtime/association.m Index: src/runtime/association.m ================================================================== --- src/runtime/association.m +++ src/runtime/association.m @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "ObjFWRT.h" +#import "private.h" + +#ifdef OF_HAVE_THREADS +# import "OFPlainMutex.h" +# define numSpinlocks 8 /* needs to be a power of 2 */ +static OFSpinlock spinlocks[numSpinlocks]; + +static OF_INLINE size_t +spinlockSlot(id object) +{ + return ((size_t)((uintptr_t)object >> 4) & (numSpinlocks - 1)); +} +#endif + +struct Association { + id object; + objc_associationPolicy policy; +}; + +static struct objc_hashtable *hashtable; + +static uint32_t +hash(const void *object) +{ + return (uint32_t)(uintptr_t)object; +} + +static bool +equal(const void *object1, const void *object2) +{ + return (object1 == object2); +} + +OF_CONSTRUCTOR() +{ + hashtable = objc_hashtable_new(hash, equal, 2); + +#ifdef OF_HAVE_THREADS + for (size_t i = 0; i < numSpinlocks; i++) + if (OFSpinlockNew(&spinlocks[i]) != 0) + OBJC_ERROR("Failed to create spinlocks!"); +#endif +} + +void +objc_setAssociatedObject(id object, const void *key, id value, + objc_associationPolicy policy) +{ +#ifdef OF_HAVE_THREADS + size_t slot; +#endif + + switch (policy) { + case OBJC_ASSOCIATION_ASSIGN: + break; + case OBJC_ASSOCIATION_RETAIN: + case OBJC_ASSOCIATION_RETAIN_NONATOMIC: + value = [value retain]; + break; + case OBJC_ASSOCIATION_COPY: + case OBJC_ASSOCIATION_COPY_NONATOMIC: + value = [value copy]; + break; + default: + /* Don't know what to do, so do nothing. */ + return; + } + +#ifdef OF_HAVE_THREADS + slot = spinlockSlot(object); + + if (OFSpinlockLock(&spinlocks[slot]) != 0) + OBJC_ERROR("Failed to lock spinlock!"); + + @try { +#endif + struct objc_hashtable *objectHashtable; + struct Association *association; + + objectHashtable = objc_hashtable_get(hashtable, object); + if (objectHashtable == NULL) { + objectHashtable = objc_hashtable_new(hash, equal, 2); + objc_hashtable_set(hashtable, object, objectHashtable); + } + + association = objc_hashtable_get(objectHashtable, key); + if (association != NULL) { + switch (association->policy) { + case OBJC_ASSOCIATION_RETAIN: + case OBJC_ASSOCIATION_RETAIN_NONATOMIC: + case OBJC_ASSOCIATION_COPY: + case OBJC_ASSOCIATION_COPY_NONATOMIC: + [association->object release]; + break; + default: + break; + } + } else { + association = malloc(sizeof(*association)); + if (association == NULL) + OBJC_ERROR("Failed to allocate association!"); + + objc_hashtable_set(objectHashtable, key, association); + } + + association->policy = policy; + association->object = value; +#ifdef OF_HAVE_THREADS + } @finally { + if (OFSpinlockUnlock(&spinlocks[slot]) != 0) + OBJC_ERROR("Failed to unlock spinlock!"); + } +#endif +} + +id +objc_getAssociatedObject(id object, const void *key) +{ +#ifdef OF_HAVE_THREADS + size_t slot = spinlockSlot(object); + + if (OFSpinlockLock(&spinlocks[slot]) != 0) + OBJC_ERROR("Failed to lock spinlock!"); + + @try { +#endif + struct objc_hashtable *objectHashtable; + struct Association *association; + + objectHashtable = objc_hashtable_get(hashtable, object); + if (objectHashtable == NULL) + return nil; + + association = objc_hashtable_get(objectHashtable, key); + if (association == NULL) + return nil; + + switch (association->policy) { + case OBJC_ASSOCIATION_RETAIN: + case OBJC_ASSOCIATION_COPY: + return [[association->object retain] autorelease]; + default: + return association->object; + } +#ifdef OF_HAVE_THREADS + } @finally { + if (OFSpinlockUnlock(&spinlocks[slot]) != 0) + OBJC_ERROR("Failed to unlock spinlock!"); + } +#endif +} + +void +objc_removeAssociatedObjects(id object) +{ +#ifdef OF_HAVE_THREADS + size_t slot = spinlockSlot(object); + + if (OFSpinlockLock(&spinlocks[slot]) != 0) + OBJC_ERROR("Failed to lock spinlock!"); + + @try { +#endif + struct objc_hashtable *objectHashtable; + + objectHashtable = objc_hashtable_get(hashtable, object); + if (objectHashtable == NULL) + return; + + for (uint32_t i = 0; i < objectHashtable->size; i++) { + struct Association *association; + + if (objectHashtable->data[i] == NULL || + objectHashtable->data[i] == &objc_deletedBucket) + continue; + + association = (struct Association *) + objectHashtable->data[i]->object; + + switch (association->policy) { + case OBJC_ASSOCIATION_RETAIN: + case OBJC_ASSOCIATION_RETAIN_NONATOMIC: + case OBJC_ASSOCIATION_COPY: + case OBJC_ASSOCIATION_COPY_NONATOMIC: + [association->object release]; + break; + default: + break; + } + + free(association); + } + + objc_hashtable_delete(hashtable, object); +#ifdef OF_HAVE_THREADS + } @finally { + if (OFSpinlockUnlock(&spinlocks[slot]) != 0) + OBJC_ERROR("Failed to unlock spinlock!"); + } +#endif +} Index: src/runtime/class.m ================================================================== --- src/runtime/class.m +++ src/runtime/class.m @@ -601,11 +601,12 @@ if (j >= count) { objc_globalMutex_unlock(); return j; } - if (classes->data[i] == NULL) + if (classes->data[i] == NULL || + classes->data[i] == &objc_deletedBucket) continue; if (strcmp(classes->data[i]->key, "Protocol") == 0) continue; Index: src/runtime/property.m ================================================================== --- src/runtime/property.m +++ src/runtime/property.m @@ -28,13 +28,11 @@ static OF_INLINE size_t spinlockSlot(const void *ptr) { return ((size_t)((uintptr_t)ptr >> 4) & (numSpinlocks - 1)); } -#endif -#ifdef OF_HAVE_THREADS OF_CONSTRUCTOR() { for (size_t i = 0; i < numSpinlocks; i++) if (OFSpinlockNew(&spinlocks[i]) != 0) OBJC_ERROR("Failed to create spinlocks!"); Index: tests/RuntimeTests.m ================================================================== --- tests/RuntimeTests.m +++ tests/RuntimeTests.m @@ -16,10 +16,11 @@ #include "config.h" #import "TestsAppDelegate.h" static OFString *const module = @"Runtime"; +static void *testKey = &testKey; @interface OFObject (SuperTest) - (id)superTest; @end @@ -86,10 +87,25 @@ test.bar = string; TEST(@"retain, atomic properties", test.bar == string && string.retainCount == 3) + TEST(@"Associated objects", + R(objc_setAssociatedObject(self, testKey, test, + OBJC_ASSOCIATION_ASSIGN)) && test.retainCount == 2 && + R(objc_setAssociatedObject(self, testKey, test, + OBJC_ASSOCIATION_RETAIN)) && test.retainCount == 3 && + objc_getAssociatedObject(self, testKey) == test && + test.retainCount == 4 && + R(objc_setAssociatedObject(self, testKey, test, + OBJC_ASSOCIATION_ASSIGN)) && test.retainCount == 3 && + R(objc_setAssociatedObject(self, testKey, test, + OBJC_ASSOCIATION_RETAIN_NONATOMIC)) && test.retainCount == 4 && + objc_getAssociatedObject(self, testKey) == test && + test.retainCount == 4 && + R(objc_removeAssociatedObjects(self)) && test.retainCount == 3) + #ifdef OF_OBJFW_RUNTIME if (sizeof(uintptr_t) == 8) value = 0xDEADBEEFDEADBEF; else if (sizeof(uintptr_t) == 4) value = 0xDEADBEF;