123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654 |
- #import "GCDMulticastDelegate.h"
- #import <libkern/OSAtomic.h>
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- #import <AppKit/AppKit.h>
- #endif
- #if ! __has_feature(objc_arc)
- #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
- #endif
- /**
- * How does this class work?
- *
- * In theory, this class is very straight-forward.
- * It provides a way for multiple delegates to be called, each on its own delegate queue.
- *
- * In other words, any delegate method call to this class
- * will get forwarded (dispatch_async'd) to each added delegate.
- *
- * Important note concerning thread-safety:
- *
- * This class is designed to be used from within a single dispatch queue.
- * In other words, it is NOT thread-safe, and should only be used from within the external dedicated dispatch_queue.
- **/
- @interface GCDMulticastDelegateNode : NSObject {
- @private
-
- #if __has_feature(objc_arc_weak)
- __weak id delegate;
- #if !TARGET_OS_IPHONE
- __unsafe_unretained id unsafeDelegate; // Some classes don't support weak references yet (e.g. NSWindowController)
- #endif
- #else
- __unsafe_unretained id delegate;
- #endif
-
- dispatch_queue_t delegateQueue;
- }
- - (id)initWithDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
- #if __has_feature(objc_arc_weak)
- @property (/* atomic */ readwrite, weak) id delegate;
- #if !TARGET_OS_IPHONE
- @property (/* atomic */ readwrite, unsafe_unretained) id unsafeDelegate;
- #endif
- #else
- @property (/* atomic */ readwrite, unsafe_unretained) id delegate;
- #endif
- @property (nonatomic, readonly) dispatch_queue_t delegateQueue;
- @end
- @interface GCDMulticastDelegate ()
- {
- NSMutableArray *delegateNodes;
- }
- - (NSInvocation *)duplicateInvocation:(NSInvocation *)origInvocation;
- @end
- @interface GCDMulticastDelegateEnumerator ()
- {
- NSUInteger numNodes;
- NSUInteger currentNodeIndex;
- NSArray *delegateNodes;
- }
- - (id)initFromDelegateNodes:(NSMutableArray *)inDelegateNodes;
- @end
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- @implementation GCDMulticastDelegate
- - (id)init
- {
- if ((self = [super init]))
- {
- delegateNodes = [[NSMutableArray alloc] init];
- }
- return self;
- }
- - (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
- {
- if (delegate == nil) return;
- if (delegateQueue == NULL) return;
-
- GCDMulticastDelegateNode *node =
- [[GCDMulticastDelegateNode alloc] initWithDelegate:delegate delegateQueue:delegateQueue];
-
- [delegateNodes addObject:node];
- }
- - (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
- {
- if (delegate == nil) return;
-
- NSUInteger i;
- for (i = [delegateNodes count]; i > 0; i--)
- {
- GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:(i-1)];
-
- id nodeDelegate = node.delegate;
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- if (nodeDelegate == [NSNull null])
- nodeDelegate = node.unsafeDelegate;
- #endif
-
- if (delegate == nodeDelegate)
- {
- if ((delegateQueue == NULL) || (delegateQueue == node.delegateQueue))
- {
- // Recall that this node may be retained by a GCDMulticastDelegateEnumerator.
- // The enumerator is a thread-safe snapshot of the delegate list at the moment it was created.
- // To properly remove this node from list, and from the list(s) of any enumerators,
- // we nullify the delegate via the atomic property.
- //
- // However, the delegateQueue is not modified.
- // The thread-safety is hinged on the atomic delegate property.
- // The delegateQueue is expected to properly exist until the node is deallocated.
-
- node.delegate = nil;
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- node.unsafeDelegate = nil;
- #endif
-
- [delegateNodes removeObjectAtIndex:(i-1)];
- }
- }
- }
- }
- - (void)removeDelegate:(id)delegate
- {
- [self removeDelegate:delegate delegateQueue:NULL];
- }
- - (void)removeAllDelegates
- {
- for (GCDMulticastDelegateNode *node in delegateNodes)
- {
- node.delegate = nil;
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- node.unsafeDelegate = nil;
- #endif
- }
-
- [delegateNodes removeAllObjects];
- }
- - (NSUInteger)count
- {
- return [delegateNodes count];
- }
- - (NSUInteger)countOfClass:(Class)aClass
- {
- NSUInteger count = 0;
-
- for (GCDMulticastDelegateNode *node in delegateNodes)
- {
- id nodeDelegate = node.delegate;
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- if (nodeDelegate == [NSNull null])
- nodeDelegate = node.unsafeDelegate;
- #endif
-
- if ([nodeDelegate isKindOfClass:aClass])
- {
- count++;
- }
- }
-
- return count;
- }
- - (NSUInteger)countForSelector:(SEL)aSelector
- {
- NSUInteger count = 0;
-
- for (GCDMulticastDelegateNode *node in delegateNodes)
- {
- id nodeDelegate = node.delegate;
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- if (nodeDelegate == [NSNull null])
- nodeDelegate = node.unsafeDelegate;
- #endif
-
- if ([nodeDelegate respondsToSelector:aSelector])
- {
- count++;
- }
- }
-
- return count;
- }
- - (BOOL)hasDelegateThatRespondsToSelector:(SEL)aSelector
- {
- for (GCDMulticastDelegateNode *node in delegateNodes)
- {
- id nodeDelegate = node.delegate;
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- if (nodeDelegate == [NSNull null])
- nodeDelegate = node.unsafeDelegate;
- #endif
-
- if ([nodeDelegate respondsToSelector:aSelector])
- {
- return YES;
- }
- }
-
- return NO;
- }
- - (GCDMulticastDelegateEnumerator *)delegateEnumerator
- {
- return [[GCDMulticastDelegateEnumerator alloc] initFromDelegateNodes:delegateNodes];
- }
- - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- {
- for (GCDMulticastDelegateNode *node in delegateNodes)
- {
- id nodeDelegate = node.delegate;
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- if (nodeDelegate == [NSNull null])
- nodeDelegate = node.unsafeDelegate;
- #endif
-
- NSMethodSignature *result = [nodeDelegate methodSignatureForSelector:aSelector];
-
- if (result != nil)
- {
- return result;
- }
- }
-
- // This causes a crash...
- // return [super methodSignatureForSelector:aSelector];
-
- // This also causes a crash...
- // return nil;
-
- return [[self class] instanceMethodSignatureForSelector:@selector(doNothing)];
- }
- - (void)forwardInvocation:(NSInvocation *)origInvocation
- {
- SEL selector = [origInvocation selector];
- BOOL foundNilDelegate = NO;
-
- for (GCDMulticastDelegateNode *node in delegateNodes)
- {
- id nodeDelegate = node.delegate;
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- if (nodeDelegate == [NSNull null])
- nodeDelegate = node.unsafeDelegate;
- #endif
-
- if ([nodeDelegate respondsToSelector:selector])
- {
- // All delegates MUST be invoked ASYNCHRONOUSLY.
-
- NSInvocation *dupInvocation = [self duplicateInvocation:origInvocation];
-
- dispatch_async(node.delegateQueue, ^{ @autoreleasepool {
-
- [dupInvocation invokeWithTarget:nodeDelegate];
-
- }});
- }
- else if (nodeDelegate == nil)
- {
- foundNilDelegate = YES;
- }
- }
-
- if (foundNilDelegate)
- {
- // At lease one weak delegate reference disappeared.
- // Remove nil delegate nodes from the list.
- //
- // This is expected to happen very infrequently.
- // This is why we handle it separately (as it requires allocating an indexSet).
-
- NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
-
- NSUInteger i = 0;
- for (GCDMulticastDelegateNode *node in delegateNodes)
- {
- id nodeDelegate = node.delegate;
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- if (nodeDelegate == [NSNull null])
- nodeDelegate = node.unsafeDelegate;
- #endif
-
- if (nodeDelegate == nil)
- {
- [indexSet addIndex:i];
- }
- i++;
- }
-
- [delegateNodes removeObjectsAtIndexes:indexSet];
- }
- }
- - (void)doesNotRecognizeSelector:(SEL)aSelector
- {
- // Prevent NSInvalidArgumentException
- }
- - (void)doNothing {}
- - (void)dealloc
- {
- [self removeAllDelegates];
- }
- - (NSInvocation *)duplicateInvocation:(NSInvocation *)origInvocation
- {
- NSMethodSignature *methodSignature = [origInvocation methodSignature];
-
- NSInvocation *dupInvocation = [NSInvocation invocationWithMethodSignature:methodSignature];
- [dupInvocation setSelector:[origInvocation selector]];
-
- NSUInteger i, count = [methodSignature numberOfArguments];
- for (i = 2; i < count; i++)
- {
- const char *type = [methodSignature getArgumentTypeAtIndex:i];
-
- if (*type == *@encode(BOOL))
- {
- BOOL value;
- [origInvocation getArgument:&value atIndex:i];
- [dupInvocation setArgument:&value atIndex:i];
- }
- else if (*type == *@encode(char) || *type == *@encode(unsigned char))
- {
- char value;
- [origInvocation getArgument:&value atIndex:i];
- [dupInvocation setArgument:&value atIndex:i];
- }
- else if (*type == *@encode(short) || *type == *@encode(unsigned short))
- {
- short value;
- [origInvocation getArgument:&value atIndex:i];
- [dupInvocation setArgument:&value atIndex:i];
- }
- else if (*type == *@encode(int) || *type == *@encode(unsigned int))
- {
- int value;
- [origInvocation getArgument:&value atIndex:i];
- [dupInvocation setArgument:&value atIndex:i];
- }
- else if (*type == *@encode(long) || *type == *@encode(unsigned long))
- {
- long value;
- [origInvocation getArgument:&value atIndex:i];
- [dupInvocation setArgument:&value atIndex:i];
- }
- else if (*type == *@encode(long long) || *type == *@encode(unsigned long long))
- {
- long long value;
- [origInvocation getArgument:&value atIndex:i];
- [dupInvocation setArgument:&value atIndex:i];
- }
- else if (*type == *@encode(double))
- {
- double value;
- [origInvocation getArgument:&value atIndex:i];
- [dupInvocation setArgument:&value atIndex:i];
- }
- else if (*type == *@encode(float))
- {
- float value;
- [origInvocation getArgument:&value atIndex:i];
- [dupInvocation setArgument:&value atIndex:i];
- }
- else if (*type == '@')
- {
- void *value;
- [origInvocation getArgument:&value atIndex:i];
- [dupInvocation setArgument:&value atIndex:i];
- }
- else if (*type == '^')
- {
- void *block;
- [origInvocation getArgument:&block atIndex:i];
- [dupInvocation setArgument:&block atIndex:i];
- }
- else
- {
- NSString *selectorStr = NSStringFromSelector([origInvocation selector]);
-
- NSString *format = @"Argument %lu to method %@ - Type(%c) not supported";
- NSString *reason = [NSString stringWithFormat:format, (unsigned long)(i - 2), selectorStr, *type];
-
- [[NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil] raise];
- }
- }
-
- [dupInvocation retainArguments];
-
- return dupInvocation;
- }
- @end
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- @implementation GCDMulticastDelegateNode
- @synthesize delegate; // atomic
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- @synthesize unsafeDelegate; // atomic
- #endif
- @synthesize delegateQueue; // non-atomic
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- static BOOL SupportsWeakReferences(id delegate)
- {
- // From Apple's documentation:
- //
- // > Which classes don’t support weak references?
- // >
- // > You cannot currently create weak references to instances of the following classes:
- // >
- // > NSATSTypesetter, NSColorSpace, NSFont, NSFontManager, NSFontPanel, NSImage, NSMenuView,
- // > NSParagraphStyle, NSSimpleHorizontalTypesetter, NSTableCellView, NSTextView, NSViewController,
- // > NSWindow, and NSWindowController.
- // >
- // > In addition, in OS X no classes in the AV Foundation framework support weak references.
- //
- // NSMenuView is deprecated (and not available to 64-bit applications).
- // NSSimpleHorizontalTypesetter is an internal class.
-
- if ([delegate isKindOfClass:[NSATSTypesetter class]]) return NO;
- if ([delegate isKindOfClass:[NSColorSpace class]]) return NO;
- if ([delegate isKindOfClass:[NSFont class]]) return NO;
- if ([delegate isKindOfClass:[NSFontManager class]]) return NO;
- if ([delegate isKindOfClass:[NSFontPanel class]]) return NO;
- if ([delegate isKindOfClass:[NSImage class]]) return NO;
- if ([delegate isKindOfClass:[NSParagraphStyle class]]) return NO;
- if ([delegate isKindOfClass:[NSTableCellView class]]) return NO;
- if ([delegate isKindOfClass:[NSTextView class]]) return NO;
- if ([delegate isKindOfClass:[NSViewController class]]) return NO;
- if ([delegate isKindOfClass:[NSWindow class]]) return NO;
- if ([delegate isKindOfClass:[NSWindowController class]]) return NO;
-
- return YES;
- }
- #endif
- - (id)initWithDelegate:(id)inDelegate delegateQueue:(dispatch_queue_t)inDelegateQueue
- {
- if ((self = [super init]))
- {
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- {
- if (SupportsWeakReferences(inDelegate))
- {
- delegate = inDelegate;
- delegateQueue = inDelegateQueue;
- }
- else
- {
- delegate = [NSNull null];
-
- unsafeDelegate = inDelegate;
- delegateQueue = inDelegateQueue;
- }
- }
- #else
- {
- delegate = inDelegate;
- delegateQueue = inDelegateQueue;
- }
- #endif
-
- #if !OS_OBJECT_USE_OBJC
- if (delegateQueue)
- dispatch_retain(delegateQueue);
- #endif
- }
- return self;
- }
- - (void)dealloc
- {
- #if !OS_OBJECT_USE_OBJC
- if (delegateQueue)
- dispatch_release(delegateQueue);
- #endif
- }
- @end
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- @implementation GCDMulticastDelegateEnumerator
- - (id)initFromDelegateNodes:(NSMutableArray *)inDelegateNodes
- {
- if ((self = [super init]))
- {
- delegateNodes = [inDelegateNodes copy];
-
- numNodes = [delegateNodes count];
- currentNodeIndex = 0;
- }
- return self;
- }
- - (NSUInteger)count
- {
- return numNodes;
- }
- - (NSUInteger)countOfClass:(Class)aClass
- {
- NSUInteger count = 0;
-
- for (GCDMulticastDelegateNode *node in delegateNodes)
- {
- id nodeDelegate = node.delegate;
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- if (nodeDelegate == [NSNull null])
- nodeDelegate = node.unsafeDelegate;
- #endif
-
- if ([nodeDelegate isKindOfClass:aClass])
- {
- count++;
- }
- }
-
- return count;
- }
- - (NSUInteger)countForSelector:(SEL)aSelector
- {
- NSUInteger count = 0;
-
- for (GCDMulticastDelegateNode *node in delegateNodes)
- {
- id nodeDelegate = node.delegate;
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- if (nodeDelegate == [NSNull null])
- nodeDelegate = node.unsafeDelegate;
- #endif
-
- if ([nodeDelegate respondsToSelector:aSelector])
- {
- count++;
- }
- }
-
- return count;
- }
- - (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr
- {
- while (currentNodeIndex < numNodes)
- {
- GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:currentNodeIndex];
- currentNodeIndex++;
-
- id nodeDelegate = node.delegate; // snapshot atomic property
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- if (nodeDelegate == [NSNull null])
- nodeDelegate = node.unsafeDelegate;
- #endif
-
- if (nodeDelegate)
- {
- if (delPtr) *delPtr = nodeDelegate;
- if (dqPtr) *dqPtr = node.delegateQueue;
-
- return YES;
- }
- }
-
- return NO;
- }
- - (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr ofClass:(Class)aClass
- {
- while (currentNodeIndex < numNodes)
- {
- GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:currentNodeIndex];
- currentNodeIndex++;
-
- id nodeDelegate = node.delegate; // snapshot atomic property
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- if (nodeDelegate == [NSNull null])
- nodeDelegate = node.unsafeDelegate;
- #endif
-
- if ([nodeDelegate isKindOfClass:aClass])
- {
- if (delPtr) *delPtr = nodeDelegate;
- if (dqPtr) *dqPtr = node.delegateQueue;
-
- return YES;
- }
- }
-
- return NO;
- }
- - (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr forSelector:(SEL)aSelector
- {
- while (currentNodeIndex < numNodes)
- {
- GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:currentNodeIndex];
- currentNodeIndex++;
-
- id nodeDelegate = node.delegate; // snapshot atomic property
- #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
- if (nodeDelegate == [NSNull null])
- nodeDelegate = node.unsafeDelegate;
- #endif
-
- if ([nodeDelegate respondsToSelector:aSelector])
- {
- if (delPtr) *delPtr = nodeDelegate;
- if (dqPtr) *dqPtr = node.delegateQueue;
-
- return YES;
- }
- }
-
- return NO;
- }
- @end
|