Index: src/OFNotificationCenter.h ================================================================== --- src/OFNotificationCenter.h +++ src/OFNotificationCenter.h @@ -20,10 +20,20 @@ @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 * @@ -32,11 +42,11 @@ @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; @@ -75,10 +85,37 @@ */ - (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 Index: src/OFNotificationCenter.m ================================================================== --- src/OFNotificationCenter.m +++ src/OFNotificationCenter.m @@ -21,40 +21,54 @@ #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: @@ -66,31 +80,65 @@ @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 -{ - if (![handler isKindOfClass: [OFNotificationHandler class]]) - return false; - - if (![handler->_observer isEqual: _observer]) - return false; - - if (!sel_isEqual(handler->_selector, _selector)) - return false; - - if (handler->_object != _object && ![handler->_object isEqual: _object]) +- (bool)isEqual: (OFNotificationCenterHandle *)handle +{ + if (![handle isKindOfClass: [OFNotificationCenterHandle class]]) + return false; + + if (![handle->_name isEqual: _name]) + return false; + + if (handle->_observer != _observer && + ![handle->_observer isEqual: _observer]) + return false; + + if (handle->_selector != _selector && + !sel_isEqual(handle->_selector, _selector)) + return false; + +#ifdef OF_HAVE_BLOCKS + if (handle->_block != _block) + return false; +#endif + + if (handle->_object != _object && ![handle->_object isEqual: _object]) return false; return true; } @@ -98,12 +146,17 @@ { 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; @@ -130,11 +183,11 @@ @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; } @@ -144,100 +197,154 @@ - (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]; - - if (notificationsForName == nil) { - notificationsForName = [OFMutableSet set]; - [_notifications setObject: notificationsForName - forKey: name]; - } - - [notificationsForName addObject: handler]; + OFNotificationName name = [[handle->_name copy] autorelease]; + OFMutableSet *handlesForName = [_handles objectForKey: name]; + + [handlesForName removeObject: handle]; + + 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]; - -#ifdef OF_HAVE_THREADS - [_mutex lock]; - @try { -#endif - [[_notifications objectForKey: name] removeObject: handler]; -#ifdef OF_HAVE_THREADS - } @finally { - [_mutex unlock]; - } -#endif + + [self removeObserver: + [[[OFNotificationCenterHandle alloc] initWithName: name + observer: observer + selector: selector + object: object] + autorelease]]; 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(handler->_observer, handler->_selector, notification); + callback(handle->_observer, handle->_selector, + notification); +#ifdef OF_HAVE_BLOCKS + } +#endif } objc_autoreleasePoolPop(pool); } @end Index: tests/OFNotificationCenterTests.m ================================================================== --- tests/OFNotificationCenterTests.m +++ tests/OFNotificationCenterTests.m @@ -104,10 +104,32 @@ 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