Merge branch 'weakref' for wek reference support improvements.

This commit is contained in:
rfm 2025-01-01 15:05:31 +00:00
commit ae8367e401
19 changed files with 840 additions and 192 deletions

View file

@ -637,5 +637,14 @@ void
GSPrivateEncodeBase64(const uint8_t *src, NSUInteger length, uint8_t *dst)
GS_ATTRIB_PRIVATE;
#ifndef OBJC_CAP_ARC
/* 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;
void GSWeakInit() GS_ATTRIB_PRIVATE;
BOOL objc_delete_weak_refs(id obj);
#endif
#endif /* _GSPrivate_h_ */

View file

@ -105,8 +105,8 @@ typedef GSIMapNode_t *GSIMapNode;
pointerFunctionsReplace(&M->cb.pf, (void**)addr, (x).obj);
#define GSI_MAP_READ_KEY(M,addr) \
(M->legacy ? *(addr) :\
(__typeof__(*addr))pointerFunctionsRead(&M->cb.pf, (void**)addr))
(M->legacy ? *(addr) :\
(__typeof__(*addr))pointerFunctionsRead(&M->cb.pf, (void**)addr))
#define GSI_MAP_ENUMERATOR NSHashEnumerator

View file

@ -28,15 +28,15 @@
# include <objc/capabilities.h>
#endif
#import "GNUstepBase/GSObjCRuntime.h"
#define WEAK_READ(x) objc_loadWeak((id*)x)
#define WEAK_WRITE(addr, x) objc_storeWeak((id*)addr, (id)x)
#if defined(OBJC_CAP_ARC)
# include <objc/objc-arc.h>
# define WEAK_READ(x) objc_loadWeak((id*)x)
# define WEAK_WRITE(addr, x) objc_storeWeak((id*)addr, (id)x)
# define STRONG_WRITE(addr, x) objc_storeStrong((id*)addr, (id)x)
# define STRONG_ACQUIRE(x) objc_retain(x)
# define STRONG_WRITE(addr, x) objc_storeStrong((id*)addr, (id)x)
# define STRONG_ACQUIRE(x) objc_retain(x)
#else
# define WEAK_READ(x) (*x)
# define WEAK_WRITE(addr, x) (*(addr) = x)
# define STRONG_WRITE(addr, x) ASSIGN(*((id*)addr), ((id)x))
# define STRONG_ACQUIRE(x) RETAIN(((id)x))
#endif

View file

