ObjFW  Check-in [18ae339de1]

Overview
Comment:runtime: Add support for associated objects
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 18ae339de1648d257408143f2607715d040295b643f470fa2e870336e8bda6e3
User & Date: js on 2024-02-03 14:41:14
Other Links: manifest | tags
References
2024-02-03
14:43 Fixed ticket [4b06796f49]: runtime: Add objc_setAssociatedObject() plus 5 other changes artifact: 9884ff5a3c user: js
Context
2024-02-03
15:50
objc_destructInstance: Remove associated objects check-in: 5f4d70c69f user: js tags: trunk
14:41
runtime: Add support for associated objects check-in: 18ae339de1 user: js tags: trunk
11:37
Minor documentation improvement check-in: e03c6b5d9d user: js tags: trunk
Changes

Modified src/runtime/Makefile from [af674fab60] to [692471a52e].

1
2
3
4
5
6
7
8
9
10
11
12
13

14
15
16
17
18
19
20
include ../../extra.mk

SUBDIRS = lookup-asm
DISTCLEAN = Info.plist

SHARED_LIB = ${OBJFWRT_SHARED_LIB}
STATIC_LIB = ${OBJFWRT_STATIC_LIB}
FRAMEWORK = ${OBJFWRT_FRAMEWORK}
LIB_MAJOR = ${OBJFWRT_LIB_MAJOR}
LIB_MINOR = ${OBJFWRT_LIB_MINOR}
LIB_PATCH = ${OBJFWRT_LIB_PATCH}

SRCS = arc.m			\

       autorelease.m		\
       category.m		\
       class.m			\
       dtable.m			\
       exception.m		\
       hashtable.m		\
       init.m			\













>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
include ../../extra.mk

SUBDIRS = lookup-asm
DISTCLEAN = Info.plist

SHARED_LIB = ${OBJFWRT_SHARED_LIB}
STATIC_LIB = ${OBJFWRT_STATIC_LIB}
FRAMEWORK = ${OBJFWRT_FRAMEWORK}
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		\
       hashtable.m		\
       init.m			\

Modified src/runtime/ObjFWRT.h from [5258b334d9] to [8884ec4c85].

169
170
171
172
173
174
175
















176
177
178
179
180
181
182
#ifdef __cplusplus
	Class _Nonnull class_;
#else
	Class _Nonnull class;
#endif
};

















#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Registers a selector with the specified name with the runtime.
 *







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#ifdef __cplusplus
	Class _Nonnull class_;
#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

/**
 * @brief Registers a selector with the specified name with the runtime.
 *
642
643
644
645
646
647
648































649
650
651
652
653
654
655
 *
 * @param class_ The tag ID for the tagged pointer class to use
 * @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);
































/*
 * 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.
 */
struct objc_module;







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
 *
 * @param class_ The tag ID for the tagged pointer class to use
 * @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.
 */
struct objc_module;

Added src/runtime/association.m version [b558229b1e].























































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * 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
}

Modified src/runtime/class.m from [99bd522eda] to [628f2b08c1].

599
600
601
602
603
604
605
606

607
608
609
610
611
612
613
		void *class;

		if (j >= count) {
			objc_globalMutex_unlock();
			return j;
		}

		if (classes->data[i] == NULL)

			continue;

		if (strcmp(classes->data[i]->key, "Protocol") == 0)
			continue;

		class = (Class)classes->data[i]->object;








|
>







599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
		void *class;

		if (j >= count) {
			objc_globalMutex_unlock();
			return j;
		}

		if (classes->data[i] == NULL ||
		    classes->data[i] == &objc_deletedBucket)
			continue;

		if (strcmp(classes->data[i]->key, "Protocol") == 0)
			continue;

		class = (Class)classes->data[i]->object;

Modified src/runtime/property.m from [eac1d5ea2f] to [20ebfada38].

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
static OFSpinlock spinlocks[numSpinlocks];

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!");
}
#endif







<

<







26
27
28
29
30
31
32

33

34
35
36
37
38
39
40
static OFSpinlock spinlocks[numSpinlocks];

static OF_INLINE size_t
spinlockSlot(const void *ptr)
{
	return ((size_t)((uintptr_t)ptr >> 4) & (numSpinlocks - 1));
}



OF_CONSTRUCTOR()
{
	for (size_t i = 0; i < numSpinlocks; i++)
		if (OFSpinlockNew(&spinlocks[i]) != 0)
			OBJC_ERROR("Failed to create spinlocks!");
}
#endif

Modified tests/RuntimeTests.m from [38c7914698] to [99fca0e28e].

14
15
16
17
18
19
20

21
22
23
24
25
26
27
 */

#include "config.h"

#import "TestsAppDelegate.h"

static OFString *const module = @"Runtime";


@interface OFObject (SuperTest)
- (id)superTest;
@end

@interface RuntimeTest: OFObject
{







>







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 */

#include "config.h"

#import "TestsAppDelegate.h"

static OFString *const module = @"Runtime";
static void *testKey = &testKey;

@interface OFObject (SuperTest)
- (id)superTest;
@end

@interface RuntimeTest: OFObject
{
84
85
86
87
88
89
90















91
92
93
94
95
96
97
	TEST(@"copy, nonatomic properties", [test.foo isEqual: foo] &&
	    test.foo != foo && test.foo.retainCount == 1)

	test.bar = string;
	TEST(@"retain, atomic properties",
	    test.bar == string && string.retainCount == 3)
















#ifdef OF_OBJFW_RUNTIME
	if (sizeof(uintptr_t) == 8)
		value = 0xDEADBEEFDEADBEF;
	else if (sizeof(uintptr_t) == 4)
		value = 0xDEADBEF;
	else
		abort();







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
	TEST(@"copy, nonatomic properties", [test.foo isEqual: foo] &&
	    test.foo != foo && test.foo.retainCount == 1)

	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;
	else
		abort();