ObjFW  Check-in [108d360116]

Overview
Comment:OFNotificationCenter: Add support for blocks
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | notifications
Files: files | file ages | folders
SHA3-256: 108d3601163bd5a3a9140c4f5820491dd00ecc7fd476d91f207f7051d1fc91d0
User & Date: js on 2021-10-31 20:14:44
Other Links: branch diff | manifest | tags
Context
2021-10-31
20:24
OFNotificationCenter: Add convenience methods check-in: fed7fa621e user: js tags: notifications
20:14
OFNotificationCenter: Add support for blocks check-in: 108d360116 user: js tags: notifications
18:02
OFNotificationCenter: Move posting out of the lock check-in: 93851ccd0f user: js tags: notifications
Changes

Modified src/OFNotificationCenter.h from [7a53480f1b] to [f094627460].

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
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







+
+
+
+
+
+
+
+
+
+












-
+








OF_ASSUME_NONNULL_BEGIN

@class OFMutableDictionary OF_GENERIC(KeyType, ObjectType);
#ifdef OF_HAVE_THREADS
@class OFMutex;
#endif
@class OFNotificationCenterHandle;

#ifdef OF_HAVE_BLOCKS
/**
 * @brief A block which is called when a notification has been posted.
 *
 * @param notification The notification that has been posted
 */
typedef void (^OFNotificationCenterBlock)(OFNotification *notification);
#endif

/**
 * @class OFNotificationCenter OFNotificationCenter.h \
 *	  ObjFW/OFNotificationCenter.h
 *
 * @brief A class to send and register for notifications.
 */
@interface OFNotificationCenter: OFObject
{
#ifdef OF_HAVE_THREADS
	OFMutex *_mutex;
#endif
	OFMutableDictionary *_notifications;
	OFMutableDictionary *_handles;
	OF_RESERVE_IVARS(OFNotificationCenter, 4)
}

#ifdef OF_HAVE_CLASS_PROPERTIES
@property (class, readonly, nonatomic) OFNotificationCenter *defaultCenter;
#endif

73
74
75
76
77
78
79



























80
81
82
83
84
85
86
87
88
89
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+










 * @param name The name that was specified when adding the observer
 * @param object The object that was specified when adding the observer
 */
- (void)removeObserver: (id)observer
	      selector: (SEL)selector
		  name: (OFNotificationName)name
		object: (nullable id)object;

#ifdef OF_HAVE_BLOCKS
/**
 * @brief Adds an observer for the specified notification and object.
 *
 * To remove the observer again, use @ref removeObserver:.
 *
 * @param name The name of the notification to observe
 * @param object The object that should be sending the notification, or `nil`
 *		 if the object should be ignored to determine what
 *		 notifications to deliver
 * @param block The block to handle notifications
 * @return An opaque object to remove the observer again
 */
- (OFNotificationCenterHandle *)
    addObserverForName: (OFNotificationName)name
		object: (nullable id)object
	    usingBlock: (OFNotificationCenterBlock)block;

/**
 * @brief Removes an observer. The specified observer must be one returned by
 *	  @ref addObserver:selector:name:object:.
 *
 * @param observer The object that was returned when adding the observer
 */
- (void)removeObserver: (OFNotificationCenterHandle *)observer;
#endif

/**
 * @brief Posts the specified notification.
 *
 * @param notification The notification to post
 */
- (void)postNotification: (OFNotification *)notification;
@end

OF_ASSUME_NONNULL_END

Modified src/OFNotificationCenter.m from [7d4ea0e843] to [ffe3d96692].

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
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







+
+




-
+


+



+
+
+



-
-
-
+
+
+
+
+
+
+
+
+




-
-
-
-
+
+
+
+
+






+















+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



+

+
+
+





-
+

-
+


+
+
+
+
-
+


+
-
+


+
+
+
+
+
-
+











+


+
+
+
+