@ -1705,8 +1705,9 @@ typedef struct {
{
NSMapRemove(messagePortMap, (void*)name);
}
[self retain];
M_UNLOCK(messagePortLock);
[self dealloc];
[super release];
}
else
{

View file

@ -132,17 +132,24 @@ struct NCTbl; /* Notification Center Table structure */
* removed from, or not yet added to any list). The end of a
* list is marked by 'next' being set to 'ENDOBS'.
*
* This is normally a structure which handles memory management using a fast
* reference count mechanism, but when built with clang for GC, a structure
* can't hold a zeroing weak pointer to an observer so it's implemented as a
* trivial class instead ... and gets managed by the garbage collector.
* The observer is a weak reference to the observing object.
* The receiver is a strong reference to the observing object but
* exists only while we are in the process of posting a notification
* to that object (in which case the value of posting is the number
* of times the observation has been added to arrays for posting).
*
* The posting count records the number of times the Observation has
* been added to arrays for posting notification to observers, it is
* needed to track the situation where multiple threads are posting
* notifications to the same server at the same time.
*/
typedef struct Obs {
id observer; /* Object to receive message. */
id observer; /* Reference to object. */
id receiver; /* Retained object (temporary). */
SEL selector; /* Method selector. */
BOOL owner; /* If we own the observer. */
int32_t retained; /* Retain count for structure. */
int32_t posting; /* Retain count for structure. */
struct Obs *next; /* Next item in linked list. */
struct NCTbl *link; /* Pointer back to chunk table */
} Observation;
@ -186,17 +193,19 @@ static inline BOOL doEqual(BOOL shouldHash, NSString* key1, NSString* key2)
*/
static void listFree(Observation *list);
/* Observations have retain/release counts managed explicitly by fast
* function calls.
*/
static void obsRetain(Observation *o);
static void obsFree(Observation *o);
/* Observations have their 'posting' counts managed explicitly by fast
* function calls when thye are added to or removed from arrays.
*/
static void obsPost(Observation *o);
static void obsDone(Observation *o);
#define GSI_ARRAY_TYPES 0
#define GSI_ARRAY_TYPE Observation*
#define GSI_ARRAY_RELEASE(A, X) obsFree(X.ext)
#define GSI_ARRAY_RETAIN(A, X) obsRetain(X.ext)
#define GSI_ARRAY_RELEASE(A, X) obsDone(X.ext)
#define GSI_ARRAY_RETAIN(A, X) obsPost(X.ext)
#include "GNUstepBase/GSIArray.h"
@ -217,7 +226,7 @@ static void obsFree(Observation *o);
/*
* An NC table is used to keep track of memory allocated to store
* Observation structures. When an Observation is removed from the
* notification center, it's memory is returned to the free list of
* notification center, its memory is returned to the free list of
* the chunk table, rather than being released to the general
* memory allocation system. This means that, once a large number
* of observers have been registered, memory usage will never shrink
@ -291,17 +300,17 @@ obsNew(NCTable *t, SEL s, id o)
obs = t->freeList;
t->freeList = (Observation*)obs->link;
obs->link = (void*)t;
obs->retained = 0;
obs->posting = 0;
obs->next = 0;
obs->receiver = nil;
obs->selector = s;
obs->observer = o;
objc_initWeak(&obs->observer, o);
/* Instances of GSNotificationObserverClass are owned by the Observation
* and must be explicitly released when the observation is removed.
*/
obs->owner = (GSNotificationObserverClass == object_getClass(o)) ? YES : NO;
return obs;
}
@ -343,8 +352,7 @@ static void endNCTable(NCTable *t)
TEST_RELEASE(t->_lock);
/*
* Free observations without notification names or numbers.
/* Free observations without notification names or numbers.
*/
listFree(t->wildcard);
@ -433,24 +441,59 @@ static inline void unlockNCTable(NCTable* t)
static void obsFree(Observation *o)
{
NSCAssert(o->retained >= 0, NSInternalInconsistencyException);
if (o->retained-- == 0)
{
NCTable *t = o->link;
NCTable *t;
if (o->owner)
{
DESTROY(o->observer);
o->owner = NO;
}
o->next = 0; // no longer in any active list
if (o->posting)
{
return; // Defer until posting is done
}
/* If we own the observer, we must release it as well as destroying
* the weak reference to it.
*/
if (o->owner)
{
id obj = objc_loadWeak(&o->observer);
[obj autorelease]; // Release to balance the fact we own it.
o->owner = NO;
}
objc_destroyWeak(&o->observer);
/* This observation can now be placed in the free list if there is one.
*/
if ((t = o->link) != 0)
{
o->link = (NCTable*)t->freeList;
t->freeList = o;
}
}
static void obsRetain(Observation *o)
static void obsDone(Observation *o)
{
o->retained++;
if (0 == --o->posting)
{
/* Posting to this observer is over, so we null-out the receiver
* and let it be deallocated if nobody else has retained it.
*/
[o->receiver autorelease];
o->receiver = nil;
/* If the observation was removed from its linked list, it needs
* to be freed now it's no longer being p[osted to.
*/
if (0 == o->next)
{
obsFree(o);
}
}
}
static void obsPost(Observation *o)
{
o->posting++;
}
static void listFree(Observation *list)
@ -477,7 +520,7 @@ static Observation *listPurge(Observation *list, id observer)
{
Observation *tmp;
while (list != ENDOBS && list->observer == observer)
while (list != ENDOBS && objc_loadWeak(&list->observer) == observer)
{
tmp = list->next;
list->next = 0;
@ -489,7 +532,7 @@ static Observation *listPurge(Observation *list, id observer)
tmp = list;
while (tmp->next != ENDOBS)
{
if (tmp->next->observer == observer)
if (objc_loadWeak(&tmp->next->observer) == observer)
{
Observation *next = tmp->next;
@ -546,14 +589,6 @@ purgeMapNode(GSIMapTable map, GSIMapNode node, id observer)
}
}
/* purgeCollected() returns a list of observations with any observations for
* a collected observer removed.
* purgeCollectedFromMapNode() does the same thing but also handles cleanup
* of the map node containing the list if necessary.
*/
#define purgeCollected(X) (X)
#define purgeCollectedFromMapNode(X, Y) ((Observation*)Y->value.ext)
@interface GSNotificationBlockOperation : NSOperation
{
@ -673,14 +708,6 @@ purgeMapNode(GSIMapTable map, GSIMapNode node, id observer)
static NSNotificationCenter *default_center = nil;
+ (void) atExit
{
id tmp = default_center;
default_center = nil;
[tmp release];
}
+ (void) initialize
{
if (self == [NSNotificationCenter class])
@ -704,7 +731,6 @@ static NSNotificationCenter *default_center = nil;
*/
default_center = [self alloc];
[default_center init];
[self registerAtExit];
}
}
@ -819,7 +845,6 @@ static NSNotificationCenter *default_center = nil;
* once - in which case, the observer will receive multiple messages when
* the notification is posted... odd, but the MacOS-X docs specify this.
*/
if (name)
{
/*
@ -937,7 +962,7 @@ static NSNotificationCenter *default_center = nil;
* of our map tables - while enumerating a table, it is safe to remove
* the entry returned by the enumerator.
*/
ENTER_POOL
lockNCTable(TABLE);
if (name == nil && object == nil)
@ -1075,6 +1100,7 @@ static NSNotificationCenter *default_center = nil;
}
unlockNCTable(TABLE);
LEAVE_POOL
}
/**
@ -1089,6 +1115,52 @@ static NSNotificationCenter *default_center = nil;
[self removeObserver: observer name: nil object: nil];
}
static Observation*
addPost(Observation *head, GSIArray a)
{
Observation *p = 0;
Observation *o = head;
Observation *t;
while (o != ENDOBS)
{
t = o->next;
if (o->receiver)
{
/* Posting already in progress, so we post to the same receiver.
*/
GSIArrayAddItem(a, (GSIArrayItem)o);
p = o;
}
else if ((o->receiver = objc_loadWeakRetained(&o->observer)) != nil)
{
/* We will start posting using a newly retained object as receiver.
*/
GSIArrayAddItem(a, (GSIArrayItem)o);
p = o;
}
else
{
/* The object has been deallocated ... remove the observation from
* the list.
*/
if (p)
{
p->next = t;
}
else
{
head = t;
}
o->next = 0;
obsFree(o);
}
o = t;
}
return head;
}
/**
* Private method to perform the actual posting of a notification.
@ -1099,7 +1171,7 @@ static NSNotificationCenter *default_center = nil;
{
Observation *o;
unsigned count;
NSString *name = [notification name];
NSString *name;
id object;
GSIMapNode n;
GSIMapTable m;
@ -1107,52 +1179,48 @@ static NSNotificationCenter *default_center = nil;
GSIArray_t b;
GSIArray a = &b;
if (name == nil)
if ((name = [notification name]) == nil)
{
RELEASE(notification);
[NSException raise: NSInvalidArgumentException
format: @"Tried to post a notification with no name."];
}
/* Do the rest in an autorelease pool so that the observers can be
* safely released (when the pool ends) outside our locked regions.
*/
ENTER_POOL
object = [notification object];
/*
* Lock the table of observations while we traverse it.
*
* The table of observations contains weak pointers which are zeroed when
* the observers get destroyed. So to avoid consistency problems
* we use scanned memory in the array in the case where there are more
* than the 64 observers we allowed room for on the stack.
/* Lock the table of observations while we traverse it.
*/
GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);
lockNCTable(TABLE);
/*
* Find all the observers that specified neither NAME nor OBJECT.
/* Find all the observers that specified neither NAME nor OBJECT.
*/
for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next)
{
GSIArrayAddItem(a, (GSIArrayItem)o);
}
WILDCARD = addPost(WILDCARD, a);
/*
* Find the observers that specified OBJECT, but didn't specify NAME.
/* Find the observers that specified OBJECT, but didn't specify NAME.
*/
if (object)
{
n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
if (n != 0)
if (n)
{
o = purgeCollectedFromMapNode(NAMELESS, n);
while (o != ENDOBS)
if (ENDOBS == (n->value.ext = addPost(n->value.ext, a)))
{
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
GSIMapBucket bucket = GSIMapBucketForKey(NAMELESS, n->key);
GSIMapRemoveNodeFromMap(NAMELESS, bucket, n);
GSIMapFreeNode(NAMELESS, n);
}
}
}
/*
* Find the observers of NAME, except those observers with a non-nil OBJECT
/** Find the observers of NAME, except those observers with a non-nil OBJECT
* that doesn't match the notification's OBJECT).
*/
if (name)
@ -1168,33 +1236,33 @@ static NSNotificationCenter *default_center = nil;
}
if (m != 0)
{
/*
* First, observers with a matching object.
/* First, observers with a matching object.
*/
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n != 0)
{
o = purgeCollectedFromMapNode(m, n);
while (o != ENDOBS)
if (ENDOBS == (n->value.ext = addPost(n->value.ext, a)))
{
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
GSIMapBucket bucket = GSIMapBucketForKey(m, n->key);
GSIMapRemoveNodeFromMap(m, bucket, n);
GSIMapFreeNode(m, n);
}
}
if (object != nil)
{
/*
* Now observers with a nil object.
/* Now observers with a nil object.
*/
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)(id)nil);
if (n != 0)
{
o = purgeCollectedFromMapNode(m, n);
while (o != ENDOBS)
if (ENDOBS == (n->value.ext = addPost(n->value.ext, a)))
{
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
GSIMapBucket bucket = GSIMapBucketForKey(m, n->key);
GSIMapRemoveNodeFromMap(m, bucket, n);
GSIMapFreeNode(m, n);
}
}
}
@ -1205,8 +1273,7 @@ static NSNotificationCenter *default_center = nil;
*/
unlockNCTable(TABLE);
/*
* Now send all the notifications.
/* Now send all the notifications.
*/
count = GSIArrayCount(a);
while (count-- > 0)
@ -1216,7 +1283,7 @@ static NSNotificationCenter *default_center = nil;
{
NS_DURING
{
[o->observer performSelector: o->selector
[o->receiver performSelector: o->selector
withObject: notification];
}
NS_HANDLER
@ -1241,11 +1308,17 @@ static NSNotificationCenter *default_center = nil;
NS_ENDHANDLER
}
}
/* Cleanup of the array of observations must be lock protected.
*/
lockNCTable(TABLE);
GSIArrayEmpty(a);
unlockNCTable(TABLE);
/* Release the notification and any objects we autoreleased during posting
*/
RELEASE(notification);
LEAVE_POOL
}

