EMMulticastDelegate.m 19 KB

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