#import "OFArray.h"
#import "OFDictionary.h"
#ifdef OF_HAVE_THREADS
# import "OFMutex.h"
#endif
#import "OFSet.h"
#import "OFString.h"

#import "OFInvalidArgumentException.h"

@interface OFDefaultNotificationCenter: OFNotificationCenter
@end

@interface OFNotificationHandler: OFObject
@interface OFNotificationCenterHandle: OFObject
{
@public
	OFNotificationName _name;
	id _observer;
	SEL _selector;
	unsigned long _selectorHash;
#ifdef OF_HAVE_BLOCKS
	OFNotificationCenterBlock _block;
#endif
	id _object;
}

- (instancetype)initWithObserver: (id)observer
			selector: (SEL)selector
			  object: (id)object;
- (instancetype)initWithName: (OFNotificationName)name
		    observer: (id)observer
		    selector: (SEL)selector
		      object: (id)object;
#ifdef OF_HAVE_BLOCKS
- (instancetype)initWithName: (OFNotificationName)name
		      object: (id)object
		       block: (OFNotificationCenterBlock)block;
#endif
@end

static OFNotificationCenter *defaultCenter;

@implementation OFNotificationHandler
- (instancetype)initWithObserver: (id)observer
			selector: (SEL)selector
			  object: (id)object
@implementation OFNotificationCenterHandle
- (instancetype)initWithName: (OFNotificationName)name
		    observer: (id)observer
		    selector: (SEL)selector
		      object: (id)object
{
	self = [super init];

