1 #import <objc/runtime.h>
2 #import <Foundation/NSObject.h>
3 #import <Foundation/NSObject.h>
10 // the cache entry size must be a power of 2
15 objc_AssociationPolicy policy;
16 } AssociationObjectEntry;
18 #define BucketsInitialSize 509 // Some prime size (and as a bonus, next three n*2+1 are prime too)
19 #define AssociationObjectEntrySize 5
21 #define HASHPTR(p) (((unsigned int)p)>>5)
23 typedef struct AssociationHashBucket {
24 struct AssociationHashBucket *next;
26 AssociationObjectEntry *value;
27 } AssociationHashBucket;
29 typedef struct AssociationTable {
31 unsigned int nBuckets;
32 AssociationHashBucket **buckets;
35 AssociationTable *CreateAssociationTable(unsigned int capacity) {
36 AssociationTable *table=malloc(sizeof(AssociationTable));
39 table->nBuckets=(capacity<5)?5:capacity;
40 table->buckets=calloc(table->nBuckets,sizeof(AssociationHashBucket *));
45 AssociationObjectEntry *AssociationTableGet(AssociationTable *table,id key){
46 unsigned int i=HASHPTR(key)%table->nBuckets;
47 AssociationHashBucket *j;
49 for(j=table->buckets[i];j!=NULL;j=j->next)
57 void AssociationTableInsert(AssociationTable *table,id key, AssociationObjectEntry *value){
58 unsigned int hash=HASHPTR(key);
59 unsigned int i=hash%table->nBuckets;
60 AssociationHashBucket *j;
62 for(j=table->buckets[i];j!=NULL;j=j->next) {
64 AssociationObjectEntry *oldValue=j->value;
73 if (table->count>=table->nBuckets) {
74 // Expand the buckets size to limit collisions
75 newSize=table->nBuckets*2+1; // Let"s use odd size
76 } else if (table->count > BucketsInitialSize && table->count < table->nBuckets/2) {
77 // Compact the table - plenty of free room
78 newSize=table->nBuckets/2;
85 unsigned int nBuckets=table->nBuckets;
86 AssociationHashBucket **buckets=table->buckets;
88 table->nBuckets=newSize;
89 table->buckets=calloc(table->nBuckets,sizeof(AssociationHashBucket *));
91 // Get all of the existing buckets and place them into the new list
92 for(i=0;i<nBuckets;i++) {
95 unsigned int newi=HASHPTR(j->key)%table->nBuckets;
97 AssociationHashBucket *next = j->next;
98 j->next=table->buckets[newi];
99 table->buckets[newi]=j;
105 i=hash%table->nBuckets;
108 j=malloc(sizeof(AssociationHashBucket));
111 j->next=table->buckets[i];
116 void *AssociationTableInsertIfAbsent(AssociationTable *table, id key, AssociationObjectEntry *value){
117 void *old=AssociationTableGet(table,key);
121 AssociationTableInsert(table,key,value);
125 typedef unsigned int AssociationSpinLock;
127 void AssociationSpinLockLock( volatile AssociationSpinLock *__lock )
129 while(!__sync_bool_compare_and_swap(__lock, 0, 1))
139 void AssociationSpinLockUnlock( volatile AssociationSpinLock *__lock )
141 __sync_bool_compare_and_swap(__lock, 1, 0);
144 static AssociationSpinLock AssociationLock=0;
145 static AssociationTable *associationTable = NULL;
148 void AssociationTableRemove(AssociationTable *table,id key){
149 AssociationSpinLockLock(&AssociationLock);
151 unsigned int i=HASHPTR(key)%table->nBuckets;
152 AssociationHashBucket *j=table->buckets[i],*prev=j;
154 for(;j!=NULL;j=j->next){
156 // array to keep track of the objects to release - that must be done outside of the lock
157 // since the release can trigger more association changes
158 int releaseCount = 0;
159 int releaseTableSize = 0;
160 id *objectsToRelease = NULL;
163 table->buckets[i]=j->next;
167 AssociationObjectEntry *entry = j->value;
169 for (int i = 0; i < AssociationObjectEntrySize; i++) {
170 AssociationObjectEntry *e = entry + i;
173 case OBJC_ASSOCIATION_ASSIGN:
175 case OBJC_ASSOCIATION_RETAIN_NONATOMIC:
176 case OBJC_ASSOCIATION_RETAIN:
177 case OBJC_ASSOCIATION_COPY_NONATOMIC:
178 case OBJC_ASSOCIATION_COPY:
179 if (releaseCount >= releaseTableSize) {
180 if (releaseTableSize == 0) {
181 releaseTableSize = 8;
183 releaseTableSize *= 2;
185 objectsToRelease = realloc(objectsToRelease, sizeof(id)*releaseTableSize);
187 objectsToRelease[releaseCount++] = e->object;
190 AssociationObjectEntry *currentEntry = e;
192 // Don't free the first entry of the list - it's part of the "entry" block that will be freed after the loop
193 if (currentEntry != entry + i) {
203 AssociationSpinLockUnlock(&AssociationLock);
205 // Do the cleaning outside of the lock since it might trigger more association playing
206 for (int i = 0; i < releaseCount; ++i) {
207 [objectsToRelease[i] release];
209 free(objectsToRelease);
215 AssociationSpinLockUnlock(&AssociationLock);
218 void objc_removeAssociatedObjects(id object)
220 if (associationTable == NULL) {
223 AssociationTableRemove(associationTable, object);
227 void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
229 AssociationSpinLockLock(&AssociationLock);
231 if (associationTable == NULL) {
232 associationTable = CreateAssociationTable(BucketsInitialSize);
235 AssociationObjectEntry *objectTable = AssociationTableGet(associationTable, object);
236 if (objectTable == NULL) {
237 objectTable = calloc(sizeof(AssociationObjectEntry), AssociationObjectEntrySize);
238 AssociationTableInsert(associationTable, object, objectTable);
241 uintptr_t index = HASHPTR(key) % AssociationObjectEntrySize;
242 AssociationObjectEntry *entry = ((AssociationObjectEntry *)objectTable) + index;
243 if (entry->object == nil) {
244 entry->policy = policy;
247 case OBJC_ASSOCIATION_ASSIGN:
248 entry->object = value;
250 case OBJC_ASSOCIATION_RETAIN_NONATOMIC:
251 case OBJC_ASSOCIATION_RETAIN:
252 entry->object = [value retain];
254 case OBJC_ASSOCIATION_COPY_NONATOMIC:
255 case OBJC_ASSOCIATION_COPY:
256 entry->object = [value copy];
261 AssociationObjectEntry *newEntry;
263 if (entry->key == key) {
264 id objectToRelease = nil;
266 switch (entry->policy) {
267 case OBJC_ASSOCIATION_ASSIGN:
269 case OBJC_ASSOCIATION_RETAIN_NONATOMIC:
270 case OBJC_ASSOCIATION_RETAIN:
271 case OBJC_ASSOCIATION_COPY_NONATOMIC:
272 case OBJC_ASSOCIATION_COPY:
273 objectToRelease = entry->object;
276 entry->policy = policy;
278 case OBJC_ASSOCIATION_ASSIGN:
279 entry->object = value;
281 case OBJC_ASSOCIATION_RETAIN_NONATOMIC:
282 case OBJC_ASSOCIATION_RETAIN:
283 entry->object = [value retain];
285 case OBJC_ASSOCIATION_COPY_NONATOMIC:
286 case OBJC_ASSOCIATION_COPY:
287 entry->object = [value copy];
290 AssociationSpinLockUnlock(&AssociationLock);
292 // Do the cleaning outside of the lock since it might trigger more association playing
293 [objectToRelease release];
297 if (entry->nextEntry != nil) {
298 entry = entry->nextEntry;
300 } while (entry->nextEntry != nil);
302 newEntry = malloc(sizeof(AssociationObjectEntry));
303 newEntry->policy = policy;
306 case OBJC_ASSOCIATION_ASSIGN:
307 newEntry->object = value;
309 case OBJC_ASSOCIATION_RETAIN_NONATOMIC:
310 case OBJC_ASSOCIATION_RETAIN:
311 newEntry->object = [value retain];
313 case OBJC_ASSOCIATION_COPY_NONATOMIC:
314 case OBJC_ASSOCIATION_COPY:
315 newEntry->object = [value copy];
318 newEntry->nextEntry = NULL;
319 entry->nextEntry = newEntry;
322 AssociationSpinLockUnlock(&AssociationLock);
325 id objc_getAssociatedObject(id object, const void *key)
328 if (associationTable == NULL) {
332 AssociationSpinLockLock(&AssociationLock);
335 AssociationObjectEntry *objectTable = AssociationTableGet(associationTable, object);
336 if (objectTable == NULL) {
337 AssociationSpinLockUnlock(&AssociationLock);
341 uintptr_t index = HASHPTR(key) % AssociationObjectEntrySize;
342 AssociationObjectEntry *entry = ((AssociationObjectEntry *)objectTable) + index;
344 if (entry->key == key) {
347 entry = entry->nextEntry;
349 AssociationSpinLockUnlock(&AssociationLock);
351 return entry->object;