mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-23 00:41:02 +00:00
associated objects initial implementation
This commit is contained in:
parent
83e67957bc
commit
a46f86837f
6 changed files with 303 additions and 15 deletions
11
ChangeLog
11
ChangeLog
|
@ -1,3 +1,14 @@
|
|||
2025-03-08 Richard Frith-Macdonald <rfm@gnu.org>
|
||||
|
||||
* Headers/GNUstepBase/GSObjCRuntime.h:
|
||||
* Source/GSPrivate.h:
|
||||
* Source/NSObject.m:
|
||||
* Source/ObjectiveC2/weak.m:
|
||||
* Tests/base/Functions/runtime.m:
|
||||
Initial implementation of associated objects for GNU runtime ...
|
||||
objc_getAssociatedObject(), objc_removeAssociatedObjects(),
|
||||
and objc_setAssociatedObject()
|
||||
|
||||
2025-03-06 Richard Frith-Macdonald <rfm@gnu.org>
|
||||
|
||||
* Source/NSTask.m: bugfix for testing a running task ... the task may
|
||||
|
|
|
@ -77,6 +77,38 @@ GS_EXPORT id objc_loadWeak(id *object);
|
|||
GS_EXPORT id objc_loadWeakRetained(id *addr);
|
||||
GS_EXPORT void objc_moveWeak(id *dest, id *src);
|
||||
GS_EXPORT id objc_storeWeak(id *addr, id obj);
|
||||
|
||||
/** objc_AssociationPolicy acts like a bitfield, but
|
||||
* only specific combinations of flags are permitted.
|
||||
*/
|
||||
typedef enum uintptr_t {
|
||||
|
||||
/** Simple pointer assignment.
|
||||
*/
|
||||
OBJC_ASSOCIATION_ASSIGN = 0,
|
||||
|
||||
/** Retain when set, release old value.
|
||||
*/
|
||||
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
|
||||
|
||||
/** Copy when set (by sending a -copy message), release old value.
|
||||
*/
|
||||
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
|
||||
|
||||
/** Atomic retain.
|
||||
*/
|
||||
OBJC_ASSOCIATION_RETAIN = 0x301,
|
||||
|
||||
/** Atomic copy.
|
||||
*/
|
||||
OBJC_ASSOCIATION_COPY = 0x303
|
||||
} objc_AssociationPolicy;
|
||||
|
||||
GS_EXPORT id objc_getAssociatedObject(id object, const void *key);
|
||||
GS_EXPORT void objc_removeAssociatedObjects(id object);
|
||||
GS_EXPORT void objc_setAssociatedObject(id object, const void *key,
|
||||
id value, objc_AssociationPolicy policy);
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
|
|
@ -641,7 +641,8 @@ GSPrivateEncodeBase64(const uint8_t *src, NSUInteger length, uint8_t *dst)
|
|||
/* When we don't have a runtime with ARC to support weak references, we
|
||||
* use our own version.
|
||||
*/
|
||||
BOOL GSPrivateMarkedWeak(id obj, BOOL mark) GS_ATTRIB_PRIVATE;
|
||||
BOOL GSPrivateMarkedAssociations(id obj, BOOL mark) GS_ATTRIB_PRIVATE;
|
||||
BOOL GSPrivateMarkedWeak(id obj, BOOL mark) GS_ATTRIB_PRIVATE;
|
||||
void GSWeakInit() GS_ATTRIB_PRIVATE;
|
||||
BOOL objc_delete_weak_refs(id obj);
|
||||
#endif
|
||||
|
|
|
@ -434,7 +434,8 @@ static inline pthread_mutex_t *GSAllocationLockForObject(id p)
|
|||
|
||||
#ifndef OBJC_CAP_ARC
|
||||
typedef struct {
|
||||
BOOL hadWeakReference: 1; // set if the instance ever had a weak reference
|
||||
BOOL hadWeakReference: 1; // if the instance ever had a weak reference
|
||||
BOOL hadAssociations: 1; // if the instance ever had associated objects
|
||||
} gsinstinfo_t;
|
||||
#endif
|
||||
|
||||
|
@ -475,7 +476,18 @@ typedef struct obj_layout *obj;
|
|||
|
||||
#ifndef OBJC_CAP_ARC
|
||||
BOOL
|
||||
GSPrivateMarkedWeak(id anObject, BOOL mark)
|
||||
GSPrivateMarkedAssociations(id anObject, BOOL mark)
|
||||
{
|
||||
BOOL wasMarked = ((obj)anObject)[-1].extra.hadAssociations;
|
||||
|
||||
if (mark)
|
||||
{
|
||||
((obj)anObject)[-1].extra.hadAssociations = YES;
|
||||
}
|
||||
return wasMarked;
|
||||
}
|
||||
BOOL
|
||||
GSPrivateMarkedWeak(id anObject, BOOL mark)
|
||||
{
|
||||
BOOL wasMarked = ((obj)anObject)[-1].extra.hadWeakReference;
|
||||
|
||||
|
@ -853,6 +865,14 @@ NSDeallocateObject(id anObject)
|
|||
(*finalize_imp)(anObject, finalize_sel);
|
||||
|
||||
AREM(aClass, (id)anObject);
|
||||
|
||||
#ifndef OBJC_CAP_ARC
|
||||
if (GSPrivateMarkedAssociations(anObject, NO))
|
||||
{
|
||||
objc_removeAssociatedObjects(anObject);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (NSZombieEnabled)
|
||||
{
|
||||
/* Replace the isa pointer etc to turn the object into a zombie.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
/* Emulation of ARC runtime support for weak references based on the gnustep
|
||||
* runtime implementation.
|
||||
/* Emulation of ARC runtime support for weak references and associated objects,
|
||||
* partially based on the libobjc2 runtime implementation for weak objects.
|
||||
*/
|
||||
|
||||
#import "common.h"
|
||||
|
@ -68,7 +68,7 @@ static int WeakRefClass = 0;
|
|||
#define GSI_MAP_RETAIN_VAL(M, X)
|
||||
#define GSI_MAP_RELEASE_VAL(M, X)
|
||||
#define GSI_MAP_KTYPES GSUNION_OBJ
|
||||
#define GSI_MAP_VTYPES GSUNION_NSINT
|
||||
#define GSI_MAP_VTYPES GSUNION_NSINT | GSUNION_PTR
|
||||
|
||||
#include "GNUstepBase/GSIMap.h"
|
||||
|
||||
|
@ -86,8 +86,13 @@ static GSIMapTable_t weakRefs = { 0 };
|
|||
*/
|
||||
static WeakRef *deallocated = NULL;
|
||||
|
||||
/* The associated table contains associated object lists for any objects
|
||||
* which have associated objects.
|
||||
*/
|
||||
static GSIMapTable_t associated = { 0 };
|
||||
|
||||
/* This must be called on startup before any weak references are taken.
|
||||
/* This must be called on startup before any weak references are taken
|
||||
* or associated objects are used.
|
||||
*/
|
||||
void
|
||||
GSWeakInit()
|
||||
|
@ -97,6 +102,8 @@ GSWeakInit()
|
|||
{
|
||||
GSIMapInitWithZoneAndCapacity(
|
||||
&weakRefs, NSDefaultMallocZone(), 1024);
|
||||
GSIMapInitWithZoneAndCapacity(
|
||||
&associated, NSDefaultMallocZone(), 1024);
|
||||
persistentClasses[0] = [NXConstantString class];
|
||||
}
|
||||
GS_MUTEX_UNLOCK(weakLock);
|
||||
|
@ -186,9 +193,7 @@ setObjectHasWeakRefs(id obj)
|
|||
|
||||
if (NO == isPersistent)
|
||||
{
|
||||
/* FIXME ... for performance we should mark the object as having
|
||||
* weak references and we should check that in objc_delete_weak_refs()
|
||||
*/
|
||||
GSPrivateMarkedWeak(obj, YES);
|
||||
}
|
||||
return isPersistent;
|
||||
}
|
||||
|
@ -200,11 +205,6 @@ incrementWeakRefCount(id obj)
|
|||
GSIMapBucket bucket;
|
||||
WeakRef *ref;
|
||||
|
||||
/* Mark the instance as having had a weak reference at some point.
|
||||
* This information is used when the instance is deallocated.
|
||||
*/
|
||||
GSPrivateMarkedWeak(obj, YES);
|
||||
|
||||
key.obj = obj;
|
||||
bucket = GSIMapBucketForKey(&weakRefs, key);
|
||||
ref = GSIMapNodeForKeyInBucket(&weakRefs, bucket, key);
|
||||
|
@ -444,3 +444,215 @@ objc_initWeak(id *addr, id obj)
|
|||
return obj;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static gs_mutex_t associatedLock = GS_MUTEX_INIT_STATIC;
|
||||
|
||||
#define REFBLOCKSIZE 8
|
||||
|
||||
typedef struct association_t {
|
||||
id value;
|
||||
const void *key;
|
||||
objc_AssociationPolicy policy;
|
||||
} association;
|
||||
|
||||
typedef struct assocblock_t {
|
||||
gs_mutex_t mutex; // Protect this block
|
||||
struct assocblock_t *more; // pointer to next block if any
|
||||
struct association_t associations[REFBLOCKSIZE];
|
||||
} *assocptr;
|
||||
|
||||
/* Returns a slot matching the key or an empty slot (or NULL if neither
|
||||
* exists and mayCreate is NO).
|
||||
*/
|
||||
static association *
|
||||
getAssociation(const void *key, assocptr ptr, BOOL mayCreate)
|
||||
{
|
||||
unsigned index;
|
||||
|
||||
for (index = 0; index < REFBLOCKSIZE; index++)
|
||||
{
|
||||
if (ptr->associations[index].key == key)
|
||||
{
|
||||
return ptr->associations + index;
|
||||
}
|
||||
}
|
||||
if (ptr->more)
|
||||
{
|
||||
return getAssociation(key, ptr->more, mayCreate);
|
||||
}
|
||||
if (mayCreate)
|
||||
{
|
||||
ptr->more = (assocptr)calloc(1, sizeof(struct assocblock_t));
|
||||
return ptr->more->associations;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
setAssociation(association *a, const void *key, id value,
|
||||
objc_AssociationPolicy policy)
|
||||
{
|
||||
objc_AssociationPolicy oldPolicy;
|
||||
id oldObject;
|
||||
|
||||
switch (policy)
|
||||
{
|
||||
case OBJC_ASSOCIATION_COPY_NONATOMIC:
|
||||
case OBJC_ASSOCIATION_COPY:
|
||||
value = [value copy];
|
||||
break;
|
||||
case OBJC_ASSOCIATION_RETAIN_NONATOMIC:
|
||||
case OBJC_ASSOCIATION_RETAIN:
|
||||
value = [value retain];
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
oldObject = a->value;
|
||||
oldPolicy = a->policy;
|
||||
|
||||
a->value = value;
|
||||
a->key = key;
|
||||
a->policy = policy;
|
||||
|
||||
switch (oldPolicy)
|
||||
{
|
||||
case OBJC_ASSOCIATION_COPY_NONATOMIC:
|
||||
case OBJC_ASSOCIATION_COPY:
|
||||
case OBJC_ASSOCIATION_RETAIN_NONATOMIC:
|
||||
case OBJC_ASSOCIATION_RETAIN:
|
||||
[oldObject release];
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clearAssociations(assocptr ptr)
|
||||
{
|
||||
if (ptr)
|
||||
{
|
||||
unsigned index = REFBLOCKSIZE;
|
||||
|
||||
clearAssociations(ptr->more);
|
||||
free(ptr->more);
|
||||
ptr->more = NULL;
|
||||
for (index = 0; index < REFBLOCKSIZE; index++)
|
||||
{
|
||||
setAssociation(ptr->associations + index, NULL, nil, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id
|
||||
objc_getAssociatedObject(id object, const void *key)
|
||||
{
|
||||
GSIMapKey nodeKey;
|
||||
GSIMapBucket bucket;
|
||||
GSIMapNode node;
|
||||
assocptr block = NULL;
|
||||
id found = nil;
|
||||
|
||||
nodeKey.obj = object;
|
||||
|
||||
/* Look up the associations for the object, ensuring that the lock
|
||||
* for those associations is obtained before we release the global
|
||||
* lock.
|
||||
*/
|
||||
GS_MUTEX_LOCK(associatedLock);
|
||||
bucket = GSIMapBucketForKey(&associated, nodeKey);
|
||||
node = GSIMapNodeForKeyInBucket(&associated, bucket, nodeKey);
|
||||
if (node)
|
||||
{
|
||||
block = node->value.ptr;
|
||||
GS_MUTEX_LOCK(block->mutex);
|
||||
}
|
||||
GS_MUTEX_UNLOCK(associatedLock);
|
||||
|
||||
/* If there were any associated objects, search for the one matching
|
||||
* the key and return it.
|
||||
*/
|
||||
if (block)
|
||||
{
|
||||
association *ptr = getAssociation(key, block, NO);
|
||||
|
||||
if (ptr != NULL)
|
||||
{
|
||||
found = ptr->value;
|
||||
}
|
||||
GS_MUTEX_UNLOCK(block->mutex);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void
|
||||
objc_removeAssociatedObjects(id object)
|
||||
{
|
||||
GSIMapKey key;
|
||||
GSIMapBucket bucket;
|
||||
GSIMapNode node;
|
||||
assocptr ptr = NULL;
|
||||
|
||||
GS_MUTEX_LOCK(associatedLock);
|
||||
key.obj = object;
|
||||
bucket = GSIMapBucketForKey(&associated, key);
|
||||
node = GSIMapNodeForKeyInBucket(&associated, bucket, key);
|
||||
if (node)
|
||||
{
|
||||
GSIMapRemoveNodeFromMap(&associated, bucket, node);
|
||||
ptr = node->value.ptr;
|
||||
node->value.ptr = NULL;
|
||||
GSIMapFreeNode(&associated, node);
|
||||
GS_MUTEX_LOCK(ptr->mutex);
|
||||
}
|
||||
GS_MUTEX_UNLOCK(associatedLock);
|
||||
if (ptr)
|
||||
{
|
||||
clearAssociations(ptr);
|
||||
GS_MUTEX_UNLOCK(ptr->mutex);
|
||||
free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
objc_setAssociatedObject(id object, const void *key, id value,
|
||||
objc_AssociationPolicy policy)
|
||||
{
|
||||
GSIMapKey nodeKey;
|
||||
GSIMapBucket bucket;
|
||||
GSIMapNode node;
|
||||
assocptr blk;
|
||||
association *association;
|
||||
|
||||
nodeKey.obj = object;
|
||||
|
||||
/* Look up the associations for the object, ensuring that the lock
|
||||
* for those associations is obtained before we release the global
|
||||
* lock.
|
||||
*/
|
||||
GS_MUTEX_LOCK(associatedLock);
|
||||
bucket = GSIMapBucketForKey(&associated, nodeKey);
|
||||
node = GSIMapNodeForKeyInBucket(&associated, bucket, nodeKey);
|
||||
if (NULL == node)
|
||||
{
|
||||
node = GSIMapGetNode(&associated);
|
||||
|
||||
node->key.obj = object;
|
||||
node->value.ptr = calloc(1, sizeof(struct assocblock_t));
|
||||
GSIMapAddNodeToBucket(bucket, node);
|
||||
associated.nodeCount++;
|
||||
if (NO == isPersistentObject(object))
|
||||
{
|
||||
// Needs cleanup on dealloc
|
||||
GSPrivateMarkedAssociations(object, YES);
|
||||
}
|
||||
}
|
||||
blk = (assocptr)(node->value.ptr);
|
||||
GS_MUTEX_LOCK(blk->mutex);
|
||||
GS_MUTEX_UNLOCK(associatedLock);
|
||||
association = getAssociation(key, blk, YES);
|
||||
setAssociation(association, key, value, policy);
|
||||
GS_MUTEX_UNLOCK(blk->mutex);
|
||||
}
|
||||
|
||||
|
|
|
@ -97,6 +97,18 @@ main(int argc, char *argv[])
|
|||
const char *n;
|
||||
unsigned u;
|
||||
int i;
|
||||
NSObject *assoc1 = AUTORELEASE([NSObject new]);
|
||||
NSObject *assoc2 = AUTORELEASE([NSObject new]);
|
||||
NSObject *o = AUTORELEASE([NSObject new]);
|
||||
|
||||
u = [assoc1 retainCount];
|
||||
objc_setAssociatedObject(o, (void*)1, assoc1, OBJC_ASSOCIATION_ASSIGN);
|
||||
u = [assoc1 retainCount];
|
||||
PASS(1 == u, "OBJC_ASSOCIATION_ASSIGN does not retain")
|
||||
PASS(objc_getAssociatedObject(o, (void*)1) == assoc1,
|
||||
"can get and set an associated object")
|
||||
objc_setAssociatedObject(o, (void*)1, assoc1, OBJC_ASSOCIATION_RETAIN);
|
||||
PASS(1 == u, "OBJC_ASSOCIATION_RETAIN does retain")
|
||||
|
||||
t0 = "1@1:@";
|
||||
t1 = NSGetSizeAndAlignment(t0, &s, &a);
|
||||
|
|
Loading…
Reference in a new issue