/// A subject that sends added execution signals. @property (nonatomic, strong, readonly) RACSubject *addedExecutionSignalsSubject;
/// A subject that sends the new value of `allowsConcurrentExecution` whenever it changes. @property (nonatomic, strong, readonly) RACSubject *allowsConcurrentExecutionSubject;
// `enabled`, but without a hop to the main thread. // // Values from this signal may arrive on any thread. @property (nonatomic, strong, readonly) RACSignal *immediateEnabled;
// The signal block that the receiver was initialized with. @property (nonatomic, copy, readonly) RACSignal * (^signalBlock)(id input);
_executionSignals = [[[self.addedExecutionSignalsSubject map:^(RACSignal *signal) { return [signal catchTo:[RACSignal empty]]; }] deliverOn:RACScheduler.mainThreadScheduler] setNameWithFormat:@"%@ -executionSignals", self]; // `errors` needs to be multicasted so that it picks up all // `activeExecutionSignals` that are added. // // In other words, if someone subscribes to `errors` _after_ an execution // has started, it should still receive any error from that execution. RACMulticastConnection *errorsConnection = [[[self.addedExecutionSignalsSubject flattenMap:^(RACSignal *signal) { return [[signal ignoreValues] catch:^(NSError *error) { return [RACSignal return:error]; }]; }] deliverOn:RACScheduler.mainThreadScheduler] publish]; _errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self]; [errorsConnection connect];
- (RACSignal *)execute:(id)input { // `immediateEnabled` is guaranteed to send a value upon subscription, so // -first is acceptable here. BOOL enabled = [[self.immediateEnabled first] boolValue]; if (!enabled) { NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{ NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil), RACUnderlyingCommandErrorKey: self }];
return [RACSignal error:error]; }
RACSignal *signal = self.signalBlock(input); NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input);
// We subscribe to the signal on the main thread so that it occurs _after_ // -addActiveExecutionSignal: completes below. // // This means that `executing` and `enabled` will send updated values before // the signal actually starts performing work. RACMulticastConnection *connection = [[signal subscribeOn:RACScheduler.mainThreadScheduler] multicast:[RACReplaySubject subject]]; [self.addedExecutionSignalsSubject sendNext:connection.signal];
- (void)setRac_command:(RACCommand *)command { objc_setAssociatedObject(self, UIButtonRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // Check for stored signal in order to remove it and add a new one RACDisposable *disposable = objc_getAssociatedObject(self, UIButtonEnabledDisposableKey); [disposable dispose]; if (command == nil) return; disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self]; objc_setAssociatedObject(self, UIButtonEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [self rac_hijackActionAndTargetIfNeeded]; }
- (void)rac_hijackActionAndTargetIfNeeded { SEL hijackSelector = @selector(rac_commandPerformAction:); for (NSString *selector in [self actionsForTarget:self forControlEvent:UIControlEventTouchUpInside]) { if (hijackSelector == NSSelectorFromString(selector)) { return; } } [self addTarget:self action:hijackSelector forControlEvents:UIControlEventTouchUpInside]; }
// Purposely not retaining 'object', since we want to tear down the binding // when it deallocates normally. __block void * volatile objectPtr = (__bridge void *)object;
RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { // Possibly spec, possibly compiler bug, but this __bridge cast does not // result in a retain here, effectively an invisible __unsafe_unretained // qualifier. Using objc_precise_lifetime gives the __strong reference // desired. The explicit use of __strong is strictly defensive. __strongNSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strongid)objectPtr; [object setValue:x ?: nilValue forKeyPath:keyPath]; } error:^(NSError *error) { __strongNSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strongid)objectPtr;
NSCAssert(NO, @"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error);
// Log the error if we're running with assertions disabled. NSLog(@"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error);
// Dispose of any active command associations. [objc_getAssociatedObject(self, UIRefreshControlDisposableKey) dispose];
if (command == nil) return;
// Like RAC(self, enabled) = command.enabled; but with access to disposable. RACDisposable *enabledDisposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self];