/* * 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" struct Association { id object; objc_associationPolicy policy; }; #ifdef OF_HAVE_THREADS # define numSlots 8 /* needs to be a power of 2 */ # import "OFPlainMutex.h" static OFSpinlock spinlocks[numSlots]; #else # define numSlots 1 #endif static struct objc_hashtable *hashtables[numSlots]; static OF_INLINE size_t slotForObject(id object) { return ((size_t)((uintptr_t)object >> 4) & (numSlots - 1)); } 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() { for (size_t i = 0; i < numSlots; i++) { hashtables[i] = objc_hashtable_new(hash, equal, 2); #ifdef OF_HAVE_THREADS 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) { size_t slot; 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; } slot = slotForObject(object); #ifdef OF_HAVE_THREADS if (OFSpinlockLock(&spinlocks[slot]) != 0) OBJC_ERROR("Failed to lock spinlock!"); @try { #endif struct objc_hashtable *objectHashtable; struct Association *association; objectHashtable = objc_hashtable_get(hashtables[slot], object); if (objectHashtable == NULL) { objectHashtable = objc_hashtable_new(hash, equal, 2); objc_hashtable_set(hashtables[slot], 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) { size_t slot = slotForObject(object); #ifdef OF_HAVE_THREADS if (OFSpinlockLock(&spinlocks[slot]) != 0) OBJC_ERROR("Failed to lock spinlock!"); @try { #endif struct objc_hashtable *objectHashtable; struct Association *association; objectHashtable = objc_hashtable_get(hashtables[slot], 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) { size_t slot = slotForObject(object); #ifdef OF_HAVE_THREADS if (OFSpinlockLock(&spinlocks[slot]) != 0) OBJC_ERROR("Failed to lock spinlock!"); @try { #endif struct objc_hashtable *objectHashtable; objectHashtable = objc_hashtable_get(hashtables[slot], 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(hashtables[slot], object); objc_hashtable_free(objectHashtable); #ifdef OF_HAVE_THREADS } @finally { if (OFSpinlockUnlock(&spinlocks[slot]) != 0) OBJC_ERROR("Failed to unlock spinlock!"); } #endif }