View file

@ -208,8 +208,7 @@ extern void GSLogZombie(id o, SEL sel)
#undef GSATOMICREAD
#endif
#if defined(__llvm__) || (defined(USE_ATOMIC_BUILTINS) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)))
/* Use the GCC atomic operations with recent GCC versions */
#ifdef OBJC_CAP_ARC
typedef intptr_t volatile *gsatomic_t;
typedef intptr_t gsrefcount_t;
@ -217,6 +216,15 @@ typedef intptr_t gsrefcount_t;
#define GSAtomicIncrement(X) __sync_add_and_fetch(X, 1)
#define GSAtomicDecrement(X) __sync_sub_and_fetch(X, 1)
#elif (defined(USE_ATOMIC_BUILTINS) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)))
/* Use the GCC atomic operations with recent GCC versions */
typedef int32_t volatile *gsatomic_t;
typedef int32_t gsrefcount_t;
#define GSATOMICREAD(X) (*(X))
#define GSAtomicIncrement(X) __sync_add_and_fetch(X, 1)
#define GSAtomicDecrement(X) __sync_sub_and_fetch(X, 1)
#elif defined(_WIN32)
/* Set up atomic read, increment and decrement for mswindows
@ -382,7 +390,7 @@ GSAtomicDecrement(gsatomic_t X)
#include <pthread.h>
typedef int gsrefcount_t; // No atomics, use a simple integer
typedef int32_t gsrefcount_t; // No atomics, use a simple integer
/* Having just one allocationLock for all leads to lock contention
* if there are lots of threads doing lots of retain/release calls.
@ -415,12 +423,21 @@ static inline pthread_mutex_t *GSAllocationLockForObject(id p)
#endif
#define alignof(type) __builtin_offsetof(struct { const char c; type member; }, member)
#ifndef OBJC_CAP_ARC
typedef struct {
BOOL hadWeakReference: 1; // set if the instance ever had a weak reference
} gsinstinfo_t;
#endif
/*
* Define a structure to hold information that is held locally
* (before the start) in each object.
*/
typedef struct obj_layout_unpadded {
gsrefcount_t retained;
#ifndef OBJC_CAP_ARC
gsinstinfo_t extra;
#endif
} unp;
#define UNP sizeof(unp)
@ -441,9 +458,26 @@ struct obj_layout {
char padding[__BIGGEST_ALIGNMENT__ - ((UNP % __BIGGEST_ALIGNMENT__)
? (UNP % __BIGGEST_ALIGNMENT__) : __BIGGEST_ALIGNMENT__)];
gsrefcount_t retained;
#ifndef OBJC_CAP_ARC
gsinstinfo_t extra;
#endif
};
typedef struct obj_layout *obj;
#ifndef OBJC_CAP_ARC
BOOL
GSPrivateMarkedWeak(id anObject, BOOL mark)
{
BOOL wasMarked = ((obj)anObject)[-1].extra.hadWeakReference;
if (mark)
{
((obj)anObject)[-1].extra.hadWeakReference = YES;
}
return wasMarked;
}
#endif
/*
* These symbols are provided by newer versions of the GNUstep Objective-C
* runtime. When linked against an older version, we will use our internal
@ -493,9 +527,7 @@ static BOOL objc_release_fast_no_destroy_internal(id anObject)
* have been greater than zero)
*/
(((obj)anObject)[-1].retained) = 0;
# ifdef OBJC_CAP_ARC
objc_delete_weak_refs(anObject);
# endif
return YES;
}
#else /* GSATOMICREAD */
@ -504,9 +536,7 @@ static BOOL objc_release_fast_no_destroy_internal(id anObject)
pthread_mutex_lock(theLock);
if (((obj)anObject)[-1].retained == 0)
{
# ifdef OBJC_CAP_ARC
objc_delete_weak_refs(anObject);
# endif
pthread_mutex_unlock(theLock);
return YES;
}
@ -947,6 +977,8 @@ static id gs_weak_load(id obj)
{
#ifdef OBJC_CAP_ARC
_objc_weak_load = gs_weak_load;
#else
GSWeakInit();
#endif
objc_create_block_classes_as_subclasses_of(self);
}
@ -2454,6 +2486,10 @@ static id gs_weak_load(id obj)
}
return c;
}
- (NSUInteger) retainCount
{
return 0; // So that gs_weak_load() knows the object was deallocated
}
- (void) logZombie: (SEL)selector
{
GSLogZombie(self, selector);

View file

@ -2474,8 +2474,9 @@ static Class tcpPortClass;
{
NSMapRemove(thePorts, host);
}
[self retain];
M_UNLOCK(tcpPortLock);
[self dealloc];
[super release];
}
else
{

View file

@ -56,7 +56,11 @@ ObjectiveC2_OBJC_FILES += \
endif
ifeq ($(CC), clang)
# We need the flag for blocks support and we have ARC built in.
ADDITIONAL_OBJCFLAGS = -fblocks
else
# We need to emulated the weak reference API from the ARC runtime.
ObjectiveC2_OBJC_FILES += weak.m
endif
-include Makefile.preamble

View file

@ -1,6 +1,6 @@
ObjectiveC2 framework implements the new Apple runtime APIs, introduced with
OS X 10.5 on top of the GCC Objective-C runtime. Its use is now discouraged.
This code has been merged into the GNUstep Objective-C-2.0 runtime, which can
act as a drop-in replacement for GCC libobjc. You can find the code here:
svn://svn.gna.org/svn/gnustep/libs/libobjc2/trunk
OS X 10.5 (and later) on top of the GCC Objective-C runtime. Much code has
been merged into the GNUstep Objective-C-2.0 runtime, which is used when
compiling with clang (svn://svn.gna.org/svn/gnustep/libs/libobjc2/trunk).
Somewhat recent versions of the GNU runtime implement much of the newer Apple
APIs.

451
Source/ObjectiveC2/weak.m Normal file
View file

@ -0,0 +1,451 @@
/* Emulation of ARC runtime support for weak references based on the gnustep
* runtime implementation.
*/
#import "common.h"
#import "Foundation/Foundation.h"
#import "../GSPrivate.h"
#import "../GSPThread.h"
static Class persistentClasses[1];
static int persistentClassCount = sizeof(persistentClasses)/sizeof(Class);
/* This function needs to identify objects which should NOT be handled by
* weak references.
* Nil is a special case which can not be stored as a weak reference because
* it indicates the absence of an object etc.
* Persistent objects do not need any sort of weak (or strong) reference and
* if they are immutable then trying to mark them as referenced would crash.
*/
__attribute__((always_inline))
static inline BOOL
isPersistentObject(id obj)
{
Class c;
int i;
if (nil == obj)
{
return YES;
}
/* If the alignment of the object does not match that needed for a
* pointer (to the class of the object) then the object must be a
* special one of some sort and we assume it's persistent.
*/
#if GS_SIZEOF_VOIDP == 8
if ((intptr_t)obj & 15)
{
return YES;
}
#else
if ((intptr_t)obj & 7)
{
return YES;
}
#endif
c = object_getClass(obj);
if (class_isMetaClass(c))
{
return YES; // obj was a class rather than an instance
}
for (i = 0; i < persistentClassCount; i++)
{
if (persistentClasses[i] == c)
{
return YES; // the object is a persistent instance
}
}
return NO;
}
static int WeakRefClass = 0;
#define GSI_MAP_NODE_CLASS (&WeakRefClass)
#define GSI_MAP_CLEAR_KEY(M, X) ((*X).obj = nil)
#define GSI_MAP_HASH(M, X) ((NSUInteger)(X.obj) >> 2)
#define GSI_MAP_EQUAL(M, X, Y) (X.obj == Y.obj)
#define GSI_MAP_RETAIN_KEY(M, X)
#define GSI_MAP_RELEASE_KEY(M, X)
#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
#include "GNUstepBase/GSIMap.h"
typedef GSIMapNode_t WeakRef;
static gs_mutex_t weakLock = GS_MUTEX_INIT_STATIC;
/* The weakRefs table contains weak references (nodes) for weak references
* to any active objects.
*/
static GSIMapTable_t weakRefs = { 0 };
/* The deallocated list contains the weak references (nodes) for objects
* which have already been deallocated (so the references are now to nil).
*/
static WeakRef *deallocated = NULL;
/* This must be called on startup before any weak references are taken.
*/
void
GSWeakInit()
{
GS_MUTEX_LOCK(weakLock);
if (0 == weakRefs.increment)
{
GSIMapInitWithZoneAndCapacity(
&weakRefs, NSDefaultMallocZone(), 1024);
persistentClasses[0] = [NXConstantString class];
}
GS_MUTEX_UNLOCK(weakLock);
}
/* Load from a weak pointer and return whether this really was a weak
* reference or a strong (not deallocatable) object in a weak pointer.
* The object will be stored in 'obj' and the weak reference in 'ref',
* if one exists.
*/
inline static BOOL
loadWeakPointer(id *addr, id *obj, WeakRef **ref)
{
id oldObj = *addr;
if (nil == oldObj)
{
*ref = NULL;
*obj = nil;
return NO;
}
if (*(void**)oldObj == (void*)&WeakRefClass)
{
*ref = (WeakRef*)oldObj;
*obj = (*ref)->key.obj;
return YES;
}
*ref = NULL;
*obj = oldObj;
return NO;
}
__attribute__((always_inline))
static inline BOOL
weakRefRelease(WeakRef *ref)
{
ref->value.nsi--;
if (ref->value.nsi == 0)
{
if (nil == ref->key.obj)
{
/* The object was already deallocated so we must remove this
* reference from the deallocated list.
*/
if (deallocated == ref)
{
deallocated = ref->nextInBucket;
}
else
{
WeakRef *tmp = deallocated;
while (tmp->nextInBucket != 0)
{
if (tmp->nextInBucket == ref)
{
tmp->nextInBucket = ref->nextInBucket;
break;
}
tmp = tmp->nextInBucket;
}
}
ref->nextInBucket = weakRefs.freeNodes;
weakRefs.freeNodes = ref;
}
else
{
GSIMapBucket bucket = GSIMapBucketForKey(&weakRefs, ref->key);
GSIMapRemoveNodeFromMap(&weakRefs, bucket, ref);
GSIMapFreeNode(&weakRefs, ref);
}
return YES;
}
return NO;
}
/* We should record the fact that the object has weak references (unless
* it is a persistent one).
* Return YES if the object is persistent and should not have weak references,
* NO otherwise.
*/
static BOOL
setObjectHasWeakRefs(id obj)
{
BOOL isPersistent = isPersistentObject(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()
*/
}
return isPersistent;
}
static WeakRef *
incrementWeakRefCount(id obj)
{
GSIMapKey key;
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);
if (NULL == ref)
{
ref = GSIMapGetNode(&weakRefs);
ref->key.obj = obj;
ref->value.nsi = 1;
GSIMapAddNodeToBucket(bucket, ref);
weakRefs.nodeCount++;
return ref;
}
ref->value.nsi++;
return ref;
}
id
objc_storeWeak(id *addr, id obj)
{
WeakRef *oldRef;
id old;
BOOL isGlobalObject;
GS_MUTEX_LOCK(weakLock);
loadWeakPointer(addr, &old, &oldRef);
/* If the old and new values are the same (and we are not setting a nil
* value to destroy an existing weak reference), then we don't need to
* do anything.
*/
if ((obj != nil || oldRef == NULL) && old == obj)
{
GS_MUTEX_UNLOCK(weakLock);
return obj;
}
isGlobalObject = setObjectHasWeakRefs(obj);
/* If we old ref exists, decrement its reference count. This may also
* delete the weak reference from the map.
*/
if (oldRef != NULL)
{
weakRefRelease(oldRef);
}
/* If we're storing nil, then just write a null pointer.
*/
if (nil == obj)
{
*addr = obj;
}
else if (isGlobalObject)
{
/* If this is a global object, it's never deallocated,
* so we don't make this a weak reference.
*/
*addr = obj;
}
else if ([obj retainCount] == 0)
{
/* The object is being deallocated ... we must store nil.
*/
*addr = obj = nil;
}
else
{
*addr = (id)incrementWeakRefCount(obj);
}
GS_MUTEX_UNLOCK(weakLock);
return obj;
}
/* Function called when objects are deallocated
*/
BOOL
objc_delete_weak_refs(id obj)
{
GSIMapKey key;
GSIMapBucket bucket;
WeakRef *ref;
/* For performance we should have marked the object as having
* weak references and we check that in order to avoid the cost
* of the map table lookup when it's not needed.
*/
if (NO == GSPrivateMarkedWeak(obj, NO))
{
return NO;
}
key.obj = obj;
GS_MUTEX_LOCK(weakLock);
bucket = GSIMapBucketForKey(&weakRefs, key);
ref = GSIMapNodeForKeyInBucket(&weakRefs, bucket, key);
if (ref)
{
GSIMapRemoveNodeFromBucket(bucket, ref);
ref->key.obj = nil;
weakRefs.nodeCount--;
/* The object is deallocated but there are still weak references
* to it so we put the weak reference node in the deallocated list.
*/
ref->nextInBucket = deallocated;
deallocated = ref;
}
GS_MUTEX_UNLOCK(weakLock);
return YES;
}
id
objc_loadWeakRetained(id *addr)
{
id obj;
WeakRef *ref;
GS_MUTEX_LOCK(weakLock);
/* If this is not actually a weak pointer, return the object directly.
*/
if (!loadWeakPointer(addr, &obj, &ref))
{
GS_MUTEX_UNLOCK(weakLock);
return obj;
}
if (nil == obj)
{
/* The object has been destroed so we should remove the weak
* reference to it.
*/
if (ref != NULL)
{
weakRefRelease(ref);
*addr = nil;
}
GS_MUTEX_UNLOCK(weakLock);
return nil;
}
obj = [obj retain];
GS_MUTEX_UNLOCK(weakLock);
return obj;
}
id
objc_loadWeak(id *object)
{
return [objc_loadWeakRetained(object) autorelease];
}
void
objc_copyWeak(id *dest, id *src)
{
/* Don't retain or release.
* 'src' is a valid pointer to a __weak pointer or nil.
* 'dest' is a valid pointer to uninitialised memory.
* After this operation, 'dest' should contain whatever 'src' contained.
*/
id obj;
WeakRef *srcRef;
GS_MUTEX_LOCK(weakLock);
loadWeakPointer(src, &obj, &srcRef);
*dest = *src;
if (srcRef)
{
srcRef->value.nsi++;
}
GS_MUTEX_UNLOCK(weakLock);
}
void
objc_moveWeak(id *dest, id *src)
{
/* Don't retain or release.
* 'dest' is a valid pointer to uninitialized memory.
* 'src' is a valid pointer to a __weak pointer.
* This operation moves from *src to *dest and must be atomic with respect
* to other stores to *src via 'objc_storeWeak'.
*/
GS_MUTEX_LOCK(weakLock);
*dest = *src;
*src = nil;
GS_MUTEX_UNLOCK(weakLock);
}
void
objc_destroyWeak(id *obj)
{
WeakRef *oldRef;
id old;
GS_MUTEX_LOCK(weakLock);
loadWeakPointer(obj, &old, &oldRef);
/* If the old ref exists, decrement its reference count.
* This may also remove the weak reference from the map.
*/
if (oldRef != NULL)
{
weakRefRelease(oldRef);
}
GS_MUTEX_UNLOCK(weakLock);
}
id
objc_initWeak(id *addr, id obj)
{
BOOL isGlobalObject;
if (nil == obj)
{
*addr = nil;
return nil;
}
GS_MUTEX_LOCK(weakLock);
isGlobalObject = setObjectHasWeakRefs(obj);
if (isGlobalObject)
{
*addr = obj;
GS_MUTEX_UNLOCK(weakLock);
return obj;
}
if ([obj retainCount] == 0)
{
*addr = nil;
GS_MUTEX_UNLOCK(weakLock);
return nil;
}
if (nil != obj)
{
*(WeakRef**)addr = incrementWeakRefCount(obj);
}
GS_MUTEX_UNLOCK(weakLock);
return obj;
}