RACCommand.m 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. //
  2. // RACCommand.m
  3. // ReactiveCocoa
  4. //
  5. // Created by Josh Abernathy on 3/3/12.
  6. // Copyright (c) 2012 GitHub, Inc. All rights reserved.
  7. //
  8. #import "RACCommand.h"
  9. #import "RACEXTScope.h"
  10. #import "NSArray+RACSequenceAdditions.h"
  11. #import "NSObject+RACDeallocating.h"
  12. #import "NSObject+RACDescription.h"
  13. #import "NSObject+RACPropertySubscribing.h"
  14. #import "RACMulticastConnection.h"
  15. #import "RACReplaySubject.h"
  16. #import "RACScheduler.h"
  17. #import "RACSequence.h"
  18. #import "RACSerialDisposable.h"
  19. #import "RACSignal+Operations.h"
  20. #import <libkern/OSAtomic.h>
  21. NSString * const RACCommandErrorDomain = @"RACCommandErrorDomain";
  22. NSString * const RACUnderlyingCommandErrorKey = @"RACUnderlyingCommandErrorKey";
  23. const NSInteger RACCommandErrorNotEnabled = 1;
  24. @interface RACCommand () {
  25. // The mutable array backing `activeExecutionSignals`.
  26. //
  27. // This should only be used while synchronized on `self`.
  28. NSMutableArray *_activeExecutionSignals;
  29. // Atomic backing variable for `allowsConcurrentExecution`.
  30. volatile uint32_t _allowsConcurrentExecution;
  31. }
  32. // An array of signals representing in-flight executions, in the order they
  33. // began.
  34. //
  35. // This property is KVO-compliant.
  36. @property (atomic, copy, readonly) NSArray *activeExecutionSignals;
  37. // `enabled`, but without a hop to the main thread.
  38. //
  39. // Values from this signal may arrive on any thread.
  40. @property (nonatomic, strong, readonly) RACSignal *immediateEnabled;
  41. // The signal block that the receiver was initialized with.
  42. @property (nonatomic, copy, readonly) RACSignal * (^signalBlock)(id input);
  43. // Improves the performance of KVO on the receiver.
  44. //
  45. // See the documentation for <NSKeyValueObserving> for more information.
  46. @property (atomic) void *observationInfo;
  47. // Adds a signal to `activeExecutionSignals` and generates a KVO notification.
  48. - (void)addActiveExecutionSignal:(RACSignal *)signal;
  49. // Removes a signal from `activeExecutionSignals` and generates a KVO
  50. // notification.
  51. - (void)removeActiveExecutionSignal:(RACSignal *)signal;
  52. @end
  53. @implementation RACCommand
  54. #pragma mark Properties
  55. - (BOOL)allowsConcurrentExecution {
  56. return _allowsConcurrentExecution != 0;
  57. }
  58. - (void)setAllowsConcurrentExecution:(BOOL)allowed {
  59. [self willChangeValueForKey:@keypath(self.allowsConcurrentExecution)];
  60. if (allowed) {
  61. OSAtomicOr32Barrier(1, &_allowsConcurrentExecution);
  62. } else {
  63. OSAtomicAnd32Barrier(0, &_allowsConcurrentExecution);
  64. }
  65. [self didChangeValueForKey:@keypath(self.allowsConcurrentExecution)];
  66. }
  67. - (NSArray *)activeExecutionSignals {
  68. @synchronized (self) {
  69. return [_activeExecutionSignals copy];
  70. }
  71. }
  72. - (void)addActiveExecutionSignal:(RACSignal *)signal {
  73. NSCParameterAssert([signal isKindOfClass:RACSignal.class]);
  74. @synchronized (self) {
  75. // The KVO notification has to be generated while synchronized, because
  76. // it depends on the index remaining consistent.
  77. NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:_activeExecutionSignals.count];
  78. [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)];
  79. [_activeExecutionSignals addObject:signal];
  80. [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)];
  81. }
  82. }
  83. - (void)removeActiveExecutionSignal:(RACSignal *)signal {
  84. NSCParameterAssert([signal isKindOfClass:RACSignal.class]);
  85. @synchronized (self) {
  86. // The indexes have to be calculated and the notification generated
  87. // while synchronized, because they depend on the indexes remaining
  88. // consistent.
  89. NSIndexSet *indexes = [_activeExecutionSignals indexesOfObjectsPassingTest:^ BOOL (RACSignal *obj, NSUInteger index, BOOL *stop) {
  90. return obj == signal;
  91. }];
  92. if (indexes.count == 0) return;
  93. [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)];
  94. [_activeExecutionSignals removeObjectsAtIndexes:indexes];
  95. [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)];
  96. }
  97. }
  98. #pragma mark Lifecycle
  99. - (id)init {
  100. NSCAssert(NO, @"Use -initWithSignalBlock: instead");
  101. return nil;
  102. }
  103. - (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock {
  104. return [self initWithEnabled:nil signalBlock:signalBlock];
  105. }
  106. - (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock {
  107. NSCParameterAssert(signalBlock != nil);
  108. self = [super init];
  109. if (self == nil) return nil;
  110. _activeExecutionSignals = [[NSMutableArray alloc] init];
  111. _signalBlock = [signalBlock copy];
  112. // A signal of additions to `activeExecutionSignals`.
  113. RACSignal *newActiveExecutionSignals = [[[[[self
  114. rac_valuesAndChangesForKeyPath:@keypath(self.activeExecutionSignals) options:NSKeyValueObservingOptionNew observer:nil]
  115. reduceEach:^(id _, NSDictionary *change) {
  116. NSArray *signals = change[NSKeyValueChangeNewKey];
  117. if (signals == nil) return [RACSignal empty];
  118. return [signals.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler];
  119. }]
  120. concat]
  121. publish]
  122. autoconnect];
  123. _executionSignals = [[[newActiveExecutionSignals
  124. map:^(RACSignal *signal) {
  125. return [signal catchTo:[RACSignal empty]];
  126. }]
  127. deliverOn:RACScheduler.mainThreadScheduler]
  128. setNameWithFormat:@"%@ -executionSignals", self];
  129. // `errors` needs to be multicasted so that it picks up all
  130. // `activeExecutionSignals` that are added.
  131. //
  132. // In other words, if someone subscribes to `errors` _after_ an execution
  133. // has started, it should still receive any error from that execution.
  134. RACMulticastConnection *errorsConnection = [[[newActiveExecutionSignals
  135. flattenMap:^(RACSignal *signal) {
  136. return [[signal
  137. ignoreValues]
  138. catch:^(NSError *error) {
  139. return [RACSignal return:error];
  140. }];
  141. }]
  142. deliverOn:RACScheduler.mainThreadScheduler]
  143. publish];
  144. _errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];
  145. [errorsConnection connect];
  146. RACSignal *immediateExecuting = [RACObserve(self, activeExecutionSignals) map:^(NSArray *activeSignals) {
  147. return @(activeSignals.count > 0);
  148. }];
  149. _executing = [[[[[immediateExecuting
  150. deliverOn:RACScheduler.mainThreadScheduler]
  151. // This is useful before the first value arrives on the main thread.
  152. startWith:@NO]
  153. distinctUntilChanged]
  154. replayLast]
  155. setNameWithFormat:@"%@ -executing", self];
  156. RACSignal *moreExecutionsAllowed = [RACSignal
  157. if:RACObserve(self, allowsConcurrentExecution)
  158. then:[RACSignal return:@YES]
  159. else:[immediateExecuting not]];
  160. if (enabledSignal == nil) {
  161. enabledSignal = [RACSignal return:@YES];
  162. } else {
  163. enabledSignal = [[[enabledSignal
  164. startWith:@YES]
  165. takeUntil:self.rac_willDeallocSignal]
  166. replayLast];
  167. }
  168. _immediateEnabled = [[RACSignal
  169. combineLatest:@[ enabledSignal, moreExecutionsAllowed ]]
  170. and];
  171. _enabled = [[[[[self.immediateEnabled
  172. take:1]
  173. concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]]
  174. distinctUntilChanged]
  175. replayLast]
  176. setNameWithFormat:@"%@ -enabled", self];
  177. return self;
  178. }
  179. #pragma mark Execution
  180. - (RACSignal *)execute:(id)input {
  181. // `immediateEnabled` is guaranteed to send a value upon subscription, so
  182. // -first is acceptable here.
  183. BOOL enabled = [[self.immediateEnabled first] boolValue];
  184. if (!enabled) {
  185. NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{
  186. NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil),
  187. RACUnderlyingCommandErrorKey: self
  188. }];
  189. return [RACSignal error:error];
  190. }
  191. RACSignal *signal = self.signalBlock(input);
  192. NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input);
  193. // We subscribe to the signal on the main thread so that it occurs _after_
  194. // -addActiveExecutionSignal: completes below.
  195. //
  196. // This means that `executing` and `enabled` will send updated values before
  197. // the signal actually starts performing work.
  198. RACMulticastConnection *connection = [[signal
  199. subscribeOn:RACScheduler.mainThreadScheduler]
  200. multicast:[RACReplaySubject subject]];
  201. @weakify(self);
  202. [self addActiveExecutionSignal:connection.signal];
  203. [connection.signal subscribeError:^(NSError *error) {
  204. @strongify(self);
  205. [self removeActiveExecutionSignal:connection.signal];
  206. } completed:^{
  207. @strongify(self);
  208. [self removeActiveExecutionSignal:connection.signal];
  209. }];
  210. [connection connect];
  211. return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, [input rac_description]];
  212. }
  213. #pragma mark NSKeyValueObserving
  214. + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
  215. // Generate all KVO notifications manually to avoid the performance impact
  216. // of unnecessary swizzling.
  217. return NO;
  218. }
  219. @end