GCDMulticastDelegate.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. #import "GCDMulticastDelegate.h"
  2. #import <libkern/OSAtomic.h>
  3. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  4. #import <AppKit/AppKit.h>
  5. #endif
  6. #if ! __has_feature(objc_arc)
  7. #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
  8. #endif
  9. /**
  10. * How does this class work?
  11. *
  12. * In theory, this class is very straight-forward.
  13. * It provides a way for multiple delegates to be called, each on its own delegate queue.
  14. *
  15. * In other words, any delegate method call to this class
  16. * will get forwarded (dispatch_async'd) to each added delegate.
  17. *
  18. * Important note concerning thread-safety:
  19. *
  20. * This class is designed to be used from within a single dispatch queue.
  21. * In other words, it is NOT thread-safe, and should only be used from within the external dedicated dispatch_queue.
  22. **/
  23. @interface GCDMulticastDelegateNode : NSObject {
  24. @private
  25. #if __has_feature(objc_arc_weak)
  26. __weak id delegate;
  27. #if !TARGET_OS_IPHONE
  28. __unsafe_unretained id unsafeDelegate; // Some classes don't support weak references yet (e.g. NSWindowController)
  29. #endif
  30. #else
  31. __unsafe_unretained id delegate;
  32. #endif
  33. dispatch_queue_t delegateQueue;
  34. }
  35. - (id)initWithDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
  36. #if __has_feature(objc_arc_weak)
  37. @property (/* atomic */ readwrite, weak) id delegate;
  38. #if !TARGET_OS_IPHONE
  39. @property (/* atomic */ readwrite, unsafe_unretained) id unsafeDelegate;
  40. #endif
  41. #else
  42. @property (/* atomic */ readwrite, unsafe_unretained) id delegate;
  43. #endif
  44. @property (nonatomic, readonly) dispatch_queue_t delegateQueue;
  45. @end
  46. @interface GCDMulticastDelegate ()
  47. {
  48. NSMutableArray *delegateNodes;
  49. }
  50. - (NSInvocation *)duplicateInvocation:(NSInvocation *)origInvocation;
  51. @end
  52. @interface GCDMulticastDelegateEnumerator ()
  53. {
  54. NSUInteger numNodes;
  55. NSUInteger currentNodeIndex;
  56. NSArray *delegateNodes;
  57. }
  58. - (id)initFromDelegateNodes:(NSMutableArray *)inDelegateNodes;
  59. @end
  60. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  61. #pragma mark -
  62. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  63. @implementation GCDMulticastDelegate
  64. - (id)init
  65. {
  66. if ((self = [super init]))
  67. {
  68. delegateNodes = [[NSMutableArray alloc] init];
  69. }
  70. return self;
  71. }
  72. - (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
  73. {
  74. if (delegate == nil) return;
  75. if (delegateQueue == NULL) return;
  76. GCDMulticastDelegateNode *node =
  77. [[GCDMulticastDelegateNode alloc] initWithDelegate:delegate delegateQueue:delegateQueue];
  78. [delegateNodes addObject:node];
  79. }
  80. - (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
  81. {
  82. if (delegate == nil) return;
  83. NSUInteger i;
  84. for (i = [delegateNodes count]; i > 0; i--)
  85. {
  86. GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:(i-1)];
  87. id nodeDelegate = node.delegate;
  88. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  89. if (nodeDelegate == [NSNull null])
  90. nodeDelegate = node.unsafeDelegate;
  91. #endif
  92. if (delegate == nodeDelegate)
  93. {
  94. if ((delegateQueue == NULL) || (delegateQueue == node.delegateQueue))
  95. {
  96. // Recall that this node may be retained by a GCDMulticastDelegateEnumerator.
  97. // The enumerator is a thread-safe snapshot of the delegate list at the moment it was created.
  98. // To properly remove this node from list, and from the list(s) of any enumerators,
  99. // we nullify the delegate via the atomic property.
  100. //
  101. // However, the delegateQueue is not modified.
  102. // The thread-safety is hinged on the atomic delegate property.
  103. // The delegateQueue is expected to properly exist until the node is deallocated.
  104. node.delegate = nil;
  105. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  106. node.unsafeDelegate = nil;
  107. #endif
  108. [delegateNodes removeObjectAtIndex:(i-1)];
  109. }
  110. }
  111. }
  112. }
  113. - (void)removeDelegate:(id)delegate
  114. {
  115. [self removeDelegate:delegate delegateQueue:NULL];
  116. }
  117. - (void)removeAllDelegates
  118. {
  119. for (GCDMulticastDelegateNode *node in delegateNodes)
  120. {
  121. node.delegate = nil;
  122. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  123. node.unsafeDelegate = nil;
  124. #endif
  125. }
  126. [delegateNodes removeAllObjects];
  127. }
  128. - (NSUInteger)count
  129. {
  130. return [delegateNodes count];
  131. }
  132. - (NSUInteger)countOfClass:(Class)aClass
  133. {
  134. NSUInteger count = 0;
  135. for (GCDMulticastDelegateNode *node in delegateNodes)
  136. {
  137. id nodeDelegate = node.delegate;
  138. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  139. if (nodeDelegate == [NSNull null])
  140. nodeDelegate = node.unsafeDelegate;
  141. #endif
  142. if ([nodeDelegate isKindOfClass:aClass])
  143. {
  144. count++;
  145. }
  146. }
  147. return count;
  148. }
  149. - (NSUInteger)countForSelector:(SEL)aSelector
  150. {
  151. NSUInteger count = 0;
  152. for (GCDMulticastDelegateNode *node in delegateNodes)
  153. {
  154. id nodeDelegate = node.delegate;
  155. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  156. if (nodeDelegate == [NSNull null])
  157. nodeDelegate = node.unsafeDelegate;
  158. #endif
  159. if ([nodeDelegate respondsToSelector:aSelector])
  160. {
  161. count++;
  162. }
  163. }
  164. return count;
  165. }
  166. - (BOOL)hasDelegateThatRespondsToSelector:(SEL)aSelector
  167. {
  168. for (GCDMulticastDelegateNode *node in delegateNodes)
  169. {
  170. id nodeDelegate = node.delegate;
  171. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  172. if (nodeDelegate == [NSNull null])
  173. nodeDelegate = node.unsafeDelegate;
  174. #endif
  175. if ([nodeDelegate respondsToSelector:aSelector])
  176. {
  177. return YES;
  178. }
  179. }
  180. return NO;
  181. }
  182. - (GCDMulticastDelegateEnumerator *)delegateEnumerator
  183. {
  184. return [[GCDMulticastDelegateEnumerator alloc] initFromDelegateNodes:delegateNodes];
  185. }
  186. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
  187. {
  188. for (GCDMulticastDelegateNode *node in delegateNodes)
  189. {
  190. id nodeDelegate = node.delegate;
  191. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  192. if (nodeDelegate == [NSNull null])
  193. nodeDelegate = node.unsafeDelegate;
  194. #endif
  195. NSMethodSignature *result = [nodeDelegate methodSignatureForSelector:aSelector];
  196. if (result != nil)
  197. {
  198. return result;
  199. }
  200. }
  201. // This causes a crash...
  202. // return [super methodSignatureForSelector:aSelector];
  203. // This also causes a crash...
  204. // return nil;
  205. return [[self class] instanceMethodSignatureForSelector:@selector(doNothing)];
  206. }
  207. - (void)forwardInvocation:(NSInvocation *)origInvocation
  208. {
  209. SEL selector = [origInvocation selector];
  210. BOOL foundNilDelegate = NO;
  211. for (GCDMulticastDelegateNode *node in delegateNodes)
  212. {
  213. id nodeDelegate = node.delegate;
  214. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  215. if (nodeDelegate == [NSNull null])
  216. nodeDelegate = node.unsafeDelegate;
  217. #endif
  218. if ([nodeDelegate respondsToSelector:selector])
  219. {
  220. // All delegates MUST be invoked ASYNCHRONOUSLY.
  221. NSInvocation *dupInvocation = [self duplicateInvocation:origInvocation];
  222. dispatch_async(node.delegateQueue, ^{ @autoreleasepool {
  223. [dupInvocation invokeWithTarget:nodeDelegate];
  224. }});
  225. }
  226. else if (nodeDelegate == nil)
  227. {
  228. foundNilDelegate = YES;
  229. }
  230. }
  231. if (foundNilDelegate)
  232. {
  233. // At lease one weak delegate reference disappeared.
  234. // Remove nil delegate nodes from the list.
  235. //
  236. // This is expected to happen very infrequently.
  237. // This is why we handle it separately (as it requires allocating an indexSet).
  238. NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
  239. NSUInteger i = 0;
  240. for (GCDMulticastDelegateNode *node in delegateNodes)
  241. {
  242. id nodeDelegate = node.delegate;
  243. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  244. if (nodeDelegate == [NSNull null])
  245. nodeDelegate = node.unsafeDelegate;
  246. #endif
  247. if (nodeDelegate == nil)
  248. {
  249. [indexSet addIndex:i];
  250. }
  251. i++;
  252. }
  253. [delegateNodes removeObjectsAtIndexes:indexSet];
  254. }
  255. }
  256. - (void)doesNotRecognizeSelector:(SEL)aSelector
  257. {
  258. // Prevent NSInvalidArgumentException
  259. }
  260. - (void)doNothing {}
  261. - (void)dealloc
  262. {
  263. [self removeAllDelegates];
  264. }
  265. - (NSInvocation *)duplicateInvocation:(NSInvocation *)origInvocation
  266. {
  267. NSMethodSignature *methodSignature = [origInvocation methodSignature];
  268. NSInvocation *dupInvocation = [NSInvocation invocationWithMethodSignature:methodSignature];
  269. [dupInvocation setSelector:[origInvocation selector]];
  270. NSUInteger i, count = [methodSignature numberOfArguments];
  271. for (i = 2; i < count; i++)
  272. {
  273. const char *type = [methodSignature getArgumentTypeAtIndex:i];
  274. if (*type == *@encode(BOOL))
  275. {
  276. BOOL value;
  277. [origInvocation getArgument:&value atIndex:i];
  278. [dupInvocation setArgument:&value atIndex:i];
  279. }
  280. else if (*type == *@encode(char) || *type == *@encode(unsigned char))
  281. {
  282. char value;
  283. [origInvocation getArgument:&value atIndex:i];
  284. [dupInvocation setArgument:&value atIndex:i];
  285. }
  286. else if (*type == *@encode(short) || *type == *@encode(unsigned short))
  287. {
  288. short value;
  289. [origInvocation getArgument:&value atIndex:i];
  290. [dupInvocation setArgument:&value atIndex:i];
  291. }
  292. else if (*type == *@encode(int) || *type == *@encode(unsigned int))
  293. {
  294. int value;
  295. [origInvocation getArgument:&value atIndex:i];
  296. [dupInvocation setArgument:&value atIndex:i];
  297. }
  298. else if (*type == *@encode(long) || *type == *@encode(unsigned long))
  299. {
  300. long value;
  301. [origInvocation getArgument:&value atIndex:i];
  302. [dupInvocation setArgument:&value atIndex:i];
  303. }
  304. else if (*type == *@encode(long long) || *type == *@encode(unsigned long long))
  305. {
  306. long long value;
  307. [origInvocation getArgument:&value atIndex:i];
  308. [dupInvocation setArgument:&value atIndex:i];
  309. }
  310. else if (*type == *@encode(double))
  311. {
  312. double value;
  313. [origInvocation getArgument:&value atIndex:i];
  314. [dupInvocation setArgument:&value atIndex:i];
  315. }
  316. else if (*type == *@encode(float))
  317. {
  318. float value;
  319. [origInvocation getArgument:&value atIndex:i];
  320. [dupInvocation setArgument:&value atIndex:i];
  321. }
  322. else if (*type == '@')
  323. {
  324. void *value;
  325. [origInvocation getArgument:&value atIndex:i];
  326. [dupInvocation setArgument:&value atIndex:i];
  327. }
  328. else if (*type == '^')
  329. {
  330. void *block;
  331. [origInvocation getArgument:&block atIndex:i];
  332. [dupInvocation setArgument:&block atIndex:i];
  333. }
  334. else
  335. {
  336. NSString *selectorStr = NSStringFromSelector([origInvocation selector]);
  337. NSString *format = @"Argument %lu to method %@ - Type(%c) not supported";
  338. NSString *reason = [NSString stringWithFormat:format, (unsigned long)(i - 2), selectorStr, *type];
  339. [[NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil] raise];
  340. }
  341. }
  342. [dupInvocation retainArguments];
  343. return dupInvocation;
  344. }
  345. @end
  346. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  347. #pragma mark -
  348. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  349. @implementation GCDMulticastDelegateNode
  350. @synthesize delegate; // atomic
  351. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  352. @synthesize unsafeDelegate; // atomic
  353. #endif
  354. @synthesize delegateQueue; // non-atomic
  355. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  356. static BOOL SupportsWeakReferences(id delegate)
  357. {
  358. // From Apple's documentation:
  359. //
  360. // > Which classes don’t support weak references?
  361. // >
  362. // > You cannot currently create weak references to instances of the following classes:
  363. // >
  364. // > NSATSTypesetter, NSColorSpace, NSFont, NSFontManager, NSFontPanel, NSImage, NSMenuView,
  365. // > NSParagraphStyle, NSSimpleHorizontalTypesetter, NSTableCellView, NSTextView, NSViewController,
  366. // > NSWindow, and NSWindowController.
  367. // >
  368. // > In addition, in OS X no classes in the AV Foundation framework support weak references.
  369. //
  370. // NSMenuView is deprecated (and not available to 64-bit applications).
  371. // NSSimpleHorizontalTypesetter is an internal class.
  372. if ([delegate isKindOfClass:[NSATSTypesetter class]]) return NO;
  373. if ([delegate isKindOfClass:[NSColorSpace class]]) return NO;
  374. if ([delegate isKindOfClass:[NSFont class]]) return NO;
  375. if ([delegate isKindOfClass:[NSFontManager class]]) return NO;
  376. if ([delegate isKindOfClass:[NSFontPanel class]]) return NO;
  377. if ([delegate isKindOfClass:[NSImage class]]) return NO;
  378. if ([delegate isKindOfClass:[NSParagraphStyle class]]) return NO;
  379. if ([delegate isKindOfClass:[NSTableCellView class]]) return NO;
  380. if ([delegate isKindOfClass:[NSTextView class]]) return NO;
  381. if ([delegate isKindOfClass:[NSViewController class]]) return NO;
  382. if ([delegate isKindOfClass:[NSWindow class]]) return NO;
  383. if ([delegate isKindOfClass:[NSWindowController class]]) return NO;
  384. return YES;
  385. }
  386. #endif
  387. - (id)initWithDelegate:(id)inDelegate delegateQueue:(dispatch_queue_t)inDelegateQueue
  388. {
  389. if ((self = [super init]))
  390. {
  391. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  392. {
  393. if (SupportsWeakReferences(inDelegate))
  394. {
  395. delegate = inDelegate;
  396. delegateQueue = inDelegateQueue;
  397. }
  398. else
  399. {
  400. delegate = [NSNull null];
  401. unsafeDelegate = inDelegate;
  402. delegateQueue = inDelegateQueue;
  403. }
  404. }
  405. #else
  406. {
  407. delegate = inDelegate;
  408. delegateQueue = inDelegateQueue;
  409. }
  410. #endif
  411. #if !OS_OBJECT_USE_OBJC
  412. if (delegateQueue)
  413. dispatch_retain(delegateQueue);
  414. #endif
  415. }
  416. return self;
  417. }
  418. - (void)dealloc
  419. {
  420. #if !OS_OBJECT_USE_OBJC
  421. if (delegateQueue)
  422. dispatch_release(delegateQueue);
  423. #endif
  424. }
  425. @end
  426. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  427. #pragma mark -
  428. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  429. @implementation GCDMulticastDelegateEnumerator
  430. - (id)initFromDelegateNodes:(NSMutableArray *)inDelegateNodes
  431. {
  432. if ((self = [super init]))
  433. {
  434. delegateNodes = [inDelegateNodes copy];
  435. numNodes = [delegateNodes count];
  436. currentNodeIndex = 0;
  437. }
  438. return self;
  439. }
  440. - (NSUInteger)count
  441. {
  442. return numNodes;
  443. }
  444. - (NSUInteger)countOfClass:(Class)aClass
  445. {
  446. NSUInteger count = 0;
  447. for (GCDMulticastDelegateNode *node in delegateNodes)
  448. {
  449. id nodeDelegate = node.delegate;
  450. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  451. if (nodeDelegate == [NSNull null])
  452. nodeDelegate = node.unsafeDelegate;
  453. #endif
  454. if ([nodeDelegate isKindOfClass:aClass])
  455. {
  456. count++;
  457. }
  458. }
  459. return count;
  460. }
  461. - (NSUInteger)countForSelector:(SEL)aSelector
  462. {
  463. NSUInteger count = 0;
  464. for (GCDMulticastDelegateNode *node in delegateNodes)
  465. {
  466. id nodeDelegate = node.delegate;
  467. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  468. if (nodeDelegate == [NSNull null])
  469. nodeDelegate = node.unsafeDelegate;
  470. #endif
  471. if ([nodeDelegate respondsToSelector:aSelector])
  472. {
  473. count++;
  474. }
  475. }
  476. return count;
  477. }
  478. - (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr
  479. {
  480. while (currentNodeIndex < numNodes)
  481. {
  482. GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:currentNodeIndex];
  483. currentNodeIndex++;
  484. id nodeDelegate = node.delegate; // snapshot atomic property
  485. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  486. if (nodeDelegate == [NSNull null])
  487. nodeDelegate = node.unsafeDelegate;
  488. #endif
  489. if (nodeDelegate)
  490. {
  491. if (delPtr) *delPtr = nodeDelegate;
  492. if (dqPtr) *dqPtr = node.delegateQueue;
  493. return YES;
  494. }
  495. }
  496. return NO;
  497. }
  498. - (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr ofClass:(Class)aClass
  499. {
  500. while (currentNodeIndex < numNodes)
  501. {
  502. GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:currentNodeIndex];
  503. currentNodeIndex++;
  504. id nodeDelegate = node.delegate; // snapshot atomic property
  505. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  506. if (nodeDelegate == [NSNull null])
  507. nodeDelegate = node.unsafeDelegate;
  508. #endif
  509. if ([nodeDelegate isKindOfClass:aClass])
  510. {
  511. if (delPtr) *delPtr = nodeDelegate;
  512. if (dqPtr) *dqPtr = node.delegateQueue;
  513. return YES;
  514. }
  515. }
  516. return NO;
  517. }
  518. - (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr forSelector:(SEL)aSelector
  519. {
  520. while (currentNodeIndex < numNodes)
  521. {
  522. GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:currentNodeIndex];
  523. currentNodeIndex++;
  524. id nodeDelegate = node.delegate; // snapshot atomic property
  525. #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
  526. if (nodeDelegate == [NSNull null])
  527. nodeDelegate = node.unsafeDelegate;
  528. #endif
  529. if ([nodeDelegate respondsToSelector:aSelector])
  530. {
  531. if (delPtr) *delPtr = nodeDelegate;
  532. if (dqPtr) *dqPtr = node.delegateQueue;
  533. return YES;
  534. }
  535. }
  536. return NO;
  537. }
  538. @end