RACCompoundDisposable.m 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. //
  2. // RACCompoundDisposable.m
  3. // ReactiveObjC
  4. //
  5. // Created by Josh Abernathy on 11/30/12.
  6. // Copyright (c) 2012 GitHub, Inc. All rights reserved.
  7. //
  8. #import "RACCompoundDisposable.h"
  9. #import "RACCompoundDisposableProvider.h"
  10. #import <pthread/pthread.h>
  11. // The number of child disposables for which space will be reserved directly in
  12. // `RACCompoundDisposable`.
  13. //
  14. // This number has been empirically determined to provide a good tradeoff
  15. // between performance, memory usage, and `RACCompoundDisposable` instance size
  16. // in a moderately complex GUI application.
  17. //
  18. // Profile any change!
  19. #define RACCompoundDisposableInlineCount 2
  20. static CFMutableArrayRef RACCreateDisposablesArray(void) {
  21. // Compare values using only pointer equality.
  22. CFArrayCallBacks callbacks = kCFTypeArrayCallBacks;
  23. callbacks.equal = NULL;
  24. return CFArrayCreateMutable(NULL, 0, &callbacks);
  25. }
  26. @interface RACCompoundDisposable () {
  27. // Used for synchronization.
  28. pthread_mutex_t _mutex;
  29. #if RACCompoundDisposableInlineCount
  30. // A fast array to the first N of the receiver's disposables.
  31. //
  32. // Once this is full, `_disposables` will be created and used for additional
  33. // disposables.
  34. //
  35. // This array should only be manipulated while _mutex is held.
  36. RACDisposable *_inlineDisposables[RACCompoundDisposableInlineCount];
  37. #endif
  38. // Contains the receiver's disposables.
  39. //
  40. // This array should only be manipulated while _mutex is held. If
  41. // `_disposed` is YES, this may be NULL.
  42. CFMutableArrayRef _disposables;
  43. // Whether the receiver has already been disposed.
  44. //
  45. // This ivar should only be accessed while _mutex is held.
  46. BOOL _disposed;
  47. }
  48. @end
  49. @implementation RACCompoundDisposable
  50. #pragma mark Properties
  51. - (BOOL)isDisposed {
  52. pthread_mutex_lock(&_mutex);
  53. BOOL disposed = _disposed;
  54. pthread_mutex_unlock(&_mutex);
  55. return disposed;
  56. }
  57. #pragma mark Lifecycle
  58. + (instancetype)compoundDisposable {
  59. return [[self alloc] initWithDisposables:nil];
  60. }
  61. + (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables {
  62. return [[self alloc] initWithDisposables:disposables];
  63. }
  64. - (instancetype)init {
  65. self = [super init];
  66. const int result __attribute__((unused)) = pthread_mutex_init(&_mutex, NULL);
  67. NSCAssert(0 == result, @"Failed to initialize mutex with error %d.", result);
  68. return self;
  69. }
  70. - (instancetype)initWithDisposables:(NSArray *)otherDisposables {
  71. self = [self init];
  72. #if RACCompoundDisposableInlineCount
  73. [otherDisposables enumerateObjectsUsingBlock:^(RACDisposable *disposable, NSUInteger index, BOOL *stop) {
  74. self->_inlineDisposables[index] = disposable;
  75. // Stop after this iteration if we've reached the end of the inlined
  76. // array.
  77. if (index == RACCompoundDisposableInlineCount - 1) *stop = YES;
  78. }];
  79. #endif
  80. if (otherDisposables.count > RACCompoundDisposableInlineCount) {
  81. _disposables = RACCreateDisposablesArray();
  82. CFRange range = CFRangeMake(RACCompoundDisposableInlineCount, (CFIndex)otherDisposables.count - RACCompoundDisposableInlineCount);
  83. CFArrayAppendArray(_disposables, (__bridge CFArrayRef)otherDisposables, range);
  84. }
  85. return self;
  86. }
  87. - (instancetype)initWithBlock:(void (^)(void))block {
  88. RACDisposable *disposable = [RACDisposable disposableWithBlock:block];
  89. return [self initWithDisposables:@[ disposable ]];
  90. }
  91. - (void)dealloc {
  92. #if RACCompoundDisposableInlineCount
  93. for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
  94. _inlineDisposables[i] = nil;
  95. }
  96. #endif
  97. if (_disposables != NULL) {
  98. CFRelease(_disposables);
  99. _disposables = NULL;
  100. }
  101. const int result __attribute__((unused)) = pthread_mutex_destroy(&_mutex);
  102. NSCAssert(0 == result, @"Failed to destroy mutex with error %d.", result);
  103. }
  104. #pragma mark Addition and Removal
  105. - (void)addDisposable:(RACDisposable *)disposable {
  106. NSCParameterAssert(disposable != self);
  107. if (disposable == nil || disposable.disposed) return;
  108. BOOL shouldDispose = NO;
  109. pthread_mutex_lock(&_mutex);
  110. {
  111. if (_disposed) {
  112. shouldDispose = YES;
  113. } else {
  114. #if RACCompoundDisposableInlineCount
  115. for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
  116. if (_inlineDisposables[i] == nil) {
  117. _inlineDisposables[i] = disposable;
  118. goto foundSlot;
  119. }
  120. }
  121. #endif
  122. if (_disposables == NULL) _disposables = RACCreateDisposablesArray();
  123. CFArrayAppendValue(_disposables, (__bridge void *)disposable);
  124. if (RACCOMPOUNDDISPOSABLE_ADDED_ENABLED()) {
  125. RACCOMPOUNDDISPOSABLE_ADDED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount);
  126. }
  127. #if RACCompoundDisposableInlineCount
  128. foundSlot:;
  129. #endif
  130. }
  131. }
  132. pthread_mutex_unlock(&_mutex);
  133. // Performed outside of the lock in case the compound disposable is used
  134. // recursively.
  135. if (shouldDispose) [disposable dispose];
  136. }
  137. - (void)removeDisposable:(RACDisposable *)disposable {
  138. if (disposable == nil) return;
  139. pthread_mutex_lock(&_mutex);
  140. {
  141. if (!_disposed) {
  142. #if RACCompoundDisposableInlineCount
  143. for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
  144. if (_inlineDisposables[i] == disposable) _inlineDisposables[i] = nil;
  145. }
  146. #endif
  147. if (_disposables != NULL) {
  148. CFIndex count = CFArrayGetCount(_disposables);
  149. for (CFIndex i = count - 1; i >= 0; i--) {
  150. const void *item = CFArrayGetValueAtIndex(_disposables, i);
  151. if (item == (__bridge void *)disposable) {
  152. CFArrayRemoveValueAtIndex(_disposables, i);
  153. }
  154. }
  155. if (RACCOMPOUNDDISPOSABLE_REMOVED_ENABLED()) {
  156. RACCOMPOUNDDISPOSABLE_REMOVED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount);
  157. }
  158. }
  159. }
  160. }
  161. pthread_mutex_unlock(&_mutex);
  162. }
  163. #pragma mark RACDisposable
  164. static void disposeEach(const void *value, void *context) {
  165. RACDisposable *disposable = (__bridge id)value;
  166. [disposable dispose];
  167. }
  168. - (void)dispose {
  169. #if RACCompoundDisposableInlineCount
  170. RACDisposable *inlineCopy[RACCompoundDisposableInlineCount];
  171. #endif
  172. CFArrayRef remainingDisposables = NULL;
  173. pthread_mutex_lock(&_mutex);
  174. {
  175. _disposed = YES;
  176. #if RACCompoundDisposableInlineCount
  177. for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
  178. inlineCopy[i] = _inlineDisposables[i];
  179. _inlineDisposables[i] = nil;
  180. }
  181. #endif
  182. remainingDisposables = _disposables;
  183. _disposables = NULL;
  184. }
  185. pthread_mutex_unlock(&_mutex);
  186. #if RACCompoundDisposableInlineCount
  187. // Dispose outside of the lock in case the compound disposable is used
  188. // recursively.
  189. for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
  190. [inlineCopy[i] dispose];
  191. }
  192. #endif
  193. if (remainingDisposables == NULL) return;
  194. CFIndex count = CFArrayGetCount(remainingDisposables);
  195. CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL);
  196. CFRelease(remainingDisposables);
  197. }
  198. @end