	@try {
		void *pool = objc_autoreleasePoolPush();

		_name = [name copy];
		_observer = [observer retain];
		_selector = selector;
		_object = [object retain];

		_selectorHash = [[OFString stringWithUTF8String:
		    sel_getName(_selector)] hash];

		objc_autoreleasePoolPop(pool);
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

#ifdef OF_HAVE_BLOCKS
- (instancetype)initWithName: (OFNotificationName)name
		      object: (id)object
		       block: (OFNotificationCenterBlock)block
{
	self = [super init];

	@try {
		_name = [name copy];
		_object = [object retain];
		_block = [block copy];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}
#endif

- (void)dealloc
{
	[_name release];
	[_observer release];
#ifdef OF_HAVE_BLOCKS
	[_block release];
#endif
	[_object release];

	[super dealloc];
}

- (bool)isEqual: (OFNotificationHandler *)handler
- (bool)isEqual: (OFNotificationCenterHandle *)handle
{
	if (![handler isKindOfClass: [OFNotificationHandler class]])
	if (![handle isKindOfClass: [OFNotificationCenterHandle class]])
		return false;

	if (![handle->_name isEqual: _name])
		return false;

	if (handle->_observer != _observer &&
	if (![handler->_observer isEqual: _observer])
	    ![handle->_observer isEqual: _observer])
		return false;

	if (handle->_selector != _selector &&
	if (!sel_isEqual(handler->_selector, _selector))
	    !sel_isEqual(handle->_selector, _selector))
		return false;

#ifdef OF_HAVE_BLOCKS
	if (handle->_block != _block)
		return false;
#endif

	if (handler->_object != _object && ![handler->_object isEqual: _object])
	if (handle->_object != _object && ![handle->_object isEqual: _object])
		return false;

	return true;
}

- (unsigned long)hash
{
	unsigned long hash;

	OFHashInit(&hash);

	OFHashAddHash(&hash, _name.hash);
	OFHashAddHash(&hash, [_observer hash]);
	OFHashAddHash(&hash, _selectorHash);
#ifdef OF_HAVE_BLOCKS
	if (_block != NULL)
		OFHashAddHash(&hash, (unsigned long)(uintptr_t)_block);
#endif
	OFHashAddHash(&hash, [_object hash]);

	OFHashFinalize(&hash);

	return hash;
}
@end
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
220
221
222
223
224
225
226





227
228
229
230
231
232
233
234
235
236










237

238




239
240
241
242
243
244
245
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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235




236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278


279
280
281

282






283
284
285
286
287
288
289
290


291
292
293
294
295
296
297
298





299
300
301
302
303
304
305










306
307
308
309
310
311
312

313
314
315
316
317
318





319
320
321
322
323
324
325
326
327
328
329




330
331
332
333
334
335
336
337
338
339
340
341

342
343
344
345
346
347
348
349
350
351
352







-
+













-
+




+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+






-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





-
-
+
+

-
+
-
-
-
-
-
-
+
+
+





-
-








-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-







-
+





-
-
-
-
-
+
+
+
+
+






-
-
-
-
+
+
+
+
+
+
+
+
+
+

+
-
+
+
+
+







{
	self = [super init];

	@try {
#ifdef OF_HAVE_THREADS
		_mutex = [[OFMutex alloc] init];
#endif
		_notifications = [[OFMutableDictionary alloc] init];
		_handles = [[OFMutableDictionary alloc] init];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (void)dealloc
{
#ifdef OF_HAVE_THREADS
	[_mutex release];
#endif
	[_notifications release];
	[_handles release];

	[super dealloc];
}

- (void)of_addObserver: (OFNotificationCenterHandle *)handle
{
#ifdef OF_HAVE_THREADS
	[_mutex lock];
	@try {
#endif
		OFMutableSet *handlesForName =
		    [_handles objectForKey: handle->_name];

		if (handlesForName == nil) {
			handlesForName = [OFMutableSet set];
			[_handles setObject: handlesForName
				     forKey: handle->_name];
		}

		[handlesForName addObject: handle];
#ifdef OF_HAVE_THREADS
	} @finally {
		[_mutex unlock];
	}
#endif
}

- (void)addObserver: (id)observer
	   selector: (SEL)selector
	       name: (OFNotificationName)name
	     object: (id)object
{
	void *pool = objc_autoreleasePoolPush();
	OFNotificationHandler *handler = [[[OFNotificationHandler alloc]
	    initWithObserver: observer
		    selector: selector
		      object: object] autorelease];

	[self of_addObserver:
	    [[[OFNotificationCenterHandle alloc] initWithName: name
						     observer: observer
						     selector: selector
						       object: object]
	    autorelease]];

	objc_autoreleasePoolPop(pool);
}

#ifdef OF_HAVE_BLOCKS
- (OFNotificationCenterHandle *)
    addObserverForName: (OFNotificationName)name
		object: (id)object
	    usingBlock: (OFNotificationCenterBlock)block
{
	void *pool = objc_autoreleasePoolPush();
	OFNotificationCenterHandle *handle =
	    [[[OFNotificationCenterHandle alloc] initWithName: name
						       object: object
							block: block]
	    autorelease];

	[self of_addObserver: handle];

	[handle retain];

	objc_autoreleasePoolPop(pool);

	return [handle autorelease];
}
#endif

- (void)removeObserver: (OFNotificationCenterHandle *)handle
{
	if (![handle isKindOfClass: [OFNotificationCenterHandle class]])
		@throw [OFInvalidArgumentException exception];

#ifdef OF_HAVE_THREADS
	[_mutex lock];
	@try {
#endif
		OFMutableSet *notificationsForName =
		    [_notifications objectForKey: name];
		OFNotificationName name = [[handle->_name copy] autorelease];
		OFMutableSet *handlesForName = [_handles objectForKey: name];

		if (notificationsForName == nil) {
		[handlesForName removeObject: handle];
			notificationsForName = [OFMutableSet set];
			[_notifications setObject: notificationsForName
					   forKey: name];
		}

		[notificationsForName addObject: handler];

		if (handlesForName.count == 0)
			[_handles removeObjectForKey: name];
#ifdef OF_HAVE_THREADS
	} @finally {
		[_mutex unlock];
	}
#endif

	objc_autoreleasePoolPop(pool);
}

- (void)removeObserver: (id)observer
	      selector: (SEL)selector
		  name: (OFNotificationName)name
		object: (id)object
{
	void *pool = objc_autoreleasePoolPush();
	OFNotificationHandler *handler = [[[OFNotificationHandler alloc]
	    initWithObserver: observer
		    selector: selector
		      object: object] autorelease];


	[self removeObserver:
	    [[[OFNotificationCenterHandle alloc] initWithName: name
						     observer: observer
						     selector: selector
						       object: object]
	    autorelease]];
#ifdef OF_HAVE_THREADS
	[_mutex lock];
	@try {
#endif
		[[_notifications objectForKey: name] removeObject: handler];
#ifdef OF_HAVE_THREADS
	} @finally {
		[_mutex unlock];
	}
#endif

	objc_autoreleasePoolPop(pool);
}

- (void)postNotification: (OFNotification *)notification
{
	void *pool = objc_autoreleasePoolPush();
	OFMutableArray *matchedHandlers = [OFMutableArray array];
	OFMutableArray *matchedHandles = [OFMutableArray array];

#ifdef OF_HAVE_THREADS
	[_mutex lock];
	@try {
#endif
		for (OFNotificationHandler *handler in
		    [_notifications objectForKey: notification.name])
			if (handler->_object == nil ||
			    handler->_object == notification.object)
				[matchedHandlers addObject: handler];
		for (OFNotificationCenterHandle *handle in
		    [_handles objectForKey: notification.name])
			if (handle->_object == nil ||
			    handle->_object == notification.object)
				[matchedHandles addObject: handle];
#ifdef OF_HAVE_THREADS
	} @finally {
		[_mutex unlock];
	}
#endif

	for (OFNotificationHandler *handler in matchedHandlers) {
		void (*callback)(id, SEL, OFNotification *) =
		    (void (*)(id, SEL, OFNotification *))
		    [handler->_observer methodForSelector: handler->_selector];
	for (OFNotificationCenterHandle *handle in matchedHandles) {
#ifdef OF_HAVE_BLOCKS
		if (handle->_block != NULL)
			handle->_block(notification);
		else {
#endif
			void (*callback)(id, SEL, OFNotification *) =
			    (void (*)(id, SEL, OFNotification *))
			    [handle->_observer methodForSelector:
			    handle->_selector];

			callback(handle->_observer, handle->_selector,
		callback(handler->_observer, handler->_selector, notification);
			    notification);
#ifdef OF_HAVE_BLOCKS
		}
#endif
	}

	objc_autoreleasePoolPop(pool);
}
@end

@implementation OFDefaultNotificationCenter

Modified tests/OFNotificationCenterTests.m from [1ce580811f] to [96bdb8154b].

102
103
104
105
106
107
108






















109
110
111
112
113
114
115
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








	notification = [OFNotification notificationWithName: notificationName
						     object: @"foo"];
	TEST(@"-[postNotification:] #3",
	    R([center postNotification: notification]) &&
	    test1->_received == 1 && test2->_received == 3 &&
	    test3->_received == 0 && test4->_received == 0)

#ifdef OF_HAVE_BLOCKS
	__block bool received = false;
	OFNotificationCenterHandle *handle;

	notification = [OFNotification notificationWithName: notificationName
						     object: self];
	TEST(@"-[addObserverForName:object:usingBlock:]",
	    (handle = [center addObserverForName: notificationName
					  object: self
				      usingBlock: ^ (OFNotification *notif) {
		OFEnsure(notif == notification && !received);
		received = true;
	    }]) && R([center postNotification: notification]) && received &&
	    test1->_received == 2 && test2->_received == 4 &&
	    test3->_received == 0 && test4->_received == 0)

	/* Act like the blocks test didn't happen. */
	[center removeObserver: handle];
	test1->_received--;
	test2->_received--;
#endif

	TEST(@"-[removeObserver:selector:name:object:]",
	    R([center removeObserver: test1
			    selector: @selector(handleNotification:)
				name: notificationName
			      object: self]) &&
	    R([center removeObserver: test2