RACCompoundDisposable.m 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. //
  2. // RACCompoundDisposable.m
  3. // ReactiveCocoa
  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. if (self == nil) return nil;
  67. const int result = pthread_mutex_init(&_mutex, NULL);
  68. NSCAssert(0 == result, @"Failed to initialize mutex with error %d.", result);
  69. return self;
  70. }
  71. - (instancetype)initWithDisposables:(NSArray *)otherDisposables {
  72. self = [self init];
  73. if (self == nil) return nil;
  74. #if RACCompoundDisposableInlineCount
  75. [otherDisposables enumerateObjectsUsingBlock:^(RACDisposable *disposable, NSUInteger index, BOOL *stop) {
  76. _inlineDisposables[index] = disposable;
  77. // Stop after this iteration if we've reached the end of the inlined
  78. // array.
  79. if (index == RACCompoundDisposableInlineCount - 1) *stop = YES;
  80. }];
  81. #endif
  82. if (otherDisposables.count > RACCompoundDisposableInlineCount) {
  83. _disposables = RACCreateDisposablesArray();
  84. CFRange range = CFRangeMake(RACCompoundDisposableInlineCount, (CFIndex)otherDisposables.count - RACCompoundDisposableInlineCount);
  85. CFArrayAppendArray(_disposables, (__bridge CFArrayRef)otherDisposables, range);
  86. }
  87. return self;
  88. }
  89. - (instancetype)initWithBlock:(void (^)(void))block {
  90. RACDisposable *disposable = [RACDisposable disposableWithBlock:block];
  91. return [self initWithDisposables:@[ disposable ]];
  92. }
  93. - (void)dealloc {
  94. #if RACCompoundDisposableInlineCount
  95. for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
  96. _inlineDisposables[i] = nil;
  97. }
  98. #endif
  99. if (_disposables != NULL) {
  100. CFRelease(_disposables);
  101. _disposables = NULL;
  102. }
  103. const int result = pthread_mutex_destroy(&_mutex);
  104. NSCAssert(0 == result, @"Failed to destroy mutex with error %d.", result);
  105. }
  106. #pragma mark Addition and Removal
  107. - (void)addDisposable:(RACDisposable *)disposable {
  108. NSCParameterAssert(disposable != self);
  109. if (disposable == nil || disposable.disposed) return;
  110. BOOL shouldDispose = NO;
  111. pthread_mutex_lock(&_mutex);
  112. {
  113. if (_disposed) {
  114. shouldDispose = YES;
  115. } else {
  116. #if RACCompoundDisposableInlineCount
  117. for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
  118. if (_inlineDisposables[i] == nil) {
  119. _inlineDisposables[i] = disposable;
  120. goto foundSlot;
  121. }
  122. }
  123. #endif
  124. if (_disposables == NULL) _disposables = RACCreateDisposablesArray();
  125. CFArrayAppendValue(_disposables, (__bridge void *)disposable);
  126. if (RACCOMPOUNDDISPOSABLE_ADDED_ENABLED()) {
  127. RACCOMPOUNDDISPOSABLE_ADDED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount);
  128. }
  129. #if RACCompoundDisposableInlineCount
  130. foundSlot:;
  131. #endif
  132. }
  133. }
  134. pthread_mutex_unlock(&_mutex);
  135. // Performed outside of the lock in case the compound disposable is used
  136. // recursively.
  137. if (shouldDispose) [disposable dispose];
  138. }
  139. - (void)removeDisposable:(RACDisposable *)disposable {
  140. if (disposable == nil) return;
  141. pthread_mutex_lock(&_mutex);
  142. {
  143. if (!_disposed) {
  144. #if RACCompoundDisposableInlineCount
  145. for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
  146. if (_inlineDisposables[i] == disposable) _inlineDisposables[i] = nil;
  147. }
  148. #endif
  149. if (_disposables != NULL) {
  150. CFIndex count = CFArrayGetCount(_disposables);
  151. for (CFIndex i = count - 1; i >= 0; i--) {
  152. const void *item = CFArrayGetValueAtIndex(_disposables, i);
  153. if (item == (__bridge void *)disposable) {
  154. CFArrayRemoveValueAtIndex(_disposables, i);
  155. }
  156. }
  157. if (RACCOMPOUNDDISPOSABLE_REMOVED_ENABLED()) {
  158. RACCOMPOUNDDISPOSABLE_REMOVED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount);
  159. }
  160. }
  161. }
  162. }
  163. pthread_mutex_unlock(&_mutex);
  164. }
  165. #pragma mark RACDisposable
  166. static void disposeEach(const void *value, void *context) {
  167. RACDisposable *disposable = (__bridge id)value;
  168. [disposable dispose];
  169. }
  170. - (void)dispose {
  171. #if RACCompoundDisposableInlineCount
  172. RACDisposable *inlineCopy[RACCompoundDisposableInlineCount];
  173. #endif
  174. CFArrayRef remainingDisposables = NULL;
  175. pthread_mutex_lock(&_mutex);
  176. {
  177. _disposed = YES;
  178. #if RACCompoundDisposableInlineCount
  179. for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
  180. inlineCopy[i] = _inlineDisposables[i];
  181. _inlineDisposables[i] = nil;
  182. }
  183. #endif
  184. remainingDisposables = _disposables;
  185. _disposables = NULL;
  186. }
  187. pthread_mutex_unlock(&_mutex);
  188. #if RACCompoundDisposableInlineCount
  189. // Dispose outside of the lock in case the compound disposable is used
  190. // recursively.
  191. for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
  192. [inlineCopy[i] dispose];
  193. }
  194. #endif
  195. if (remainingDisposables == NULL) return;
  196. CFIndex count = CFArrayGetCount(remainingDisposables);
  197. CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL);
  198. CFRelease(remainingDisposables);
  199. }
  200. @end