From 3977e1262408083146879e8a122985e6e0deacb4 Mon Sep 17 00:00:00 2001 From: Yaron Inger Date: Mon, 5 Nov 2018 14:22:58 +0200 Subject: [PATCH] ReactiveObjC: convert tabs to spaces. --- .../MKAnnotationView+RACSignalSupport.m | 16 +- ReactiveObjC/NSArray+RACSequenceAdditions.m | 2 +- ReactiveObjC/NSControl+RACCommandSupport.m | 50 +- ReactiveObjC/NSControl+RACTextSignalSupport.m | 32 +- ReactiveObjC/NSData+RACSupport.m | 34 +- .../NSDictionary+RACSequenceAdditions.m | 16 +- .../NSEnumerator+RACSequenceAdditions.m | 10 +- ReactiveObjC/NSFileHandle+RACSupport.m | 38 +- .../NSIndexSet+RACSequenceAdditions.m | 2 +- ReactiveObjC/NSInvocation+RACTypeParsing.m | 372 ++--- .../NSNotificationCenter+RACSupport.m | 20 +- ReactiveObjC/NSObject+RACAppKitBindings.m | 82 +- ReactiveObjC/NSObject+RACDeallocating.m | 128 +- ReactiveObjC/NSObject+RACDescription.m | 32 +- ReactiveObjC/NSObject+RACKVOWrapper.m | 352 ++--- ReactiveObjC/NSObject+RACLifting.m | 86 +- .../NSObject+RACPropertySubscribing.h | 24 +- .../NSObject+RACPropertySubscribing.m | 94 +- ReactiveObjC/NSObject+RACSelectorSignal.m | 502 +++---- .../NSOrderedSet+RACSequenceAdditions.m | 4 +- ReactiveObjC/NSSet+RACSequenceAdditions.m | 4 +- ReactiveObjC/NSString+RACKeyPathUtilities.m | 28 +- ReactiveObjC/NSString+RACSequenceAdditions.m | 2 +- ReactiveObjC/NSString+RACSupport.m | 38 +- ReactiveObjC/NSText+RACSignalSupport.m | 32 +- ReactiveObjC/NSURLConnection+RACSupport.m | 60 +- ReactiveObjC/NSUserDefaults+RACSupport.m | 70 +- ReactiveObjC/RACArraySequence.m | 94 +- ReactiveObjC/RACBehaviorSubject.m | 38 +- ReactiveObjC/RACBlockTrampoline.h | 74 +- ReactiveObjC/RACChannel.m | 44 +- ReactiveObjC/RACCommand.m | 252 ++-- ReactiveObjC/RACCompoundDisposable.m | 334 ++--- ReactiveObjC/RACDelegateProxy.m | 48 +- ReactiveObjC/RACDisposable.m | 64 +- ReactiveObjC/RACDynamicSequence.m | 202 +-- ReactiveObjC/RACDynamicSignal.m | 30 +- ReactiveObjC/RACEagerSequence.m | 52 +- ReactiveObjC/RACEmptySequence.m | 36 +- ReactiveObjC/RACEmptySignal.m | 32 +- ReactiveObjC/RACErrorSignal.m | 18 +- ReactiveObjC/RACEvent.m | 74 +- ReactiveObjC/RACGroupedSignal.m | 6 +- ReactiveObjC/RACImmediateScheduler.m | 34 +- ReactiveObjC/RACIndexSetSequence.m | 102 +- ReactiveObjC/RACKVOChannel.m | 220 +-- ReactiveObjC/RACKVOProxy.m | 52 +- ReactiveObjC/RACKVOTrampoline.m | 94 +- ReactiveObjC/RACMulticastConnection.m | 76 +- ReactiveObjC/RACPassthroughSubscriber.m | 64 +- ReactiveObjC/RACQueueScheduler.m | 96 +- ReactiveObjC/RACReplaySubject.m | 90 +- ReactiveObjC/RACReturnSignal.m | 68 +- ReactiveObjC/RACScheduler.h | 8 +- ReactiveObjC/RACScheduler.m | 210 +-- ReactiveObjC/RACScopedDisposable.m | 12 +- ReactiveObjC/RACSequence.m | 420 +++--- ReactiveObjC/RACSerialDisposable.m | 118 +- ReactiveObjC/RACSignal+Operations.m | 56 +- ReactiveObjC/RACSignalSequence.m | 64 +- ReactiveObjC/RACStream.m | 438 +++--- ReactiveObjC/RACStringSequence.m | 34 +- ReactiveObjC/RACSubject.m | 120 +- ReactiveObjC/RACSubscriber.m | 92 +- .../RACSubscriptingAssignmentTrampoline.m | 18 +- ReactiveObjC/RACSubscriptionScheduler.m | 22 +- ReactiveObjC/RACTargetQueueScheduler.m | 16 +- ReactiveObjC/RACTestScheduler.m | 172 +-- ReactiveObjC/RACTuple.m | 330 ++--- ReactiveObjC/RACTupleSequence.m | 38 +- ReactiveObjC/RACUnarySequence.m | 36 +- ReactiveObjC/RACUnit.m | 14 +- ReactiveObjC/RACValueTransformer.m | 12 +- ReactiveObjC/ReactiveObjC.h | 54 +- ReactiveObjC/UIActionSheet+RACSignalSupport.m | 30 +- ReactiveObjC/UIAlertView+RACSignalSupport.m | 54 +- .../UIBarButtonItem+RACCommandSupport.m | 42 +- ReactiveObjC/UIButton+RACCommandSupport.m | 46 +- ...ICollectionReusableView+RACSignalSupport.m | 20 +- ReactiveObjC/UIControl+RACSignalSupport.m | 40 +- .../UIControl+RACSignalSupportPrivate.m | 50 +- ReactiveObjC/UIDatePicker+RACSignalSupport.m | 2 +- .../UIGestureRecognizer+RACSignalSupport.m | 28 +- ...UIImagePickerController+RACSignalSupport.m | 44 +- .../UIRefreshControl+RACCommandSupport.m | 58 +- .../UISegmentedControl+RACSignalSupport.m | 2 +- ReactiveObjC/UISlider+RACSignalSupport.m | 2 +- ReactiveObjC/UIStepper+RACSignalSupport.m | 2 +- ReactiveObjC/UISwitch+RACSignalSupport.m | 2 +- .../UITableViewCell+RACSignalSupport.m | 18 +- ...bleViewHeaderFooterView+RACSignalSupport.m | 16 +- ReactiveObjC/UITextField+RACSignalSupport.m | 26 +- ReactiveObjC/UITextView+RACSignalSupport.m | 44 +- .../NSControllerRACSupportSpec.m | 34 +- .../NSEnumeratorRACSequenceAdditionsSpec.m | 14 +- .../NSNotificationCenterRACSupportSpec.m | 76 +- .../NSObjectRACAppKitBindingsSpec.m | 32 +- .../NSObjectRACDeallocatingSpec.m | 272 ++-- ReactiveObjCTests/NSObjectRACLiftingSpec.m | 610 ++++---- .../NSObjectRACPropertySubscribingExamples.m | 374 ++--- .../NSObjectRACPropertySubscribingSpec.m | 200 +-- .../NSObjectRACSelectorSignalSpec.m | 720 ++++----- .../NSStringRACKeyPathUtilitiesSpec.m | 58 +- .../NSURLConnectionRACSupportSpec.m | 28 +- .../NSUserDefaultsRACSupportSpec.m | 134 +- ReactiveObjCTests/RACBlockTrampolineSpec.m | 48 +- ReactiveObjCTests/RACChannelExamples.m | 528 +++---- ReactiveObjCTests/RACChannelSpec.m | 86 +- ReactiveObjCTests/RACCommandSpec.m | 870 +++++------ ReactiveObjCTests/RACCompoundDisposableSpec.m | 148 +- ReactiveObjCTests/RACControlCommandExamples.m | 98 +- ReactiveObjCTests/RACDelegateProxySpec.m | 62 +- ReactiveObjCTests/RACDisposableSpec.m | 80 +- ReactiveObjCTests/RACEventSpec.m | 84 +- ReactiveObjCTests/RACKVOChannelSpec.m | 726 +++++----- ReactiveObjCTests/RACKVOProxySpec.m | 368 ++--- ReactiveObjCTests/RACKVOWrapperSpec.m | 1176 +++++++-------- .../RACMulticastConnectionSpec.m | 230 +-- ReactiveObjCTests/RACPropertySignalExamples.m | 172 +-- ReactiveObjCTests/RACSchedulerSpec.m | 702 ++++----- ReactiveObjCTests/RACSequenceAdditionsSpec.m | 614 ++++---- ReactiveObjCTests/RACSequenceExamples.m | 208 +-- ReactiveObjCTests/RACSequenceSpec.m | 752 +++++----- ReactiveObjCTests/RACSerialDisposableSpec.m | 204 +-- ReactiveObjCTests/RACStreamExamples.m | 1284 ++++++++--------- ReactiveObjCTests/RACSubclassObject.m | 20 +- ReactiveObjCTests/RACSubjectSpec.m | 592 ++++---- ReactiveObjCTests/RACSubscriberExamples.m | 318 ++-- ReactiveObjCTests/RACSubscriberSpec.m | 142 +- .../RACSubscriptingAssignmentTrampolineSpec.m | 14 +- .../RACTargetQueueSchedulerSpec.m | 44 +- ReactiveObjCTests/RACTestExampleScheduler.m | 20 +- ReactiveObjCTests/RACTestObject.h | 4 +- ReactiveObjCTests/RACTestObject.m | 90 +- ReactiveObjCTests/RACTestSchedulerSpec.m | 244 ++-- ReactiveObjCTests/RACTestUIButton.m | 6 +- ReactiveObjCTests/RACTupleSpec.m | 386 ++--- .../UIActionSheetRACSupportSpec.m | 36 +- ReactiveObjCTests/UIAlertViewRACSupportSpec.m | 52 +- .../UIBarButtonItemRACSupportSpec.m | 40 +- ReactiveObjCTests/UIButtonRACSupportSpec.m | 34 +- .../UIImagePickerControllerRACSupportSpec.m | 68 +- 142 files changed, 10168 insertions(+), 10168 deletions(-) diff --git a/ReactiveObjC/MKAnnotationView+RACSignalSupport.m b/ReactiveObjC/MKAnnotationView+RACSignalSupport.m index d138a79f4..7fb854b84 100644 --- a/ReactiveObjC/MKAnnotationView+RACSignalSupport.m +++ b/ReactiveObjC/MKAnnotationView+RACSignalSupport.m @@ -16,16 +16,16 @@ @implementation MKAnnotationView (RACSignalSupport) - (RACSignal *)rac_prepareForReuseSignal { - RACSignal *signal = objc_getAssociatedObject(self, _cmd); - if (signal != nil) return signal; + RACSignal *signal = objc_getAssociatedObject(self, _cmd); + if (signal != nil) return signal; - signal = [[[self - rac_signalForSelector:@selector(prepareForReuse)] - mapReplace:RACUnit.defaultUnit] - setNameWithFormat:@"%@ -rac_prepareForReuseSignal", RACDescription(self)]; + signal = [[[self + rac_signalForSelector:@selector(prepareForReuse)] + mapReplace:RACUnit.defaultUnit] + setNameWithFormat:@"%@ -rac_prepareForReuseSignal", RACDescription(self)]; - objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - return signal; + objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return signal; } @end diff --git a/ReactiveObjC/NSArray+RACSequenceAdditions.m b/ReactiveObjC/NSArray+RACSequenceAdditions.m index 4eaebb8a1..1dd3345ce 100644 --- a/ReactiveObjC/NSArray+RACSequenceAdditions.m +++ b/ReactiveObjC/NSArray+RACSequenceAdditions.m @@ -12,7 +12,7 @@ @implementation NSArray (RACSequenceAdditions) - (RACSequence *)rac_sequence { - return [RACArraySequence sequenceWithArray:self offset:0]; + return [RACArraySequence sequenceWithArray:self offset:0]; } @end diff --git a/ReactiveObjC/NSControl+RACCommandSupport.m b/ReactiveObjC/NSControl+RACCommandSupport.m index 1d8f6ba25..abb9e6b4f 100644 --- a/ReactiveObjC/NSControl+RACCommandSupport.m +++ b/ReactiveObjC/NSControl+RACCommandSupport.m @@ -18,40 +18,40 @@ @implementation NSControl (RACCommandSupport) - (RACCommand *)rac_command { - return objc_getAssociatedObject(self, NSControlRACCommandKey); + return objc_getAssociatedObject(self, NSControlRACCommandKey); } - (void)setRac_command:(RACCommand *)command { - objc_setAssociatedObject(self, NSControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - // Tear down any previous binding before setting up our new one, or else we - // might get assertion failures. - [objc_getAssociatedObject(self, NSControlEnabledDisposableKey) dispose]; - objc_setAssociatedObject(self, NSControlEnabledDisposableKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - if (command == nil) { - self.enabled = YES; - return; - } - - [self rac_hijackActionAndTargetIfNeeded]; - - RACScopedDisposable *disposable = [[command.enabled setKeyPath:@"enabled" onObject:self] asScopedDisposable]; - objc_setAssociatedObject(self, NSControlEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, NSControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // Tear down any previous binding before setting up our new one, or else we + // might get assertion failures. + [objc_getAssociatedObject(self, NSControlEnabledDisposableKey) dispose]; + objc_setAssociatedObject(self, NSControlEnabledDisposableKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + if (command == nil) { + self.enabled = YES; + return; + } + + [self rac_hijackActionAndTargetIfNeeded]; + + RACScopedDisposable *disposable = [[command.enabled setKeyPath:@"enabled" onObject:self] asScopedDisposable]; + objc_setAssociatedObject(self, NSControlEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (void)rac_hijackActionAndTargetIfNeeded { - SEL hijackSelector = @selector(rac_commandPerformAction:); - if (self.target == self && self.action == hijackSelector) return; - - if (self.target != nil) NSLog(@"WARNING: NSControl.rac_command hijacks the control's existing target and action."); - - self.target = self; - self.action = hijackSelector; + SEL hijackSelector = @selector(rac_commandPerformAction:); + if (self.target == self && self.action == hijackSelector) return; + + if (self.target != nil) NSLog(@"WARNING: NSControl.rac_command hijacks the control's existing target and action."); + + self.target = self; + self.action = hijackSelector; } - (void)rac_commandPerformAction:(id)sender { - [self.rac_command execute:sender]; + [self.rac_command execute:sender]; } @end diff --git a/ReactiveObjC/NSControl+RACTextSignalSupport.m b/ReactiveObjC/NSControl+RACTextSignalSupport.m index ba77c40be..e77f0a4d2 100644 --- a/ReactiveObjC/NSControl+RACTextSignalSupport.m +++ b/ReactiveObjC/NSControl+RACTextSignalSupport.m @@ -16,23 +16,23 @@ @implementation NSControl (RACTextSignalSupport) - (RACSignal *)rac_textSignal { - @rac_weakify(self); - return [[[[RACSignal - createSignal:^(id subscriber) { - @rac_strongify(self); - id observer = [NSNotificationCenter.defaultCenter addObserverForName:NSControlTextDidChangeNotification object:self queue:nil usingBlock:^(NSNotification *note) { - [subscriber sendNext:note.object]; - }]; + @rac_weakify(self); + return [[[[RACSignal + createSignal:^(id subscriber) { + @rac_strongify(self); + id observer = [NSNotificationCenter.defaultCenter addObserverForName:NSControlTextDidChangeNotification object:self queue:nil usingBlock:^(NSNotification *note) { + [subscriber sendNext:note.object]; + }]; - return [RACDisposable disposableWithBlock:^{ - [NSNotificationCenter.defaultCenter removeObserver:observer]; - }]; - }] - map:^(NSControl *control) { - return [control.stringValue copy]; - }] - startWith:[self.stringValue copy]] - setNameWithFormat:@"%@ -rac_textSignal", RACDescription(self)]; + return [RACDisposable disposableWithBlock:^{ + [NSNotificationCenter.defaultCenter removeObserver:observer]; + }]; + }] + map:^(NSControl *control) { + return [control.stringValue copy]; + }] + startWith:[self.stringValue copy]] + setNameWithFormat:@"%@ -rac_textSignal", RACDescription(self)]; } @end diff --git a/ReactiveObjC/NSData+RACSupport.m b/ReactiveObjC/NSData+RACSupport.m index 977ba4aea..af622fa92 100644 --- a/ReactiveObjC/NSData+RACSupport.m +++ b/ReactiveObjC/NSData+RACSupport.m @@ -13,23 +13,23 @@ @implementation NSData (RACSupport) + (RACSignal *)rac_readContentsOfURL:(NSURL *)URL options:(NSDataReadingOptions)options scheduler:(RACScheduler *)scheduler { - NSCParameterAssert(scheduler != nil); - - RACReplaySubject *subject = [RACReplaySubject subject]; - [subject setNameWithFormat:@"+rac_readContentsOfURL: %@ options: %lu scheduler: %@", URL, (unsigned long)options, scheduler]; - - [scheduler schedule:^{ - NSError *error = nil; - NSData *data = [[NSData alloc] initWithContentsOfURL:URL options:options error:&error]; - if (data == nil) { - [subject sendError:error]; - } else { - [subject sendNext:data]; - [subject sendCompleted]; - } - }]; - - return subject; + NSCParameterAssert(scheduler != nil); + + RACReplaySubject *subject = [RACReplaySubject subject]; + [subject setNameWithFormat:@"+rac_readContentsOfURL: %@ options: %lu scheduler: %@", URL, (unsigned long)options, scheduler]; + + [scheduler schedule:^{ + NSError *error = nil; + NSData *data = [[NSData alloc] initWithContentsOfURL:URL options:options error:&error]; + if (data == nil) { + [subject sendError:error]; + } else { + [subject sendNext:data]; + [subject sendCompleted]; + } + }]; + + return subject; } @end diff --git a/ReactiveObjC/NSDictionary+RACSequenceAdditions.m b/ReactiveObjC/NSDictionary+RACSequenceAdditions.m index 2998b9a15..ce7dbcf12 100644 --- a/ReactiveObjC/NSDictionary+RACSequenceAdditions.m +++ b/ReactiveObjC/NSDictionary+RACSequenceAdditions.m @@ -14,21 +14,21 @@ @implementation NSDictionary (RACSequenceAdditions) - (RACSequence *)rac_sequence { - NSDictionary *immutableDict = [self copy]; + NSDictionary *immutableDict = [self copy]; - // TODO: First class support for dictionary sequences. - return [immutableDict.allKeys.rac_sequence map:^(id key) { - id value = immutableDict[key]; - return RACTuplePack(key, value); - }]; + // TODO: First class support for dictionary sequences. + return [immutableDict.allKeys.rac_sequence map:^(id key) { + id value = immutableDict[key]; + return RACTuplePack(key, value); + }]; } - (RACSequence *)rac_keySequence { - return self.allKeys.rac_sequence; + return self.allKeys.rac_sequence; } - (RACSequence *)rac_valueSequence { - return self.allValues.rac_sequence; + return self.allValues.rac_sequence; } @end diff --git a/ReactiveObjC/NSEnumerator+RACSequenceAdditions.m b/ReactiveObjC/NSEnumerator+RACSequenceAdditions.m index 59b638000..9deeec243 100644 --- a/ReactiveObjC/NSEnumerator+RACSequenceAdditions.m +++ b/ReactiveObjC/NSEnumerator+RACSequenceAdditions.m @@ -12,11 +12,11 @@ @implementation NSEnumerator (RACSequenceAdditions) - (RACSequence *)rac_sequence { - return [RACSequence sequenceWithHeadBlock:^{ - return [self nextObject]; - } tailBlock:^{ - return self.rac_sequence; - }]; + return [RACSequence sequenceWithHeadBlock:^{ + return [self nextObject]; + } tailBlock:^{ + return self.rac_sequence; + }]; } @end diff --git a/ReactiveObjC/NSFileHandle+RACSupport.m b/ReactiveObjC/NSFileHandle+RACSupport.m index 9d1c42f37..d3fa8734e 100644 --- a/ReactiveObjC/NSFileHandle+RACSupport.m +++ b/ReactiveObjC/NSFileHandle+RACSupport.m @@ -15,26 +15,26 @@ @implementation NSFileHandle (RACSupport) - (RACSignal *)rac_readInBackground { - RACReplaySubject *subject = [RACReplaySubject subject]; - [subject setNameWithFormat:@"%@ -rac_readInBackground", RACDescription(self)]; + RACReplaySubject *subject = [RACReplaySubject subject]; + [subject setNameWithFormat:@"%@ -rac_readInBackground", RACDescription(self)]; - RACSignal *dataNotification = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:NSFileHandleReadCompletionNotification object:self] map:^(NSNotification *note) { - return note.userInfo[NSFileHandleNotificationDataItem]; - }]; - - __block RACDisposable *subscription = [dataNotification subscribeNext:^(NSData *data) { - if (data.length > 0) { - [subject sendNext:data]; - [self readInBackgroundAndNotify]; - } else { - [subject sendCompleted]; - [subscription dispose]; - } - }]; - - [self readInBackgroundAndNotify]; - - return subject; + RACSignal *dataNotification = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:NSFileHandleReadCompletionNotification object:self] map:^(NSNotification *note) { + return note.userInfo[NSFileHandleNotificationDataItem]; + }]; + + __block RACDisposable *subscription = [dataNotification subscribeNext:^(NSData *data) { + if (data.length > 0) { + [subject sendNext:data]; + [self readInBackgroundAndNotify]; + } else { + [subject sendCompleted]; + [subscription dispose]; + } + }]; + + [self readInBackgroundAndNotify]; + + return subject; } @end diff --git a/ReactiveObjC/NSIndexSet+RACSequenceAdditions.m b/ReactiveObjC/NSIndexSet+RACSequenceAdditions.m index e2866b823..2fef9a3b3 100644 --- a/ReactiveObjC/NSIndexSet+RACSequenceAdditions.m +++ b/ReactiveObjC/NSIndexSet+RACSequenceAdditions.m @@ -12,7 +12,7 @@ @implementation NSIndexSet (RACSequenceAdditions) - (RACSequence *)rac_sequence { - return [RACIndexSetSequence sequenceWithIndexSet:self]; + return [RACIndexSetSequence sequenceWithIndexSet:self]; } @end diff --git a/ReactiveObjC/NSInvocation+RACTypeParsing.m b/ReactiveObjC/NSInvocation+RACTypeParsing.m index 92b5b7d06..3d382be24 100644 --- a/ReactiveObjC/NSInvocation+RACTypeParsing.m +++ b/ReactiveObjC/NSInvocation+RACTypeParsing.m @@ -15,216 +15,216 @@ @implementation NSInvocation (RACTypeParsing) - (void)rac_setArgument:(id)object atIndex:(NSUInteger)index { #define PULL_AND_SET(type, selector) \ - do { \ - type val = [object selector]; \ - [self setArgument:&val atIndex:(NSInteger)index]; \ - } while (0) - - const char *argType = [self.methodSignature getArgumentTypeAtIndex:index]; - // Skip const type qualifier. - if (argType[0] == 'r') { - argType++; - } - - if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) { - [self setArgument:&object atIndex:(NSInteger)index]; - } else if (strcmp(argType, @encode(char)) == 0) { - PULL_AND_SET(char, charValue); - } else if (strcmp(argType, @encode(int)) == 0) { - PULL_AND_SET(int, intValue); - } else if (strcmp(argType, @encode(short)) == 0) { - PULL_AND_SET(short, shortValue); - } else if (strcmp(argType, @encode(long)) == 0) { - PULL_AND_SET(long, longValue); - } else if (strcmp(argType, @encode(long long)) == 0) { - PULL_AND_SET(long long, longLongValue); - } else if (strcmp(argType, @encode(unsigned char)) == 0) { - PULL_AND_SET(unsigned char, unsignedCharValue); - } else if (strcmp(argType, @encode(unsigned int)) == 0) { - PULL_AND_SET(unsigned int, unsignedIntValue); - } else if (strcmp(argType, @encode(unsigned short)) == 0) { - PULL_AND_SET(unsigned short, unsignedShortValue); - } else if (strcmp(argType, @encode(unsigned long)) == 0) { - PULL_AND_SET(unsigned long, unsignedLongValue); - } else if (strcmp(argType, @encode(unsigned long long)) == 0) { - PULL_AND_SET(unsigned long long, unsignedLongLongValue); - } else if (strcmp(argType, @encode(float)) == 0) { - PULL_AND_SET(float, floatValue); - } else if (strcmp(argType, @encode(double)) == 0) { - PULL_AND_SET(double, doubleValue); - } else if (strcmp(argType, @encode(BOOL)) == 0) { - PULL_AND_SET(BOOL, boolValue); - } else if (strcmp(argType, @encode(char *)) == 0) { - const char *cString = [object UTF8String]; - [self setArgument:&cString atIndex:(NSInteger)index]; - [self retainArguments]; - } else if (strcmp(argType, @encode(void (^)(void))) == 0) { - [self setArgument:&object atIndex:(NSInteger)index]; - } else { - NSCParameterAssert([object isKindOfClass:NSValue.class]); - - NSUInteger valueSize = 0; - NSGetSizeAndAlignment([object objCType], &valueSize, NULL); + do { \ + type val = [object selector]; \ + [self setArgument:&val atIndex:(NSInteger)index]; \ + } while (0) + + const char *argType = [self.methodSignature getArgumentTypeAtIndex:index]; + // Skip const type qualifier. + if (argType[0] == 'r') { + argType++; + } + + if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) { + [self setArgument:&object atIndex:(NSInteger)index]; + } else if (strcmp(argType, @encode(char)) == 0) { + PULL_AND_SET(char, charValue); + } else if (strcmp(argType, @encode(int)) == 0) { + PULL_AND_SET(int, intValue); + } else if (strcmp(argType, @encode(short)) == 0) { + PULL_AND_SET(short, shortValue); + } else if (strcmp(argType, @encode(long)) == 0) { + PULL_AND_SET(long, longValue); + } else if (strcmp(argType, @encode(long long)) == 0) { + PULL_AND_SET(long long, longLongValue); + } else if (strcmp(argType, @encode(unsigned char)) == 0) { + PULL_AND_SET(unsigned char, unsignedCharValue); + } else if (strcmp(argType, @encode(unsigned int)) == 0) { + PULL_AND_SET(unsigned int, unsignedIntValue); + } else if (strcmp(argType, @encode(unsigned short)) == 0) { + PULL_AND_SET(unsigned short, unsignedShortValue); + } else if (strcmp(argType, @encode(unsigned long)) == 0) { + PULL_AND_SET(unsigned long, unsignedLongValue); + } else if (strcmp(argType, @encode(unsigned long long)) == 0) { + PULL_AND_SET(unsigned long long, unsignedLongLongValue); + } else if (strcmp(argType, @encode(float)) == 0) { + PULL_AND_SET(float, floatValue); + } else if (strcmp(argType, @encode(double)) == 0) { + PULL_AND_SET(double, doubleValue); + } else if (strcmp(argType, @encode(BOOL)) == 0) { + PULL_AND_SET(BOOL, boolValue); + } else if (strcmp(argType, @encode(char *)) == 0) { + const char *cString = [object UTF8String]; + [self setArgument:&cString atIndex:(NSInteger)index]; + [self retainArguments]; + } else if (strcmp(argType, @encode(void (^)(void))) == 0) { + [self setArgument:&object atIndex:(NSInteger)index]; + } else { + NSCParameterAssert([object isKindOfClass:NSValue.class]); + + NSUInteger valueSize = 0; + NSGetSizeAndAlignment([object objCType], &valueSize, NULL); #if DEBUG - NSUInteger argSize = 0; - NSGetSizeAndAlignment(argType, &argSize, NULL); - NSCAssert(valueSize == argSize, @"Value size does not match argument size in -rac_setArgument: %@ atIndex: %lu", object, (unsigned long)index); + NSUInteger argSize = 0; + NSGetSizeAndAlignment(argType, &argSize, NULL); + NSCAssert(valueSize == argSize, @"Value size does not match argument size in -rac_setArgument: %@ atIndex: %lu", object, (unsigned long)index); #endif - - unsigned char valueBytes[valueSize]; - [object getValue:valueBytes]; + + unsigned char valueBytes[valueSize]; + [object getValue:valueBytes]; - [self setArgument:valueBytes atIndex:(NSInteger)index]; - } + [self setArgument:valueBytes atIndex:(NSInteger)index]; + } #undef PULL_AND_SET } - (id)rac_argumentAtIndex:(NSUInteger)index { #define WRAP_AND_RETURN(type) \ - do { \ - type val = 0; \ - [self getArgument:&val atIndex:(NSInteger)index]; \ - return @(val); \ - } while (0) - - const char *argType = [self.methodSignature getArgumentTypeAtIndex:index]; - // Skip const type qualifier. - if (argType[0] == 'r') { - argType++; - } - - if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) { - __autoreleasing id returnObj; - [self getArgument:&returnObj atIndex:(NSInteger)index]; - return returnObj; - } else if (strcmp(argType, @encode(char)) == 0) { - WRAP_AND_RETURN(char); - } else if (strcmp(argType, @encode(int)) == 0) { - WRAP_AND_RETURN(int); - } else if (strcmp(argType, @encode(short)) == 0) { - WRAP_AND_RETURN(short); - } else if (strcmp(argType, @encode(long)) == 0) { - WRAP_AND_RETURN(long); - } else if (strcmp(argType, @encode(long long)) == 0) { - WRAP_AND_RETURN(long long); - } else if (strcmp(argType, @encode(unsigned char)) == 0) { - WRAP_AND_RETURN(unsigned char); - } else if (strcmp(argType, @encode(unsigned int)) == 0) { - WRAP_AND_RETURN(unsigned int); - } else if (strcmp(argType, @encode(unsigned short)) == 0) { - WRAP_AND_RETURN(unsigned short); - } else if (strcmp(argType, @encode(unsigned long)) == 0) { - WRAP_AND_RETURN(unsigned long); - } else if (strcmp(argType, @encode(unsigned long long)) == 0) { - WRAP_AND_RETURN(unsigned long long); - } else if (strcmp(argType, @encode(float)) == 0) { - WRAP_AND_RETURN(float); - } else if (strcmp(argType, @encode(double)) == 0) { - WRAP_AND_RETURN(double); - } else if (strcmp(argType, @encode(BOOL)) == 0) { - WRAP_AND_RETURN(BOOL); - } else if (strcmp(argType, @encode(char *)) == 0) { - WRAP_AND_RETURN(const char *); - } else if (strcmp(argType, @encode(void (^)(void))) == 0) { - __unsafe_unretained id block = nil; - [self getArgument:&block atIndex:(NSInteger)index]; - return [block copy]; - } else { - NSUInteger valueSize = 0; - NSGetSizeAndAlignment(argType, &valueSize, NULL); - - unsigned char valueBytes[valueSize]; - [self getArgument:valueBytes atIndex:(NSInteger)index]; - - return [NSValue valueWithBytes:valueBytes objCType:argType]; - } - - return nil; + do { \ + type val = 0; \ + [self getArgument:&val atIndex:(NSInteger)index]; \ + return @(val); \ + } while (0) + + const char *argType = [self.methodSignature getArgumentTypeAtIndex:index]; + // Skip const type qualifier. + if (argType[0] == 'r') { + argType++; + } + + if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) { + __autoreleasing id returnObj; + [self getArgument:&returnObj atIndex:(NSInteger)index]; + return returnObj; + } else if (strcmp(argType, @encode(char)) == 0) { + WRAP_AND_RETURN(char); + } else if (strcmp(argType, @encode(int)) == 0) { + WRAP_AND_RETURN(int); + } else if (strcmp(argType, @encode(short)) == 0) { + WRAP_AND_RETURN(short); + } else if (strcmp(argType, @encode(long)) == 0) { + WRAP_AND_RETURN(long); + } else if (strcmp(argType, @encode(long long)) == 0) { + WRAP_AND_RETURN(long long); + } else if (strcmp(argType, @encode(unsigned char)) == 0) { + WRAP_AND_RETURN(unsigned char); + } else if (strcmp(argType, @encode(unsigned int)) == 0) { + WRAP_AND_RETURN(unsigned int); + } else if (strcmp(argType, @encode(unsigned short)) == 0) { + WRAP_AND_RETURN(unsigned short); + } else if (strcmp(argType, @encode(unsigned long)) == 0) { + WRAP_AND_RETURN(unsigned long); + } else if (strcmp(argType, @encode(unsigned long long)) == 0) { + WRAP_AND_RETURN(unsigned long long); + } else if (strcmp(argType, @encode(float)) == 0) { + WRAP_AND_RETURN(float); + } else if (strcmp(argType, @encode(double)) == 0) { + WRAP_AND_RETURN(double); + } else if (strcmp(argType, @encode(BOOL)) == 0) { + WRAP_AND_RETURN(BOOL); + } else if (strcmp(argType, @encode(char *)) == 0) { + WRAP_AND_RETURN(const char *); + } else if (strcmp(argType, @encode(void (^)(void))) == 0) { + __unsafe_unretained id block = nil; + [self getArgument:&block atIndex:(NSInteger)index]; + return [block copy]; + } else { + NSUInteger valueSize = 0; + NSGetSizeAndAlignment(argType, &valueSize, NULL); + + unsigned char valueBytes[valueSize]; + [self getArgument:valueBytes atIndex:(NSInteger)index]; + + return [NSValue valueWithBytes:valueBytes objCType:argType]; + } + + return nil; #undef WRAP_AND_RETURN } - (RACTuple *)rac_argumentsTuple { - NSUInteger numberOfArguments = self.methodSignature.numberOfArguments; - NSMutableArray *argumentsArray = [NSMutableArray arrayWithCapacity:numberOfArguments - 2]; - for (NSUInteger index = 2; index < numberOfArguments; index++) { - [argumentsArray addObject:[self rac_argumentAtIndex:index] ?: RACTupleNil.tupleNil]; - } + NSUInteger numberOfArguments = self.methodSignature.numberOfArguments; + NSMutableArray *argumentsArray = [NSMutableArray arrayWithCapacity:numberOfArguments - 2]; + for (NSUInteger index = 2; index < numberOfArguments; index++) { + [argumentsArray addObject:[self rac_argumentAtIndex:index] ?: RACTupleNil.tupleNil]; + } - return [RACTuple tupleWithObjectsFromArray:argumentsArray]; + return [RACTuple tupleWithObjectsFromArray:argumentsArray]; } - (void)setRac_argumentsTuple:(RACTuple *)arguments { - NSCAssert(arguments.count == self.methodSignature.numberOfArguments - 2, @"Number of supplied arguments (%lu), does not match the number expected by the signature (%lu)", (unsigned long)arguments.count, (unsigned long)self.methodSignature.numberOfArguments - 2); + NSCAssert(arguments.count == self.methodSignature.numberOfArguments - 2, @"Number of supplied arguments (%lu), does not match the number expected by the signature (%lu)", (unsigned long)arguments.count, (unsigned long)self.methodSignature.numberOfArguments - 2); - NSUInteger index = 2; - for (id arg in arguments) { - [self rac_setArgument:(arg == RACTupleNil.tupleNil ? nil : arg) atIndex:index]; - index++; - } + NSUInteger index = 2; + for (id arg in arguments) { + [self rac_setArgument:(arg == RACTupleNil.tupleNil ? nil : arg) atIndex:index]; + index++; + } } - (id)rac_returnValue { #define WRAP_AND_RETURN(type) \ - do { \ - type val = 0; \ - [self getReturnValue:&val]; \ - return @(val); \ - } while (0) - - const char *returnType = self.methodSignature.methodReturnType; - // Skip const type qualifier. - if (returnType[0] == 'r') { - returnType++; - } - - if (strcmp(returnType, @encode(id)) == 0 || strcmp(returnType, @encode(Class)) == 0 || strcmp(returnType, @encode(void (^)(void))) == 0) { - __autoreleasing id returnObj; - [self getReturnValue:&returnObj]; - return returnObj; - } else if (strcmp(returnType, @encode(char)) == 0) { - WRAP_AND_RETURN(char); - } else if (strcmp(returnType, @encode(int)) == 0) { - WRAP_AND_RETURN(int); - } else if (strcmp(returnType, @encode(short)) == 0) { - WRAP_AND_RETURN(short); - } else if (strcmp(returnType, @encode(long)) == 0) { - WRAP_AND_RETURN(long); - } else if (strcmp(returnType, @encode(long long)) == 0) { - WRAP_AND_RETURN(long long); - } else if (strcmp(returnType, @encode(unsigned char)) == 0) { - WRAP_AND_RETURN(unsigned char); - } else if (strcmp(returnType, @encode(unsigned int)) == 0) { - WRAP_AND_RETURN(unsigned int); - } else if (strcmp(returnType, @encode(unsigned short)) == 0) { - WRAP_AND_RETURN(unsigned short); - } else if (strcmp(returnType, @encode(unsigned long)) == 0) { - WRAP_AND_RETURN(unsigned long); - } else if (strcmp(returnType, @encode(unsigned long long)) == 0) { - WRAP_AND_RETURN(unsigned long long); - } else if (strcmp(returnType, @encode(float)) == 0) { - WRAP_AND_RETURN(float); - } else if (strcmp(returnType, @encode(double)) == 0) { - WRAP_AND_RETURN(double); - } else if (strcmp(returnType, @encode(BOOL)) == 0) { - WRAP_AND_RETURN(BOOL); - } else if (strcmp(returnType, @encode(char *)) == 0) { - WRAP_AND_RETURN(const char *); - } else if (strcmp(returnType, @encode(void)) == 0) { - return RACUnit.defaultUnit; - } else { - NSUInteger valueSize = 0; - NSGetSizeAndAlignment(returnType, &valueSize, NULL); - - unsigned char valueBytes[valueSize]; - [self getReturnValue:valueBytes]; - - return [NSValue valueWithBytes:valueBytes objCType:returnType]; - } - - return nil; + do { \ + type val = 0; \ + [self getReturnValue:&val]; \ + return @(val); \ + } while (0) + + const char *returnType = self.methodSignature.methodReturnType; + // Skip const type qualifier. + if (returnType[0] == 'r') { + returnType++; + } + + if (strcmp(returnType, @encode(id)) == 0 || strcmp(returnType, @encode(Class)) == 0 || strcmp(returnType, @encode(void (^)(void))) == 0) { + __autoreleasing id returnObj; + [self getReturnValue:&returnObj]; + return returnObj; + } else if (strcmp(returnType, @encode(char)) == 0) { + WRAP_AND_RETURN(char); + } else if (strcmp(returnType, @encode(int)) == 0) { + WRAP_AND_RETURN(int); + } else if (strcmp(returnType, @encode(short)) == 0) { + WRAP_AND_RETURN(short); + } else if (strcmp(returnType, @encode(long)) == 0) { + WRAP_AND_RETURN(long); + } else if (strcmp(returnType, @encode(long long)) == 0) { + WRAP_AND_RETURN(long long); + } else if (strcmp(returnType, @encode(unsigned char)) == 0) { + WRAP_AND_RETURN(unsigned char); + } else if (strcmp(returnType, @encode(unsigned int)) == 0) { + WRAP_AND_RETURN(unsigned int); + } else if (strcmp(returnType, @encode(unsigned short)) == 0) { + WRAP_AND_RETURN(unsigned short); + } else if (strcmp(returnType, @encode(unsigned long)) == 0) { + WRAP_AND_RETURN(unsigned long); + } else if (strcmp(returnType, @encode(unsigned long long)) == 0) { + WRAP_AND_RETURN(unsigned long long); + } else if (strcmp(returnType, @encode(float)) == 0) { + WRAP_AND_RETURN(float); + } else if (strcmp(returnType, @encode(double)) == 0) { + WRAP_AND_RETURN(double); + } else if (strcmp(returnType, @encode(BOOL)) == 0) { + WRAP_AND_RETURN(BOOL); + } else if (strcmp(returnType, @encode(char *)) == 0) { + WRAP_AND_RETURN(const char *); + } else if (strcmp(returnType, @encode(void)) == 0) { + return RACUnit.defaultUnit; + } else { + NSUInteger valueSize = 0; + NSGetSizeAndAlignment(returnType, &valueSize, NULL); + + unsigned char valueBytes[valueSize]; + [self getReturnValue:valueBytes]; + + return [NSValue valueWithBytes:valueBytes objCType:returnType]; + } + + return nil; #undef WRAP_AND_RETURN } diff --git a/ReactiveObjC/NSNotificationCenter+RACSupport.m b/ReactiveObjC/NSNotificationCenter+RACSupport.m index a3d615d93..0403f1142 100644 --- a/ReactiveObjC/NSNotificationCenter+RACSupport.m +++ b/ReactiveObjC/NSNotificationCenter+RACSupport.m @@ -15,17 +15,17 @@ @implementation NSNotificationCenter (RACSupport) - (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object { - @rac_unsafeify(object); - return [[RACSignal createSignal:^(id subscriber) { - @rac_strongify(object); - id observer = [self addObserverForName:notificationName object:object queue:nil usingBlock:^(NSNotification *note) { - [subscriber sendNext:note]; - }]; + @rac_unsafeify(object); + return [[RACSignal createSignal:^(id subscriber) { + @rac_strongify(object); + id observer = [self addObserverForName:notificationName object:object queue:nil usingBlock:^(NSNotification *note) { + [subscriber sendNext:note]; + }]; - return [RACDisposable disposableWithBlock:^{ - [self removeObserver:observer]; - }]; - }] setNameWithFormat:@"-rac_addObserverForName: %@ object: <%@: %p>", notificationName, [object class], object]; + return [RACDisposable disposableWithBlock:^{ + [self removeObserver:observer]; + }]; + }] setNameWithFormat:@"-rac_addObserverForName: %@ object: <%@: %p>", notificationName, [object class], object]; } @end diff --git a/ReactiveObjC/NSObject+RACAppKitBindings.m b/ReactiveObjC/NSObject+RACAppKitBindings.m index 1f8efc064..c3fe0c125 100644 --- a/ReactiveObjC/NSObject+RACAppKitBindings.m +++ b/ReactiveObjC/NSObject+RACAppKitBindings.m @@ -57,14 +57,14 @@ - (instancetype)initWithTarget:(id)target bindingName:(NSString *)bindingName op @implementation NSObject (RACAppKitBindings) - (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding { - return [self rac_channelToBinding:binding options:nil]; + return [self rac_channelToBinding:binding options:nil]; } - (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding options:(NSDictionary *)options { - NSCParameterAssert(binding != nil); + NSCParameterAssert(binding != nil); - RACChannelProxy *proxy = [[RACChannelProxy alloc] initWithTarget:self bindingName:binding options:options]; - return proxy.channel.leadingTerminal; + RACChannelProxy *proxy = [[RACChannelProxy alloc] initWithTarget:self bindingName:binding options:options]; + return proxy.channel.leadingTerminal; } @end @@ -74,73 +74,73 @@ @implementation RACChannelProxy #pragma mark Properties - (void)setValue:(id)value { - [self willChangeValueForKey:@rac_keypath(self.value)]; - _value = value; - [self didChangeValueForKey:@rac_keypath(self.value)]; + [self willChangeValueForKey:@rac_keypath(self.value)]; + _value = value; + [self didChangeValueForKey:@rac_keypath(self.value)]; } #pragma mark Lifecycle - (instancetype)initWithTarget:(id)target bindingName:(NSString *)bindingName options:(NSDictionary *)options { - NSCParameterAssert(target != nil); - NSCParameterAssert(bindingName != nil); + NSCParameterAssert(target != nil); + NSCParameterAssert(bindingName != nil); - self = [super init]; + self = [super init]; - _target = target; - _bindingName = [bindingName copy]; - _channel = [[RACChannel alloc] init]; + _target = target; + _bindingName = [bindingName copy]; + _channel = [[RACChannel alloc] init]; - @rac_weakify(self); + @rac_weakify(self); - void (^cleanUp)() = ^{ - @rac_strongify(self); + void (^cleanUp)() = ^{ + @rac_strongify(self); - id target = self.target; - if (target == nil) return; + id target = self.target; + if (target == nil) return; - self.target = nil; + self.target = nil; - [target unbind:bindingName]; - objc_setAssociatedObject(target, (__bridge void *)self, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - }; + [target unbind:bindingName]; + objc_setAssociatedObject(target, (__bridge void *)self, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + }; - // When the channel terminates, tear down this proxy. - [self.channel.followingTerminal subscribeError:^(NSError *error) { - cleanUp(); - } completed:cleanUp]; + // When the channel terminates, tear down this proxy. + [self.channel.followingTerminal subscribeError:^(NSError *error) { + cleanUp(); + } completed:cleanUp]; - [self.target bind:bindingName toObject:self withKeyPath:@rac_keypath(self.value) options:options]; + [self.target bind:bindingName toObject:self withKeyPath:@rac_keypath(self.value) options:options]; - // Keep the proxy alive as long as the target, or until the property subject - // terminates. - objc_setAssociatedObject(self.target, (__bridge void *)self, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + // Keep the proxy alive as long as the target, or until the property subject + // terminates. + objc_setAssociatedObject(self.target, (__bridge void *)self, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - [[self.target rac_deallocDisposable] addDisposable:[RACDisposable disposableWithBlock:^{ - @rac_strongify(self); - [self.channel.followingTerminal sendCompleted]; - }]]; + [[self.target rac_deallocDisposable] addDisposable:[RACDisposable disposableWithBlock:^{ + @rac_strongify(self); + [self.channel.followingTerminal sendCompleted]; + }]]; - RACChannelTo(self, value, options[NSNullPlaceholderBindingOption]) = self.channel.followingTerminal; - return self; + RACChannelTo(self, value, options[NSNullPlaceholderBindingOption]) = self.channel.followingTerminal; + return self; } - (void)dealloc { - [self.channel.followingTerminal sendCompleted]; + [self.channel.followingTerminal sendCompleted]; } #pragma mark NSObject - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p>{ target: %@, binding: %@ }", self.class, self, self.target, self.bindingName]; + return [NSString stringWithFormat:@"<%@: %p>{ target: %@, binding: %@ }", self.class, self, self.target, self.bindingName]; } #pragma mark NSKeyValueObserving + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { - // Generating manual notifications for `value` is simpler and more - // performant than having KVO swizzle our class and add its own logic. - return NO; + // Generating manual notifications for `value` is simpler and more + // performant than having KVO swizzle our class and add its own logic. + return NO; } @end diff --git a/ReactiveObjC/NSObject+RACDeallocating.m b/ReactiveObjC/NSObject+RACDeallocating.m index 49ed17671..0bda617ad 100644 --- a/ReactiveObjC/NSObject+RACDeallocating.m +++ b/ReactiveObjC/NSObject+RACDeallocating.m @@ -16,88 +16,88 @@ static const void *RACObjectCompoundDisposable = &RACObjectCompoundDisposable; static NSMutableSet *swizzledClasses() { - static dispatch_once_t onceToken; - static NSMutableSet *swizzledClasses = nil; - dispatch_once(&onceToken, ^{ - swizzledClasses = [[NSMutableSet alloc] init]; - }); - - return swizzledClasses; + static dispatch_once_t onceToken; + static NSMutableSet *swizzledClasses = nil; + dispatch_once(&onceToken, ^{ + swizzledClasses = [[NSMutableSet alloc] init]; + }); + + return swizzledClasses; } static void swizzleDeallocIfNeeded(Class classToSwizzle) { - @synchronized (swizzledClasses()) { - NSString *className = NSStringFromClass(classToSwizzle); - if ([swizzledClasses() containsObject:className]) return; - - SEL deallocSelector = sel_registerName("dealloc"); - - __block void (*originalDealloc)(__unsafe_unretained id, SEL) = NULL; - - id newDealloc = ^(__unsafe_unretained id self) { - RACCompoundDisposable *compoundDisposable = objc_getAssociatedObject(self, RACObjectCompoundDisposable); - [compoundDisposable dispose]; - - if (originalDealloc == NULL) { - struct objc_super superInfo = { - .receiver = self, - .super_class = class_getSuperclass(classToSwizzle) - }; - - void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper; - msgSend(&superInfo, deallocSelector); - } else { - originalDealloc(self, deallocSelector); - } - }; - - IMP newDeallocIMP = imp_implementationWithBlock(newDealloc); - - if (!class_addMethod(classToSwizzle, deallocSelector, newDeallocIMP, "v@:")) { - // The class already contains a method implementation. - Method deallocMethod = class_getInstanceMethod(classToSwizzle, deallocSelector); - - // We need to store original implementation before setting new implementation - // in case method is called at the time of setting. - originalDealloc = (__typeof__(originalDealloc))method_getImplementation(deallocMethod); - - // We need to store original implementation again, in case it just changed. - originalDealloc = (__typeof__(originalDealloc))method_setImplementation(deallocMethod, newDeallocIMP); - } - - [swizzledClasses() addObject:className]; - } + @synchronized (swizzledClasses()) { + NSString *className = NSStringFromClass(classToSwizzle); + if ([swizzledClasses() containsObject:className]) return; + + SEL deallocSelector = sel_registerName("dealloc"); + + __block void (*originalDealloc)(__unsafe_unretained id, SEL) = NULL; + + id newDealloc = ^(__unsafe_unretained id self) { + RACCompoundDisposable *compoundDisposable = objc_getAssociatedObject(self, RACObjectCompoundDisposable); + [compoundDisposable dispose]; + + if (originalDealloc == NULL) { + struct objc_super superInfo = { + .receiver = self, + .super_class = class_getSuperclass(classToSwizzle) + }; + + void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper; + msgSend(&superInfo, deallocSelector); + } else { + originalDealloc(self, deallocSelector); + } + }; + + IMP newDeallocIMP = imp_implementationWithBlock(newDealloc); + + if (!class_addMethod(classToSwizzle, deallocSelector, newDeallocIMP, "v@:")) { + // The class already contains a method implementation. + Method deallocMethod = class_getInstanceMethod(classToSwizzle, deallocSelector); + + // We need to store original implementation before setting new implementation + // in case method is called at the time of setting. + originalDealloc = (__typeof__(originalDealloc))method_getImplementation(deallocMethod); + + // We need to store original implementation again, in case it just changed. + originalDealloc = (__typeof__(originalDealloc))method_setImplementation(deallocMethod, newDeallocIMP); + } + + [swizzledClasses() addObject:className]; + } } @implementation NSObject (RACDeallocating) - (RACSignal *)rac_willDeallocSignal { - RACSignal *signal = objc_getAssociatedObject(self, _cmd); - if (signal != nil) return signal; + RACSignal *signal = objc_getAssociatedObject(self, _cmd); + if (signal != nil) return signal; - RACReplaySubject *subject = [RACReplaySubject subject]; + RACReplaySubject *subject = [RACReplaySubject subject]; - [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - [subject sendCompleted]; - }]]; + [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + [subject sendCompleted]; + }]]; - objc_setAssociatedObject(self, _cmd, subject, OBJC_ASSOCIATION_RETAIN); + objc_setAssociatedObject(self, _cmd, subject, OBJC_ASSOCIATION_RETAIN); - return subject; + return subject; } - (RACCompoundDisposable *)rac_deallocDisposable { - @synchronized (self) { - RACCompoundDisposable *compoundDisposable = objc_getAssociatedObject(self, RACObjectCompoundDisposable); - if (compoundDisposable != nil) return compoundDisposable; + @synchronized (self) { + RACCompoundDisposable *compoundDisposable = objc_getAssociatedObject(self, RACObjectCompoundDisposable); + if (compoundDisposable != nil) return compoundDisposable; - swizzleDeallocIfNeeded(self.class); + swizzleDeallocIfNeeded(self.class); - compoundDisposable = [RACCompoundDisposable compoundDisposable]; - objc_setAssociatedObject(self, RACObjectCompoundDisposable, compoundDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + compoundDisposable = [RACCompoundDisposable compoundDisposable]; + objc_setAssociatedObject(self, RACObjectCompoundDisposable, compoundDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - return compoundDisposable; - } + return compoundDisposable; + } } @end diff --git a/ReactiveObjC/NSObject+RACDescription.m b/ReactiveObjC/NSObject+RACDescription.m index 1b214277e..56b1ca104 100644 --- a/ReactiveObjC/NSObject+RACDescription.m +++ b/ReactiveObjC/NSObject+RACDescription.m @@ -12,7 +12,7 @@ @implementation NSValue (RACDescription) - (NSString *)rac_description { - return self.description; + return self.description; } @end @@ -20,7 +20,7 @@ - (NSString *)rac_description { @implementation NSString (RACDescription) - (NSString *)rac_description { - return self.description; + return self.description; } @end @@ -28,23 +28,23 @@ - (NSString *)rac_description { @implementation RACTuple (RACDescription) - (NSString *)rac_description { - if (getenv("RAC_DEBUG_SIGNAL_NAMES") != NULL) { - return self.allObjects.description; - } else { - return @"(description skipped)"; - } + if (getenv("RAC_DEBUG_SIGNAL_NAMES") != NULL) { + return self.allObjects.description; + } else { + return @"(description skipped)"; + } } @end NSString *RACDescription(id object) { - if (getenv("RAC_DEBUG_SIGNAL_NAMES") != NULL) { - if ([object respondsToSelector:@selector(rac_description)]) { - return [object rac_description]; - } else { - return [[NSString alloc] initWithFormat:@"<%@: %p>", [object class], object]; - } - } else { - return @"(description skipped)"; - } + if (getenv("RAC_DEBUG_SIGNAL_NAMES") != NULL) { + if ([object respondsToSelector:@selector(rac_description)]) { + return [object rac_description]; + } else { + return [[NSString alloc] initWithFormat:@"<%@: %p>", [object class], object]; + } + } else { + return @"(description skipped)"; + } } diff --git a/ReactiveObjC/NSObject+RACKVOWrapper.m b/ReactiveObjC/NSObject+RACKVOWrapper.m index ce295ddfb..c1584e152 100644 --- a/ReactiveObjC/NSObject+RACKVOWrapper.m +++ b/ReactiveObjC/NSObject+RACKVOWrapper.m @@ -19,182 +19,182 @@ @implementation NSObject (RACKVOWrapper) - (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver block:(void (^)(id, NSDictionary *, BOOL, BOOL))block { - NSCParameterAssert(block != nil); - NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0); - - keyPath = [keyPath copy]; - - NSObject *strongObserver = weakObserver; - - NSArray *keyPathComponents = keyPath.rac_keyPathComponents; - BOOL keyPathHasOneComponent = (keyPathComponents.count == 1); - NSString *keyPathHead = keyPathComponents[0]; - NSString *keyPathTail = keyPath.rac_keyPathByDeletingFirstKeyPathComponent; - - RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; - - // The disposable that groups all disposal necessary to clean up the callbacks - // added to the value of the first key path component. - RACSerialDisposable *firstComponentSerialDisposable = [RACSerialDisposable serialDisposableWithDisposable:[RACCompoundDisposable compoundDisposable]]; - RACCompoundDisposable * (^firstComponentDisposable)(void) = ^{ - return (RACCompoundDisposable *)firstComponentSerialDisposable.disposable; - }; - - [disposable addDisposable:firstComponentSerialDisposable]; - - BOOL shouldAddDeallocObserver = NO; - - objc_property_t property = class_getProperty(object_getClass(self), keyPathHead.UTF8String); - if (property != NULL) { - rac_propertyAttributes *attributes = rac_copyPropertyAttributes(property); - if (attributes != NULL) { - @rac_onExit { - free(attributes); - }; - - BOOL isObject = attributes->objectClass != nil || strstr(attributes->type, @encode(id)) == attributes->type; - BOOL isProtocol = attributes->objectClass == NSClassFromString(@"Protocol"); - BOOL isBlock = strcmp(attributes->type, @encode(void(^)(void))) == 0; - BOOL isWeak = attributes->weak; - - // If this property isn't actually an object (or is a Class object), - // no point in observing the deallocation of the wrapper returned by - // KVC. - // - // If this property is an object, but not declared `weak`, we - // don't need to watch for it spontaneously being set to nil. - // - // Attempting to observe non-weak properties will result in - // broken behavior for dynamic getters, so don't even try. - shouldAddDeallocObserver = isObject && isWeak && !isBlock && !isProtocol; - } - } - - // Adds the callback block to the value's deallocation. Also adds the logic to - // clean up the callback to the firstComponentDisposable. - void (^addDeallocObserverToPropertyValue)(NSObject *) = ^(NSObject *value) { - if (!shouldAddDeallocObserver) return; - - // If a key path value is the observer, commonly when a key path begins - // with "self", we prevent deallocation triggered callbacks for any such key - // path components. Thus, the observer's deallocation is not considered a - // change to the key path. - if (value == weakObserver) return; - - NSDictionary *change = @{ - NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting), - NSKeyValueChangeNewKey: NSNull.null, - }; - - RACCompoundDisposable *valueDisposable = value.rac_deallocDisposable; - RACDisposable *deallocDisposable = [RACDisposable disposableWithBlock:^{ - block(nil, change, YES, keyPathHasOneComponent); - }]; - - [valueDisposable addDisposable:deallocDisposable]; - [firstComponentDisposable() addDisposable:[RACDisposable disposableWithBlock:^{ - [valueDisposable removeDisposable:deallocDisposable]; - }]]; - }; - - // Adds the callback block to the remaining path components on the value. Also - // adds the logic to clean up the callbacks to the firstComponentDisposable. - void (^addObserverToValue)(NSObject *) = ^(NSObject *value) { - RACDisposable *observerDisposable = [value rac_observeKeyPath:keyPathTail options:(options & ~NSKeyValueObservingOptionInitial) observer:weakObserver block:block]; - [firstComponentDisposable() addDisposable:observerDisposable]; - }; - - // Observe only the first key path component, when the value changes clean up - // the callbacks on the old value, add callbacks to the new value and call the - // callback block as needed. - // - // Note this does not use NSKeyValueObservingOptionInitial so this only - // handles changes to the value, callbacks to the initial value must be added - // separately. - NSKeyValueObservingOptions trampolineOptions = (options | NSKeyValueObservingOptionPrior) & ~NSKeyValueObservingOptionInitial; - RACKVOTrampoline *trampoline = [[RACKVOTrampoline alloc] initWithTarget:self observer:strongObserver keyPath:keyPathHead options:trampolineOptions block:^(id trampolineTarget, id trampolineObserver, NSDictionary *change) { - // If this is a prior notification, clean up all the callbacks added to the - // previous value and call the callback block. Everything else is deferred - // until after we get the notification after the change. - if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { - [firstComponentDisposable() dispose]; - - if ((options & NSKeyValueObservingOptionPrior) != 0) { - block([trampolineTarget valueForKeyPath:keyPath], change, NO, keyPathHasOneComponent); - } - - return; - } - - // From here the notification is not prior. - NSObject *value = [trampolineTarget valueForKey:keyPathHead]; - - // If the value has changed but is nil, there is no need to add callbacks to - // it, just call the callback block. - if (value == nil) { - block(nil, change, NO, keyPathHasOneComponent); - return; - } - - // From here the notification is not prior and the value is not nil. - - // Create a new firstComponentDisposable while getting rid of the old one at - // the same time, in case this is being called concurrently. - RACDisposable *oldFirstComponentDisposable = [firstComponentSerialDisposable swapInDisposable:[RACCompoundDisposable compoundDisposable]]; - [oldFirstComponentDisposable dispose]; - - addDeallocObserverToPropertyValue(value); - - // If there are no further key path components, there is no need to add the - // other callbacks, just call the callback block with the value itself. - if (keyPathHasOneComponent) { - block(value, change, NO, keyPathHasOneComponent); - return; - } - - // The value has changed, is not nil, and there are more key path components - // to consider. Add the callbacks to the value for the remaining key path - // components and call the callback block with the current value of the full - // key path. - addObserverToValue(value); - block([value valueForKeyPath:keyPathTail], change, NO, keyPathHasOneComponent); - }]; - - // Stop the KVO observation when this one is disposed of. - [disposable addDisposable:trampoline]; - - // Add the callbacks to the initial value if needed. - NSObject *value = [self valueForKey:keyPathHead]; - if (value != nil) { - addDeallocObserverToPropertyValue(value); - - if (!keyPathHasOneComponent) { - addObserverToValue(value); - } - } - - // Call the block with the initial value if needed. - if ((options & NSKeyValueObservingOptionInitial) != 0) { - id initialValue = [self valueForKeyPath:keyPath]; - NSDictionary *initialChange = @{ - NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting), - NSKeyValueChangeNewKey: initialValue ?: NSNull.null, - }; - block(initialValue, initialChange, NO, keyPathHasOneComponent); - } - - - RACCompoundDisposable *observerDisposable = strongObserver.rac_deallocDisposable; - RACCompoundDisposable *selfDisposable = self.rac_deallocDisposable; - // Dispose of this observation if the receiver or the observer deallocate. - [observerDisposable addDisposable:disposable]; - [selfDisposable addDisposable:disposable]; - - return [RACDisposable disposableWithBlock:^{ - [disposable dispose]; - [observerDisposable removeDisposable:disposable]; - [selfDisposable removeDisposable:disposable]; - }]; + NSCParameterAssert(block != nil); + NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0); + + keyPath = [keyPath copy]; + + NSObject *strongObserver = weakObserver; + + NSArray *keyPathComponents = keyPath.rac_keyPathComponents; + BOOL keyPathHasOneComponent = (keyPathComponents.count == 1); + NSString *keyPathHead = keyPathComponents[0]; + NSString *keyPathTail = keyPath.rac_keyPathByDeletingFirstKeyPathComponent; + + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + // The disposable that groups all disposal necessary to clean up the callbacks + // added to the value of the first key path component. + RACSerialDisposable *firstComponentSerialDisposable = [RACSerialDisposable serialDisposableWithDisposable:[RACCompoundDisposable compoundDisposable]]; + RACCompoundDisposable * (^firstComponentDisposable)(void) = ^{ + return (RACCompoundDisposable *)firstComponentSerialDisposable.disposable; + }; + + [disposable addDisposable:firstComponentSerialDisposable]; + + BOOL shouldAddDeallocObserver = NO; + + objc_property_t property = class_getProperty(object_getClass(self), keyPathHead.UTF8String); + if (property != NULL) { + rac_propertyAttributes *attributes = rac_copyPropertyAttributes(property); + if (attributes != NULL) { + @rac_onExit { + free(attributes); + }; + + BOOL isObject = attributes->objectClass != nil || strstr(attributes->type, @encode(id)) == attributes->type; + BOOL isProtocol = attributes->objectClass == NSClassFromString(@"Protocol"); + BOOL isBlock = strcmp(attributes->type, @encode(void(^)(void))) == 0; + BOOL isWeak = attributes->weak; + + // If this property isn't actually an object (or is a Class object), + // no point in observing the deallocation of the wrapper returned by + // KVC. + // + // If this property is an object, but not declared `weak`, we + // don't need to watch for it spontaneously being set to nil. + // + // Attempting to observe non-weak properties will result in + // broken behavior for dynamic getters, so don't even try. + shouldAddDeallocObserver = isObject && isWeak && !isBlock && !isProtocol; + } + } + + // Adds the callback block to the value's deallocation. Also adds the logic to + // clean up the callback to the firstComponentDisposable. + void (^addDeallocObserverToPropertyValue)(NSObject *) = ^(NSObject *value) { + if (!shouldAddDeallocObserver) return; + + // If a key path value is the observer, commonly when a key path begins + // with "self", we prevent deallocation triggered callbacks for any such key + // path components. Thus, the observer's deallocation is not considered a + // change to the key path. + if (value == weakObserver) return; + + NSDictionary *change = @{ + NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting), + NSKeyValueChangeNewKey: NSNull.null, + }; + + RACCompoundDisposable *valueDisposable = value.rac_deallocDisposable; + RACDisposable *deallocDisposable = [RACDisposable disposableWithBlock:^{ + block(nil, change, YES, keyPathHasOneComponent); + }]; + + [valueDisposable addDisposable:deallocDisposable]; + [firstComponentDisposable() addDisposable:[RACDisposable disposableWithBlock:^{ + [valueDisposable removeDisposable:deallocDisposable]; + }]]; + }; + + // Adds the callback block to the remaining path components on the value. Also + // adds the logic to clean up the callbacks to the firstComponentDisposable. + void (^addObserverToValue)(NSObject *) = ^(NSObject *value) { + RACDisposable *observerDisposable = [value rac_observeKeyPath:keyPathTail options:(options & ~NSKeyValueObservingOptionInitial) observer:weakObserver block:block]; + [firstComponentDisposable() addDisposable:observerDisposable]; + }; + + // Observe only the first key path component, when the value changes clean up + // the callbacks on the old value, add callbacks to the new value and call the + // callback block as needed. + // + // Note this does not use NSKeyValueObservingOptionInitial so this only + // handles changes to the value, callbacks to the initial value must be added + // separately. + NSKeyValueObservingOptions trampolineOptions = (options | NSKeyValueObservingOptionPrior) & ~NSKeyValueObservingOptionInitial; + RACKVOTrampoline *trampoline = [[RACKVOTrampoline alloc] initWithTarget:self observer:strongObserver keyPath:keyPathHead options:trampolineOptions block:^(id trampolineTarget, id trampolineObserver, NSDictionary *change) { + // If this is a prior notification, clean up all the callbacks added to the + // previous value and call the callback block. Everything else is deferred + // until after we get the notification after the change. + if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { + [firstComponentDisposable() dispose]; + + if ((options & NSKeyValueObservingOptionPrior) != 0) { + block([trampolineTarget valueForKeyPath:keyPath], change, NO, keyPathHasOneComponent); + } + + return; + } + + // From here the notification is not prior. + NSObject *value = [trampolineTarget valueForKey:keyPathHead]; + + // If the value has changed but is nil, there is no need to add callbacks to + // it, just call the callback block. + if (value == nil) { + block(nil, change, NO, keyPathHasOneComponent); + return; + } + + // From here the notification is not prior and the value is not nil. + + // Create a new firstComponentDisposable while getting rid of the old one at + // the same time, in case this is being called concurrently. + RACDisposable *oldFirstComponentDisposable = [firstComponentSerialDisposable swapInDisposable:[RACCompoundDisposable compoundDisposable]]; + [oldFirstComponentDisposable dispose]; + + addDeallocObserverToPropertyValue(value); + + // If there are no further key path components, there is no need to add the + // other callbacks, just call the callback block with the value itself. + if (keyPathHasOneComponent) { + block(value, change, NO, keyPathHasOneComponent); + return; + } + + // The value has changed, is not nil, and there are more key path components + // to consider. Add the callbacks to the value for the remaining key path + // components and call the callback block with the current value of the full + // key path. + addObserverToValue(value); + block([value valueForKeyPath:keyPathTail], change, NO, keyPathHasOneComponent); + }]; + + // Stop the KVO observation when this one is disposed of. + [disposable addDisposable:trampoline]; + + // Add the callbacks to the initial value if needed. + NSObject *value = [self valueForKey:keyPathHead]; + if (value != nil) { + addDeallocObserverToPropertyValue(value); + + if (!keyPathHasOneComponent) { + addObserverToValue(value); + } + } + + // Call the block with the initial value if needed. + if ((options & NSKeyValueObservingOptionInitial) != 0) { + id initialValue = [self valueForKeyPath:keyPath]; + NSDictionary *initialChange = @{ + NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting), + NSKeyValueChangeNewKey: initialValue ?: NSNull.null, + }; + block(initialValue, initialChange, NO, keyPathHasOneComponent); + } + + + RACCompoundDisposable *observerDisposable = strongObserver.rac_deallocDisposable; + RACCompoundDisposable *selfDisposable = self.rac_deallocDisposable; + // Dispose of this observation if the receiver or the observer deallocate. + [observerDisposable addDisposable:disposable]; + [selfDisposable addDisposable:disposable]; + + return [RACDisposable disposableWithBlock:^{ + [disposable dispose]; + [observerDisposable removeDisposable:disposable]; + [selfDisposable removeDisposable:disposable]; + }]; } @end diff --git a/ReactiveObjC/NSObject+RACLifting.m b/ReactiveObjC/NSObject+RACLifting.m index 9676da519..18292e05d 100644 --- a/ReactiveObjC/NSObject+RACLifting.m +++ b/ReactiveObjC/NSObject+RACLifting.m @@ -17,62 +17,62 @@ @implementation NSObject (RACLifting) - (RACSignal *)rac_liftSelector:(SEL)selector withSignalOfArguments:(RACSignal *)arguments { - NSCParameterAssert(selector != NULL); - NSCParameterAssert(arguments != nil); - - @rac_unsafeify(self); - - NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector]; - NSCAssert(methodSignature != nil, @"%@ does not respond to %@", self, NSStringFromSelector(selector)); - - return [[[[arguments - takeUntil:self.rac_willDeallocSignal] - map:^(RACTuple *arguments) { - @rac_strongify(self); - - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; - invocation.selector = selector; - invocation.rac_argumentsTuple = arguments; - [invocation invokeWithTarget:self]; - - return invocation.rac_returnValue; - }] - replayLast] - setNameWithFormat:@"%@ -rac_liftSelector: %s withSignalsOfArguments: %@", RACDescription(self), sel_getName(selector), arguments]; + NSCParameterAssert(selector != NULL); + NSCParameterAssert(arguments != nil); + + @rac_unsafeify(self); + + NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector]; + NSCAssert(methodSignature != nil, @"%@ does not respond to %@", self, NSStringFromSelector(selector)); + + return [[[[arguments + takeUntil:self.rac_willDeallocSignal] + map:^(RACTuple *arguments) { + @rac_strongify(self); + + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; + invocation.selector = selector; + invocation.rac_argumentsTuple = arguments; + [invocation invokeWithTarget:self]; + + return invocation.rac_returnValue; + }] + replayLast] + setNameWithFormat:@"%@ -rac_liftSelector: %s withSignalsOfArguments: %@", RACDescription(self), sel_getName(selector), arguments]; } - (RACSignal *)rac_liftSelector:(SEL)selector withSignalsFromArray:(NSArray *)signals { - NSCParameterAssert(signals != nil); - NSCParameterAssert(signals.count > 0); + NSCParameterAssert(signals != nil); + NSCParameterAssert(signals.count > 0); - NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector]; - NSCAssert(methodSignature != nil, @"%@ does not respond to %@", self, NSStringFromSelector(selector)); + NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector]; + NSCAssert(methodSignature != nil, @"%@ does not respond to %@", self, NSStringFromSelector(selector)); - NSUInteger numberOfArguments __attribute__((unused)) = methodSignature.numberOfArguments - 2; - NSCAssert(numberOfArguments == signals.count, @"Wrong number of signals for %@ (expected %lu, got %lu)", NSStringFromSelector(selector), (unsigned long)numberOfArguments, (unsigned long)signals.count); + NSUInteger numberOfArguments __attribute__((unused)) = methodSignature.numberOfArguments - 2; + NSCAssert(numberOfArguments == signals.count, @"Wrong number of signals for %@ (expected %lu, got %lu)", NSStringFromSelector(selector), (unsigned long)numberOfArguments, (unsigned long)signals.count); - return [[self - rac_liftSelector:selector withSignalOfArguments:[RACSignal combineLatest:signals]] - setNameWithFormat:@"%@ -rac_liftSelector: %s withSignalsFromArray: %@", RACDescription(self), sel_getName(selector), signals]; + return [[self + rac_liftSelector:selector withSignalOfArguments:[RACSignal combineLatest:signals]] + setNameWithFormat:@"%@ -rac_liftSelector: %s withSignalsFromArray: %@", RACDescription(self), sel_getName(selector), signals]; } - (RACSignal *)rac_liftSelector:(SEL)selector withSignals:(RACSignal *)firstSignal, ... { - NSCParameterAssert(firstSignal != nil); + NSCParameterAssert(firstSignal != nil); - NSMutableArray *signals = [NSMutableArray array]; + NSMutableArray *signals = [NSMutableArray array]; - va_list args; - va_start(args, firstSignal); - for (id currentSignal = firstSignal; currentSignal != nil; currentSignal = va_arg(args, id)) { - NSCAssert([currentSignal isKindOfClass:RACSignal.class], @"Argument %@ is not a RACSignal", currentSignal); + va_list args; + va_start(args, firstSignal); + for (id currentSignal = firstSignal; currentSignal != nil; currentSignal = va_arg(args, id)) { + NSCAssert([currentSignal isKindOfClass:RACSignal.class], @"Argument %@ is not a RACSignal", currentSignal); - [signals addObject:currentSignal]; - } - va_end(args); + [signals addObject:currentSignal]; + } + va_end(args); - return [[self - rac_liftSelector:selector withSignalsFromArray:signals] - setNameWithFormat:@"%@ -rac_liftSelector: %s withSignals: %@", RACDescription(self), sel_getName(selector), signals]; + return [[self + rac_liftSelector:selector withSignalsFromArray:signals] + setNameWithFormat:@"%@ -rac_liftSelector: %s withSignals: %@", RACDescription(self), sel_getName(selector), signals]; } @end diff --git a/ReactiveObjC/NSObject+RACPropertySubscribing.h b/ReactiveObjC/NSObject+RACPropertySubscribing.h index 6b8e49e1e..83ab94aec 100644 --- a/ReactiveObjC/NSObject+RACPropertySubscribing.h +++ b/ReactiveObjC/NSObject+RACPropertySubscribing.h @@ -48,8 +48,8 @@ /// completed if self or observer is deallocated. #define _RACObserve(TARGET, KEYPATH) \ ({ \ - __weak id target_ = (TARGET); \ - [target_ rac_valuesForKeyPath:@rac_keypath(TARGET, KEYPATH) observer:self]; \ + __weak id target_ = (TARGET); \ + [target_ rac_valuesForKeyPath:@rac_keypath(TARGET, KEYPATH) observer:self]; \ }) #if __clang__ && (__clang_major__ >= 8) @@ -57,10 +57,10 @@ #else #define RACObserve(TARGET, KEYPATH) \ ({ \ - _Pragma("clang diagnostic push") \ - _Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \ - _RACObserve(TARGET, KEYPATH) \ - _Pragma("clang diagnostic pop") \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \ + _RACObserve(TARGET, KEYPATH) \ + _Pragma("clang diagnostic pop") \ }) #endif @@ -106,15 +106,15 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END #define RACAble(...) \ - metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \ - (_RACAbleObject(self, __VA_ARGS__)) \ - (_RACAbleObject(__VA_ARGS__)) + metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \ + (_RACAbleObject(self, __VA_ARGS__)) \ + (_RACAbleObject(__VA_ARGS__)) #define _RACAbleObject(object, property) [object rac_signalForKeyPath:@rac_keypath(object, property) observer:self] #define RACAbleWithStart(...) \ - metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \ - (_RACAbleWithStartObject(self, __VA_ARGS__)) \ - (_RACAbleWithStartObject(__VA_ARGS__)) + metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \ + (_RACAbleWithStartObject(self, __VA_ARGS__)) \ + (_RACAbleWithStartObject(__VA_ARGS__)) #define _RACAbleWithStartObject(object, property) [object rac_signalWithStartingValueForKeyPath:@rac_keypath(object, property) observer:self] diff --git a/ReactiveObjC/NSObject+RACPropertySubscribing.m b/ReactiveObjC/NSObject+RACPropertySubscribing.m index 3de3ae40d..dbede782c 100644 --- a/ReactiveObjC/NSObject+RACPropertySubscribing.m +++ b/ReactiveObjC/NSObject+RACPropertySubscribing.m @@ -22,63 +22,63 @@ @implementation NSObject (RACPropertySubscribing) - (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer { - return [[[self - rac_valuesAndChangesForKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:observer] - map:^(RACTuple *value) { - // -map: because it doesn't require the block trampoline that -reduceEach: uses - return value[0]; - }] - setNameWithFormat:@"RACObserve(%@, %@)", RACDescription(self), keyPath]; + return [[[self + rac_valuesAndChangesForKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:observer] + map:^(RACTuple *value) { + // -map: because it doesn't require the block trampoline that -reduceEach: uses + return value[0]; + }] + setNameWithFormat:@"RACObserve(%@, %@)", RACDescription(self), keyPath]; } - (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver { - NSObject *strongObserver = weakObserver; - keyPath = [keyPath copy]; + NSObject *strongObserver = weakObserver; + keyPath = [keyPath copy]; - NSRecursiveLock *objectLock = [[NSRecursiveLock alloc] init]; - objectLock.name = @"org.reactivecocoa.ReactiveObjC.NSObjectRACPropertySubscribing"; + NSRecursiveLock *objectLock = [[NSRecursiveLock alloc] init]; + objectLock.name = @"org.reactivecocoa.ReactiveObjC.NSObjectRACPropertySubscribing"; - __weak NSObject *weakSelf = self; + __weak NSObject *weakSelf = self; - RACSignal *deallocSignal = [[RACSignal - zip:@[ - self.rac_willDeallocSignal, - strongObserver.rac_willDeallocSignal ?: [RACSignal never] - ]] - doCompleted:^{ - // Forces deallocation to wait if the object variables are currently - // being read on another thread. - [objectLock lock]; - @rac_onExit { - [objectLock unlock]; - }; - }]; + RACSignal *deallocSignal = [[RACSignal + zip:@[ + self.rac_willDeallocSignal, + strongObserver.rac_willDeallocSignal ?: [RACSignal never] + ]] + doCompleted:^{ + // Forces deallocation to wait if the object variables are currently + // being read on another thread. + [objectLock lock]; + @rac_onExit { + [objectLock unlock]; + }; + }]; - return [[[RACSignal - createSignal:^ RACDisposable * (id subscriber) { - // Hold onto the lock the whole time we're setting up the KVO - // observation, because any resurrection that might be caused by our - // retaining below must be balanced out by the time -dealloc returns - // (if another thread is waiting on the lock above). - [objectLock lock]; - @rac_onExit { - [objectLock unlock]; - }; + return [[[RACSignal + createSignal:^ RACDisposable * (id subscriber) { + // Hold onto the lock the whole time we're setting up the KVO + // observation, because any resurrection that might be caused by our + // retaining below must be balanced out by the time -dealloc returns + // (if another thread is waiting on the lock above). + [objectLock lock]; + @rac_onExit { + [objectLock unlock]; + }; - __strong NSObject *observer __attribute__((objc_precise_lifetime)) = weakObserver; - __strong NSObject *self __attribute__((objc_precise_lifetime)) = weakSelf; + __strong NSObject *observer __attribute__((objc_precise_lifetime)) = weakObserver; + __strong NSObject *self __attribute__((objc_precise_lifetime)) = weakSelf; - if (self == nil) { - [subscriber sendCompleted]; - return nil; - } + if (self == nil) { + [subscriber sendCompleted]; + return nil; + } - return [self rac_observeKeyPath:keyPath options:options observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - [subscriber sendNext:RACTuplePack(value, change)]; - }]; - }] - takeUntil:deallocSignal] - setNameWithFormat:@"%@ -rac_valueAndChangesForKeyPath: %@ options: %lu observer: %@", RACDescription(self), keyPath, (unsigned long)options, RACDescription(strongObserver)]; + return [self rac_observeKeyPath:keyPath options:options observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + [subscriber sendNext:RACTuplePack(value, change)]; + }]; + }] + takeUntil:deallocSignal] + setNameWithFormat:@"%@ -rac_valueAndChangesForKeyPath: %@ options: %lu observer: %@", RACDescription(self), keyPath, (unsigned long)options, RACDescription(strongObserver)]; } @end diff --git a/ReactiveObjC/NSObject+RACSelectorSignal.m b/ReactiveObjC/NSObject+RACSelectorSignal.m index 8970681fa..74b51c6a2 100644 --- a/ReactiveObjC/NSObject+RACSelectorSignal.m +++ b/ReactiveObjC/NSObject+RACSelectorSignal.m @@ -26,131 +26,131 @@ static void *RACSubclassAssociationKey = &RACSubclassAssociationKey; static NSMutableSet *swizzledClasses() { - static NSMutableSet *set; - static dispatch_once_t pred; - - dispatch_once(&pred, ^{ - set = [[NSMutableSet alloc] init]; - }); - - return set; + static NSMutableSet *set; + static dispatch_once_t pred; + + dispatch_once(&pred, ^{ + set = [[NSMutableSet alloc] init]; + }); + + return set; } @implementation NSObject (RACSelectorSignal) static BOOL RACForwardInvocation(id self, NSInvocation *invocation) { - SEL aliasSelector = RACAliasForSelector(invocation.selector); - RACSubject *subject = objc_getAssociatedObject(self, aliasSelector); + SEL aliasSelector = RACAliasForSelector(invocation.selector); + RACSubject *subject = objc_getAssociatedObject(self, aliasSelector); - Class class = object_getClass(invocation.target); - BOOL respondsToAlias = [class instancesRespondToSelector:aliasSelector]; - if (respondsToAlias) { - invocation.selector = aliasSelector; - [invocation invoke]; - } + Class class = object_getClass(invocation.target); + BOOL respondsToAlias = [class instancesRespondToSelector:aliasSelector]; + if (respondsToAlias) { + invocation.selector = aliasSelector; + [invocation invoke]; + } - if (subject == nil) return respondsToAlias; + if (subject == nil) return respondsToAlias; - [subject sendNext:invocation.rac_argumentsTuple]; - return YES; + [subject sendNext:invocation.rac_argumentsTuple]; + return YES; } static void RACSwizzleForwardInvocation(Class class) { - SEL forwardInvocationSEL = @selector(forwardInvocation:); - Method forwardInvocationMethod = class_getInstanceMethod(class, forwardInvocationSEL); - - // Preserve any existing implementation of -forwardInvocation:. - void (*originalForwardInvocation)(id, SEL, NSInvocation *) = NULL; - if (forwardInvocationMethod != NULL) { - originalForwardInvocation = (__typeof__(originalForwardInvocation))method_getImplementation(forwardInvocationMethod); - } - - // Set up a new version of -forwardInvocation:. - // - // If the selector has been passed to -rac_signalForSelector:, invoke - // the aliased method, and forward the arguments to any attached signals. - // - // If the selector has not been passed to -rac_signalForSelector:, - // invoke any existing implementation of -forwardInvocation:. If there - // was no existing implementation, throw an unrecognized selector - // exception. - id newForwardInvocation = ^(id self, NSInvocation *invocation) { - BOOL matched = RACForwardInvocation(self, invocation); - if (matched) return; - - if (originalForwardInvocation == NULL) { - [self doesNotRecognizeSelector:invocation.selector]; - } else { - originalForwardInvocation(self, forwardInvocationSEL, invocation); - } - }; - - class_replaceMethod(class, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@"); + SEL forwardInvocationSEL = @selector(forwardInvocation:); + Method forwardInvocationMethod = class_getInstanceMethod(class, forwardInvocationSEL); + + // Preserve any existing implementation of -forwardInvocation:. + void (*originalForwardInvocation)(id, SEL, NSInvocation *) = NULL; + if (forwardInvocationMethod != NULL) { + originalForwardInvocation = (__typeof__(originalForwardInvocation))method_getImplementation(forwardInvocationMethod); + } + + // Set up a new version of -forwardInvocation:. + // + // If the selector has been passed to -rac_signalForSelector:, invoke + // the aliased method, and forward the arguments to any attached signals. + // + // If the selector has not been passed to -rac_signalForSelector:, + // invoke any existing implementation of -forwardInvocation:. If there + // was no existing implementation, throw an unrecognized selector + // exception. + id newForwardInvocation = ^(id self, NSInvocation *invocation) { + BOOL matched = RACForwardInvocation(self, invocation); + if (matched) return; + + if (originalForwardInvocation == NULL) { + [self doesNotRecognizeSelector:invocation.selector]; + } else { + originalForwardInvocation(self, forwardInvocationSEL, invocation); + } + }; + + class_replaceMethod(class, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@"); } static void RACSwizzleRespondsToSelector(Class class) { - SEL respondsToSelectorSEL = @selector(respondsToSelector:); - - // Preserve existing implementation of -respondsToSelector:. - Method respondsToSelectorMethod = class_getInstanceMethod(class, respondsToSelectorSEL); - BOOL (*originalRespondsToSelector)(id, SEL, SEL) = (__typeof__(originalRespondsToSelector))method_getImplementation(respondsToSelectorMethod); - - // Set up a new version of -respondsToSelector: that returns YES for methods - // added by -rac_signalForSelector:. - // - // If the selector has a method defined on the receiver's actual class, and - // if that method's implementation is _objc_msgForward, then returns whether - // the instance has a signal for the selector. - // Otherwise, call the original -respondsToSelector:. - id newRespondsToSelector = ^ BOOL (id self, SEL selector) { - Method method = rac_getImmediateInstanceMethod(class, selector); - - if (method != NULL && method_getImplementation(method) == _objc_msgForward) { - SEL aliasSelector = RACAliasForSelector(selector); - if (objc_getAssociatedObject(self, aliasSelector) != nil) return YES; - } - - return originalRespondsToSelector(self, respondsToSelectorSEL, selector); - }; - - class_replaceMethod(class, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), method_getTypeEncoding(respondsToSelectorMethod)); + SEL respondsToSelectorSEL = @selector(respondsToSelector:); + + // Preserve existing implementation of -respondsToSelector:. + Method respondsToSelectorMethod = class_getInstanceMethod(class, respondsToSelectorSEL); + BOOL (*originalRespondsToSelector)(id, SEL, SEL) = (__typeof__(originalRespondsToSelector))method_getImplementation(respondsToSelectorMethod); + + // Set up a new version of -respondsToSelector: that returns YES for methods + // added by -rac_signalForSelector:. + // + // If the selector has a method defined on the receiver's actual class, and + // if that method's implementation is _objc_msgForward, then returns whether + // the instance has a signal for the selector. + // Otherwise, call the original -respondsToSelector:. + id newRespondsToSelector = ^ BOOL (id self, SEL selector) { + Method method = rac_getImmediateInstanceMethod(class, selector); + + if (method != NULL && method_getImplementation(method) == _objc_msgForward) { + SEL aliasSelector = RACAliasForSelector(selector); + if (objc_getAssociatedObject(self, aliasSelector) != nil) return YES; + } + + return originalRespondsToSelector(self, respondsToSelectorSEL, selector); + }; + + class_replaceMethod(class, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), method_getTypeEncoding(respondsToSelectorMethod)); } static void RACSwizzleGetClass(Class class, Class statedClass) { - SEL selector = @selector(class); - Method method = class_getInstanceMethod(class, selector); - IMP newIMP = imp_implementationWithBlock(^(id self) { - return statedClass; - }); - class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(method)); + SEL selector = @selector(class); + Method method = class_getInstanceMethod(class, selector); + IMP newIMP = imp_implementationWithBlock(^(id self) { + return statedClass; + }); + class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(method)); } static void RACSwizzleMethodSignatureForSelector(Class class) { - IMP newIMP = imp_implementationWithBlock(^(id self, SEL selector) { - // Don't send the -class message to the receiver because we've changed - // that to return the original class. - Class actualClass = object_getClass(self); - Method method = class_getInstanceMethod(actualClass, selector); - if (method == NULL) { - // Messages that the original class dynamically implements fall - // here. - // - // Call the original class' -methodSignatureForSelector:. - struct objc_super target = { - .super_class = class_getSuperclass(class), - .receiver = self, - }; - NSMethodSignature * (*messageSend)(struct objc_super *, SEL, SEL) = (__typeof__(messageSend))objc_msgSendSuper; - return messageSend(&target, @selector(methodSignatureForSelector:), selector); - } - - char const *encoding = method_getTypeEncoding(method); - return [NSMethodSignature signatureWithObjCTypes:encoding]; - }); - - SEL selector = @selector(methodSignatureForSelector:); - Method methodSignatureForSelectorMethod = class_getInstanceMethod(class, selector); - class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(methodSignatureForSelectorMethod)); + IMP newIMP = imp_implementationWithBlock(^(id self, SEL selector) { + // Don't send the -class message to the receiver because we've changed + // that to return the original class. + Class actualClass = object_getClass(self); + Method method = class_getInstanceMethod(actualClass, selector); + if (method == NULL) { + // Messages that the original class dynamically implements fall + // here. + // + // Call the original class' -methodSignatureForSelector:. + struct objc_super target = { + .super_class = class_getSuperclass(class), + .receiver = self, + }; + NSMethodSignature * (*messageSend)(struct objc_super *, SEL, SEL) = (__typeof__(messageSend))objc_msgSendSuper; + return messageSend(&target, @selector(methodSignatureForSelector:), selector); + } + + char const *encoding = method_getTypeEncoding(method); + return [NSMethodSignature signatureWithObjCTypes:encoding]; + }); + + SEL selector = @selector(methodSignatureForSelector:); + Method methodSignatureForSelectorMethod = class_getInstanceMethod(class, selector); + class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(methodSignatureForSelectorMethod)); } // It's hard to tell which struct return types use _objc_msgForward, and @@ -158,176 +158,176 @@ static void RACSwizzleMethodSignatureForSelector(Class class) { // union, complex and vector return types. static void RACCheckTypeEncoding(const char *typeEncoding) { #if !NS_BLOCK_ASSERTIONS - // Some types, including vector types, are not encoded. In these cases the - // signature starts with the size of the argument frame. - NSCAssert(*typeEncoding < '1' || *typeEncoding > '9', @"unknown method return type not supported in type encoding: %s", typeEncoding); - NSCAssert(strstr(typeEncoding, "(") != typeEncoding, @"union method return type not supported"); - NSCAssert(strstr(typeEncoding, "{") != typeEncoding, @"struct method return type not supported"); - NSCAssert(strstr(typeEncoding, "[") != typeEncoding, @"array method return type not supported"); - NSCAssert(strstr(typeEncoding, @encode(_Complex float)) != typeEncoding, @"complex float method return type not supported"); - NSCAssert(strstr(typeEncoding, @encode(_Complex double)) != typeEncoding, @"complex double method return type not supported"); - NSCAssert(strstr(typeEncoding, @encode(_Complex long double)) != typeEncoding, @"complex long double method return type not supported"); + // Some types, including vector types, are not encoded. In these cases the + // signature starts with the size of the argument frame. + NSCAssert(*typeEncoding < '1' || *typeEncoding > '9', @"unknown method return type not supported in type encoding: %s", typeEncoding); + NSCAssert(strstr(typeEncoding, "(") != typeEncoding, @"union method return type not supported"); + NSCAssert(strstr(typeEncoding, "{") != typeEncoding, @"struct method return type not supported"); + NSCAssert(strstr(typeEncoding, "[") != typeEncoding, @"array method return type not supported"); + NSCAssert(strstr(typeEncoding, @encode(_Complex float)) != typeEncoding, @"complex float method return type not supported"); + NSCAssert(strstr(typeEncoding, @encode(_Complex double)) != typeEncoding, @"complex double method return type not supported"); + NSCAssert(strstr(typeEncoding, @encode(_Complex long double)) != typeEncoding, @"complex long double method return type not supported"); #endif // !NS_BLOCK_ASSERTIONS } static RACSignal *NSObjectRACSignalForSelector(NSObject *self, SEL selector, Protocol *protocol) { - SEL aliasSelector = RACAliasForSelector(selector); - - @synchronized (self) { - RACSubject *subject = objc_getAssociatedObject(self, aliasSelector); - if (subject != nil) return subject; - - Class class = RACSwizzleClass(self); - NSCAssert(class != nil, @"Could not swizzle class of %@", self); - - subject = [[RACSubject subject] setNameWithFormat:@"%@ -rac_signalForSelector: %s", RACDescription(self), sel_getName(selector)]; - objc_setAssociatedObject(self, aliasSelector, subject, OBJC_ASSOCIATION_RETAIN); - - [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - [subject sendCompleted]; - }]]; - - @synchronized (class) { - Method targetMethod = class_getInstanceMethod(class, selector); - if (targetMethod == NULL) { - const char *typeEncoding; - if (protocol == NULL) { - typeEncoding = RACSignatureForUndefinedSelector(selector); - } else { - // Look for the selector as an optional instance method. - struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); - - if (methodDescription.name == NULL) { - // Then fall back to looking for a required instance - // method. - methodDescription = protocol_getMethodDescription(protocol, selector, YES, YES); - NSCAssert(methodDescription.name != NULL, @"Selector %@ does not exist in <%s>", NSStringFromSelector(selector), protocol_getName(protocol)); - } - - typeEncoding = methodDescription.types; - } - - RACCheckTypeEncoding(typeEncoding); - - // Define the selector to call -forwardInvocation:. - if (!class_addMethod(class, selector, _objc_msgForward, typeEncoding)) { - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"A race condition occurred implementing %@ on class %@", nil), NSStringFromSelector(selector), class], - NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Invoke -rac_signalForSelector: again to override the implementation.", nil) - }; - - return [RACSignal error:[NSError errorWithDomain:RACSelectorSignalErrorDomain code:RACSelectorSignalErrorMethodSwizzlingRace userInfo:userInfo]]; - } - } else if (method_getImplementation(targetMethod) != _objc_msgForward) { - // Make a method alias for the existing method implementation. - const char *typeEncoding = method_getTypeEncoding(targetMethod); - - RACCheckTypeEncoding(typeEncoding); - - BOOL addedAlias __attribute__((unused)) = class_addMethod(class, aliasSelector, method_getImplementation(targetMethod), typeEncoding); - NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), class); - - // Redefine the selector to call -forwardInvocation:. - class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod)); - } - - return subject; - } - } + SEL aliasSelector = RACAliasForSelector(selector); + + @synchronized (self) { + RACSubject *subject = objc_getAssociatedObject(self, aliasSelector); + if (subject != nil) return subject; + + Class class = RACSwizzleClass(self); + NSCAssert(class != nil, @"Could not swizzle class of %@", self); + + subject = [[RACSubject subject] setNameWithFormat:@"%@ -rac_signalForSelector: %s", RACDescription(self), sel_getName(selector)]; + objc_setAssociatedObject(self, aliasSelector, subject, OBJC_ASSOCIATION_RETAIN); + + [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + [subject sendCompleted]; + }]]; + + @synchronized (class) { + Method targetMethod = class_getInstanceMethod(class, selector); + if (targetMethod == NULL) { + const char *typeEncoding; + if (protocol == NULL) { + typeEncoding = RACSignatureForUndefinedSelector(selector); + } else { + // Look for the selector as an optional instance method. + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + if (methodDescription.name == NULL) { + // Then fall back to looking for a required instance + // method. + methodDescription = protocol_getMethodDescription(protocol, selector, YES, YES); + NSCAssert(methodDescription.name != NULL, @"Selector %@ does not exist in <%s>", NSStringFromSelector(selector), protocol_getName(protocol)); + } + + typeEncoding = methodDescription.types; + } + + RACCheckTypeEncoding(typeEncoding); + + // Define the selector to call -forwardInvocation:. + if (!class_addMethod(class, selector, _objc_msgForward, typeEncoding)) { + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"A race condition occurred implementing %@ on class %@", nil), NSStringFromSelector(selector), class], + NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Invoke -rac_signalForSelector: again to override the implementation.", nil) + }; + + return [RACSignal error:[NSError errorWithDomain:RACSelectorSignalErrorDomain code:RACSelectorSignalErrorMethodSwizzlingRace userInfo:userInfo]]; + } + } else if (method_getImplementation(targetMethod) != _objc_msgForward) { + // Make a method alias for the existing method implementation. + const char *typeEncoding = method_getTypeEncoding(targetMethod); + + RACCheckTypeEncoding(typeEncoding); + + BOOL addedAlias __attribute__((unused)) = class_addMethod(class, aliasSelector, method_getImplementation(targetMethod), typeEncoding); + NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), class); + + // Redefine the selector to call -forwardInvocation:. + class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod)); + } + + return subject; + } + } } static SEL RACAliasForSelector(SEL originalSelector) { - NSString *selectorName = NSStringFromSelector(originalSelector); - return NSSelectorFromString([RACSignalForSelectorAliasPrefix stringByAppendingString:selectorName]); + NSString *selectorName = NSStringFromSelector(originalSelector); + return NSSelectorFromString([RACSignalForSelectorAliasPrefix stringByAppendingString:selectorName]); } static const char *RACSignatureForUndefinedSelector(SEL selector) { - const char *name = sel_getName(selector); - NSMutableString *signature = [NSMutableString stringWithString:@"v@:"]; + const char *name = sel_getName(selector); + NSMutableString *signature = [NSMutableString stringWithString:@"v@:"]; - while ((name = strchr(name, ':')) != NULL) { - [signature appendString:@"@"]; - name++; - } + while ((name = strchr(name, ':')) != NULL) { + [signature appendString:@"@"]; + name++; + } - return signature.UTF8String; + return signature.UTF8String; } static Class RACSwizzleClass(NSObject *self) { - Class statedClass = self.class; - Class baseClass = object_getClass(self); - - // The "known dynamic subclass" is the subclass generated by RAC. - // It's stored as an associated object on every instance that's already - // been swizzled, so that even if something else swizzles the class of - // this instance, we can still access the RAC generated subclass. - Class knownDynamicSubclass = objc_getAssociatedObject(self, RACSubclassAssociationKey); - if (knownDynamicSubclass != nil) return knownDynamicSubclass; - - NSString *className = NSStringFromClass(baseClass); - - if (statedClass != baseClass) { - // If the class is already lying about what it is, it's probably a KVO - // dynamic subclass or something else that we shouldn't subclass - // ourselves. - // - // Just swizzle -forwardInvocation: in-place. Since the object's class - // was almost certainly dynamically changed, we shouldn't see another of - // these classes in the hierarchy. - // - // Additionally, swizzle -respondsToSelector: because the default - // implementation may be ignorant of methods added to this class. - @synchronized (swizzledClasses()) { - if (![swizzledClasses() containsObject:className]) { - RACSwizzleForwardInvocation(baseClass); - RACSwizzleRespondsToSelector(baseClass); - RACSwizzleGetClass(baseClass, statedClass); - RACSwizzleGetClass(object_getClass(baseClass), statedClass); - RACSwizzleMethodSignatureForSelector(baseClass); - [swizzledClasses() addObject:className]; - } - } - - return baseClass; - } - - @synchronized (baseClass) { - const char *subclassName = [className stringByAppendingString:RACSubclassSuffix].UTF8String; - Class subclass = objc_getClass(subclassName); - - if (subclass == nil) { - subclass = objc_allocateClassPair(baseClass, subclassName, 0); - if (subclass == nil) return nil; - - RACSwizzleForwardInvocation(subclass); - RACSwizzleRespondsToSelector(subclass); - - RACSwizzleGetClass(subclass, statedClass); - RACSwizzleGetClass(object_getClass(subclass), statedClass); - - RACSwizzleMethodSignatureForSelector(subclass); - - objc_registerClassPair(subclass); - } - - object_setClass(self, subclass); - objc_setAssociatedObject(self, RACSubclassAssociationKey, subclass, OBJC_ASSOCIATION_ASSIGN); - return subclass; - } + Class statedClass = self.class; + Class baseClass = object_getClass(self); + + // The "known dynamic subclass" is the subclass generated by RAC. + // It's stored as an associated object on every instance that's already + // been swizzled, so that even if something else swizzles the class of + // this instance, we can still access the RAC generated subclass. + Class knownDynamicSubclass = objc_getAssociatedObject(self, RACSubclassAssociationKey); + if (knownDynamicSubclass != nil) return knownDynamicSubclass; + + NSString *className = NSStringFromClass(baseClass); + + if (statedClass != baseClass) { + // If the class is already lying about what it is, it's probably a KVO + // dynamic subclass or something else that we shouldn't subclass + // ourselves. + // + // Just swizzle -forwardInvocation: in-place. Since the object's class + // was almost certainly dynamically changed, we shouldn't see another of + // these classes in the hierarchy. + // + // Additionally, swizzle -respondsToSelector: because the default + // implementation may be ignorant of methods added to this class. + @synchronized (swizzledClasses()) { + if (![swizzledClasses() containsObject:className]) { + RACSwizzleForwardInvocation(baseClass); + RACSwizzleRespondsToSelector(baseClass); + RACSwizzleGetClass(baseClass, statedClass); + RACSwizzleGetClass(object_getClass(baseClass), statedClass); + RACSwizzleMethodSignatureForSelector(baseClass); + [swizzledClasses() addObject:className]; + } + } + + return baseClass; + } + + @synchronized (baseClass) { + const char *subclassName = [className stringByAppendingString:RACSubclassSuffix].UTF8String; + Class subclass = objc_getClass(subclassName); + + if (subclass == nil) { + subclass = objc_allocateClassPair(baseClass, subclassName, 0); + if (subclass == nil) return nil; + + RACSwizzleForwardInvocation(subclass); + RACSwizzleRespondsToSelector(subclass); + + RACSwizzleGetClass(subclass, statedClass); + RACSwizzleGetClass(object_getClass(subclass), statedClass); + + RACSwizzleMethodSignatureForSelector(subclass); + + objc_registerClassPair(subclass); + } + + object_setClass(self, subclass); + objc_setAssociatedObject(self, RACSubclassAssociationKey, subclass, OBJC_ASSOCIATION_ASSIGN); + return subclass; + } } - (RACSignal *)rac_signalForSelector:(SEL)selector { - NSCParameterAssert(selector != NULL); + NSCParameterAssert(selector != NULL); - return NSObjectRACSignalForSelector(self, selector, NULL); + return NSObjectRACSignalForSelector(self, selector, NULL); } - (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol { - NSCParameterAssert(selector != NULL); - NSCParameterAssert(protocol != NULL); + NSCParameterAssert(selector != NULL); + NSCParameterAssert(protocol != NULL); - return NSObjectRACSignalForSelector(self, selector, protocol); + return NSObjectRACSignalForSelector(self, selector, protocol); } @end diff --git a/ReactiveObjC/NSOrderedSet+RACSequenceAdditions.m b/ReactiveObjC/NSOrderedSet+RACSequenceAdditions.m index 31792d457..dd6f73b30 100644 --- a/ReactiveObjC/NSOrderedSet+RACSequenceAdditions.m +++ b/ReactiveObjC/NSOrderedSet+RACSequenceAdditions.m @@ -12,8 +12,8 @@ @implementation NSOrderedSet (RACSequenceAdditions) - (RACSequence *)rac_sequence { - // TODO: First class support for ordered set sequences. - return self.array.rac_sequence; + // TODO: First class support for ordered set sequences. + return self.array.rac_sequence; } @end diff --git a/ReactiveObjC/NSSet+RACSequenceAdditions.m b/ReactiveObjC/NSSet+RACSequenceAdditions.m index 8c7355fac..d1d412270 100644 --- a/ReactiveObjC/NSSet+RACSequenceAdditions.m +++ b/ReactiveObjC/NSSet+RACSequenceAdditions.m @@ -12,8 +12,8 @@ @implementation NSSet (RACSequenceAdditions) - (RACSequence *)rac_sequence { - // TODO: First class support for set sequences. - return self.allObjects.rac_sequence; + // TODO: First class support for set sequences. + return self.allObjects.rac_sequence; } @end diff --git a/ReactiveObjC/NSString+RACKeyPathUtilities.m b/ReactiveObjC/NSString+RACKeyPathUtilities.m index d62c0521b..1c32be1a8 100644 --- a/ReactiveObjC/NSString+RACKeyPathUtilities.m +++ b/ReactiveObjC/NSString+RACKeyPathUtilities.m @@ -11,26 +11,26 @@ @implementation NSString (RACKeyPathUtilities) - (NSArray *)rac_keyPathComponents { - if (self.length == 0) { - return nil; - } - return [self componentsSeparatedByString:@"."]; + if (self.length == 0) { + return nil; + } + return [self componentsSeparatedByString:@"."]; } - (NSString *)rac_keyPathByDeletingLastKeyPathComponent { - NSUInteger lastDotIndex = [self rangeOfString:@"." options:NSBackwardsSearch].location; - if (lastDotIndex == NSNotFound) { - return nil; - } - return [self substringToIndex:lastDotIndex]; + NSUInteger lastDotIndex = [self rangeOfString:@"." options:NSBackwardsSearch].location; + if (lastDotIndex == NSNotFound) { + return nil; + } + return [self substringToIndex:lastDotIndex]; } - (NSString *)rac_keyPathByDeletingFirstKeyPathComponent { - NSUInteger firstDotIndex = [self rangeOfString:@"."].location; - if (firstDotIndex == NSNotFound) { - return nil; - } - return [self substringFromIndex:firstDotIndex + 1]; + NSUInteger firstDotIndex = [self rangeOfString:@"."].location; + if (firstDotIndex == NSNotFound) { + return nil; + } + return [self substringFromIndex:firstDotIndex + 1]; } @end diff --git a/ReactiveObjC/NSString+RACSequenceAdditions.m b/ReactiveObjC/NSString+RACSequenceAdditions.m index cbcc31b07..01127a2f0 100644 --- a/ReactiveObjC/NSString+RACSequenceAdditions.m +++ b/ReactiveObjC/NSString+RACSequenceAdditions.m @@ -12,7 +12,7 @@ @implementation NSString (RACSequenceAdditions) - (RACSequence *)rac_sequence { - return [RACStringSequence sequenceWithString:self offset:0]; + return [RACStringSequence sequenceWithString:self offset:0]; } @end diff --git a/ReactiveObjC/NSString+RACSupport.m b/ReactiveObjC/NSString+RACSupport.m index 033bb5665..8af4dcba7 100644 --- a/ReactiveObjC/NSString+RACSupport.m +++ b/ReactiveObjC/NSString+RACSupport.m @@ -13,25 +13,25 @@ @implementation NSString (RACSupport) + (RACSignal *)rac_readContentsOfURL:(NSURL *)URL usedEncoding:(NSStringEncoding *)encoding scheduler:(RACScheduler *)scheduler { - NSCParameterAssert(URL != nil); - NSCParameterAssert(encoding != nil); - NSCParameterAssert(scheduler != nil); - - RACReplaySubject *subject = [RACReplaySubject subject]; - [subject setNameWithFormat:@"+rac_readContentsOfURL: %@ usedEncoding:scheduler: %@", URL, scheduler]; - - [scheduler schedule:^{ - NSError *error = nil; - NSString *string = [NSString stringWithContentsOfURL:URL usedEncoding:encoding error:&error]; - if (string == nil) { - [subject sendError:error]; - } else { - [subject sendNext:string]; - [subject sendCompleted]; - } - }]; - - return subject; + NSCParameterAssert(URL != nil); + NSCParameterAssert(encoding != nil); + NSCParameterAssert(scheduler != nil); + + RACReplaySubject *subject = [RACReplaySubject subject]; + [subject setNameWithFormat:@"+rac_readContentsOfURL: %@ usedEncoding:scheduler: %@", URL, scheduler]; + + [scheduler schedule:^{ + NSError *error = nil; + NSString *string = [NSString stringWithContentsOfURL:URL usedEncoding:encoding error:&error]; + if (string == nil) { + [subject sendError:error]; + } else { + [subject sendNext:string]; + [subject sendCompleted]; + } + }]; + + return subject; } @end diff --git a/ReactiveObjC/NSText+RACSignalSupport.m b/ReactiveObjC/NSText+RACSignalSupport.m index 701c29266..d1a8c8e75 100644 --- a/ReactiveObjC/NSText+RACSignalSupport.m +++ b/ReactiveObjC/NSText+RACSignalSupport.m @@ -16,23 +16,23 @@ @implementation NSText (RACSignalSupport) - (RACSignal *)rac_textSignal { - @rac_unsafeify(self); - return [[[[RACSignal - createSignal:^(id subscriber) { - @rac_strongify(self); - id observer = [NSNotificationCenter.defaultCenter addObserverForName:NSTextDidChangeNotification object:self queue:nil usingBlock:^(NSNotification *note) { - [subscriber sendNext:note.object]; - }]; + @rac_unsafeify(self); + return [[[[RACSignal + createSignal:^(id subscriber) { + @rac_strongify(self); + id observer = [NSNotificationCenter.defaultCenter addObserverForName:NSTextDidChangeNotification object:self queue:nil usingBlock:^(NSNotification *note) { + [subscriber sendNext:note.object]; + }]; - return [RACDisposable disposableWithBlock:^{ - [NSNotificationCenter.defaultCenter removeObserver:observer]; - }]; - }] - map:^(NSText *text) { - return [text.string copy]; - }] - startWith:[self.string copy]] - setNameWithFormat:@"%@ -rac_textSignal", RACDescription(self)]; + return [RACDisposable disposableWithBlock:^{ + [NSNotificationCenter.defaultCenter removeObserver:observer]; + }]; + }] + map:^(NSText *text) { + return [text.string copy]; + }] + startWith:[self.string copy]] + setNameWithFormat:@"%@ -rac_textSignal", RACDescription(self)]; } @end diff --git a/ReactiveObjC/NSURLConnection+RACSupport.m b/ReactiveObjC/NSURLConnection+RACSupport.m index 7acd322ae..812b74250 100644 --- a/ReactiveObjC/NSURLConnection+RACSupport.m +++ b/ReactiveObjC/NSURLConnection+RACSupport.m @@ -15,40 +15,40 @@ @implementation NSURLConnection (RACSupport) + (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request { - NSCParameterAssert(request != nil); + NSCParameterAssert(request != nil); - return [[RACSignal - createSignal:^(id subscriber) { - NSOperationQueue *queue = [[NSOperationQueue alloc] init]; - queue.name = @"org.reactivecocoa.ReactiveObjC.NSURLConnectionRACSupport"; + return [[RACSignal + createSignal:^(id subscriber) { + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + queue.name = @"org.reactivecocoa.ReactiveObjC.NSURLConnectionRACSupport"; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { - // The docs say that `nil` data means an error occurred, but - // `nil` responses can also occur in practice (circumstances - // unknown). Consider either to be an error. - // - // Note that _empty_ data is not necessarily erroneous, as there - // may be headers but no HTTP body. - if (response == nil || data == nil) { - [subscriber sendError:error]; - } else { - [subscriber sendNext:RACTuplePack(response, data)]; - [subscriber sendCompleted]; - } - }]; + [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { + // The docs say that `nil` data means an error occurred, but + // `nil` responses can also occur in practice (circumstances + // unknown). Consider either to be an error. + // + // Note that _empty_ data is not necessarily erroneous, as there + // may be headers but no HTTP body. + if (response == nil || data == nil) { + [subscriber sendError:error]; + } else { + [subscriber sendNext:RACTuplePack(response, data)]; + [subscriber sendCompleted]; + } + }]; #pragma clang diagnostic pop - - return [RACDisposable disposableWithBlock:^{ - // It's not clear if this will actually cancel the connection, - // but we can at least prevent _some_ unnecessary work -- - // without writing all the code for a proper delegate, which - // doesn't really belong in RAC. - queue.suspended = YES; - [queue cancelAllOperations]; - }]; - }] - setNameWithFormat:@"+rac_sendAsynchronousRequest: %@", request]; + + return [RACDisposable disposableWithBlock:^{ + // It's not clear if this will actually cancel the connection, + // but we can at least prevent _some_ unnecessary work -- + // without writing all the code for a proper delegate, which + // doesn't really belong in RAC. + queue.suspended = YES; + [queue cancelAllOperations]; + }]; + }] + setNameWithFormat:@"+rac_sendAsynchronousRequest: %@", request]; } @end diff --git a/ReactiveObjC/NSUserDefaults+RACSupport.m b/ReactiveObjC/NSUserDefaults+RACSupport.m index 9633fa6e4..04af763eb 100644 --- a/ReactiveObjC/NSUserDefaults+RACSupport.m +++ b/ReactiveObjC/NSUserDefaults+RACSupport.m @@ -17,42 +17,42 @@ @implementation NSUserDefaults (RACSupport) - (RACChannelTerminal *)rac_channelTerminalForKey:(NSString *)key { - NSParameterAssert(key != nil); + NSParameterAssert(key != nil); - RACChannel *channel = [RACChannel new]; - - RACScheduler *scheduler = [RACScheduler scheduler]; - __block BOOL ignoreNextValue = NO; - - @rac_weakify(self); - [[[[[[[NSNotificationCenter.defaultCenter - rac_addObserverForName:NSUserDefaultsDidChangeNotification object:self] - map:^(id _) { - @rac_strongify(self); - return [self objectForKey:key]; - }] - startWith:[self objectForKey:key]] - // Don't send values that were set on the other side of the terminal. - filter:^ BOOL (id _) { - if (RACScheduler.currentScheduler == scheduler && ignoreNextValue) { - ignoreNextValue = NO; - return NO; - } - return YES; - }] - distinctUntilChanged] - takeUntil:self.rac_willDeallocSignal] - subscribe:channel.leadingTerminal]; - - [[channel.leadingTerminal - deliverOn:scheduler] - subscribeNext:^(id value) { - @rac_strongify(self); - ignoreNextValue = YES; - [self setObject:value forKey:key]; - }]; - - return channel.followingTerminal; + RACChannel *channel = [RACChannel new]; + + RACScheduler *scheduler = [RACScheduler scheduler]; + __block BOOL ignoreNextValue = NO; + + @rac_weakify(self); + [[[[[[[NSNotificationCenter.defaultCenter + rac_addObserverForName:NSUserDefaultsDidChangeNotification object:self] + map:^(id _) { + @rac_strongify(self); + return [self objectForKey:key]; + }] + startWith:[self objectForKey:key]] + // Don't send values that were set on the other side of the terminal. + filter:^ BOOL (id _) { + if (RACScheduler.currentScheduler == scheduler && ignoreNextValue) { + ignoreNextValue = NO; + return NO; + } + return YES; + }] + distinctUntilChanged] + takeUntil:self.rac_willDeallocSignal] + subscribe:channel.leadingTerminal]; + + [[channel.leadingTerminal + deliverOn:scheduler] + subscribeNext:^(id value) { + @rac_strongify(self); + ignoreNextValue = YES; + [self setObject:value forKey:key]; + }]; + + return channel.followingTerminal; } @end diff --git a/ReactiveObjC/RACArraySequence.m b/ReactiveObjC/RACArraySequence.m index 797d48fbe..7c21ee308 100644 --- a/ReactiveObjC/RACArraySequence.m +++ b/ReactiveObjC/RACArraySequence.m @@ -27,99 +27,99 @@ @implementation RACArraySequence #pragma mark Lifecycle + (RACSequence *)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset { - NSCParameterAssert(offset <= array.count); + NSCParameterAssert(offset <= array.count); - if (offset == array.count) return self.empty; + if (offset == array.count) return self.empty; - RACArraySequence *seq = [[self alloc] init]; - seq->_backingArray = [array copy]; - seq->_offset = offset; - return seq; + RACArraySequence *seq = [[self alloc] init]; + seq->_backingArray = [array copy]; + seq->_offset = offset; + return seq; } #pragma mark RACSequence - (id)head { - return self.backingArray[self.offset]; + return self.backingArray[self.offset]; } - (RACSequence *)tail { - RACSequence *sequence = [self.class sequenceWithArray:self.backingArray offset:self.offset + 1]; - sequence.name = self.name; - return sequence; + RACSequence *sequence = [self.class sequenceWithArray:self.backingArray offset:self.offset + 1]; + sequence.name = self.name; + return sequence; } #pragma mark NSFastEnumeration - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id[])stackbuf count:(NSUInteger)len { - NSCParameterAssert(len > 0); + NSCParameterAssert(len > 0); - if (state->state >= self.backingArray.count) { - // Enumeration has completed. - return 0; - } + if (state->state >= self.backingArray.count) { + // Enumeration has completed. + return 0; + } - if (state->state == 0) { - state->state = self.offset; + if (state->state == 0) { + state->state = self.offset; - // Since a sequence doesn't mutate, this just needs to be set to - // something non-NULL. - state->mutationsPtr = state->extra; - } + // Since a sequence doesn't mutate, this just needs to be set to + // something non-NULL. + state->mutationsPtr = state->extra; + } - state->itemsPtr = stackbuf; + state->itemsPtr = stackbuf; - NSUInteger startIndex = state->state; - NSUInteger index = 0; + NSUInteger startIndex = state->state; + NSUInteger index = 0; - for (id value in self.backingArray) { - // Constructing an index set for -enumerateObjectsAtIndexes: can actually be - // slower than just skipping the items we don't care about. - if (index < startIndex) { - ++index; - continue; - } + for (id value in self.backingArray) { + // Constructing an index set for -enumerateObjectsAtIndexes: can actually be + // slower than just skipping the items we don't care about. + if (index < startIndex) { + ++index; + continue; + } - stackbuf[index - startIndex] = value; + stackbuf[index - startIndex] = value; - ++index; - if (index - startIndex >= len) break; - } + ++index; + if (index - startIndex >= len) break; + } - NSCAssert(index > startIndex, @"Final index (%lu) should be greater than start index (%lu)", (unsigned long)index, (unsigned long)startIndex); + NSCAssert(index > startIndex, @"Final index (%lu) should be greater than start index (%lu)", (unsigned long)index, (unsigned long)startIndex); - state->state = index; - return index - startIndex; + state->state = index; + return index - startIndex; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (NSArray *)array { - return [self.backingArray subarrayWithRange:NSMakeRange(self.offset, self.backingArray.count - self.offset)]; + return [self.backingArray subarrayWithRange:NSMakeRange(self.offset, self.backingArray.count - self.offset)]; } #pragma clang diagnostic pop #pragma mark NSCoding - (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self == nil) return nil; + self = [super initWithCoder:coder]; + if (self == nil) return nil; - _backingArray = [coder decodeObjectForKey:@"array"]; - _offset = 0; + _backingArray = [coder decodeObjectForKey:@"array"]; + _offset = 0; - return self; + return self; } - (void)encodeWithCoder:(NSCoder *)coder { - // Encoding is handled in RACSequence. - [super encodeWithCoder:coder]; + // Encoding is handled in RACSequence. + [super encodeWithCoder:coder]; } #pragma mark NSObject - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p>{ name = %@, array = %@ }", self.class, self, self.name, self.backingArray]; + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, array = %@ }", self.class, self, self.name, self.backingArray]; } @end diff --git a/ReactiveObjC/RACBehaviorSubject.m b/ReactiveObjC/RACBehaviorSubject.m index 857289f40..d56ed4ae7 100644 --- a/ReactiveObjC/RACBehaviorSubject.m +++ b/ReactiveObjC/RACBehaviorSubject.m @@ -22,35 +22,35 @@ @implementation RACBehaviorSubject #pragma mark Lifecycle + (instancetype)behaviorSubjectWithDefaultValue:(id)value { - RACBehaviorSubject *subject = [self subject]; - subject.currentValue = value; - return subject; + RACBehaviorSubject *subject = [self subject]; + subject.currentValue = value; + return subject; } #pragma mark RACSignal - (RACDisposable *)subscribe:(id)subscriber { - RACDisposable *subscriptionDisposable = [super subscribe:subscriber]; - - RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ - @synchronized (self) { - [subscriber sendNext:self.currentValue]; - } - }]; - - return [RACDisposable disposableWithBlock:^{ - [subscriptionDisposable dispose]; - [schedulingDisposable dispose]; - }]; + RACDisposable *subscriptionDisposable = [super subscribe:subscriber]; + + RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ + @synchronized (self) { + [subscriber sendNext:self.currentValue]; + } + }]; + + return [RACDisposable disposableWithBlock:^{ + [subscriptionDisposable dispose]; + [schedulingDisposable dispose]; + }]; } #pragma mark RACSubscriber - (void)sendNext:(id)value { - @synchronized (self) { - self.currentValue = value; - [super sendNext:value]; - } + @synchronized (self) { + self.currentValue = value; + [super sendNext:value]; + } } @end diff --git a/ReactiveObjC/RACBlockTrampoline.h b/ReactiveObjC/RACBlockTrampoline.h index 396155804..59b2d7e33 100644 --- a/ReactiveObjC/RACBlockTrampoline.h +++ b/ReactiveObjC/RACBlockTrampoline.h @@ -23,43 +23,43 @@ // // Returns the return value of invoking the block. static inline id RACInvokeBlock(id block, RACTuple *args) { - NSCParameterAssert(block != NULL && args.count > 0); + NSCParameterAssert(block != NULL && args.count > 0); - switch (args.count) { - case 0: - return nil; - case 1: - return ((id(^)(id))block)(args[0]); - case 2: - return ((id(^)(id, id))block)(args[0], args[1]); - case 3: - return ((id(^)(id, id, id))block)(args[0], args[1], args[2]); - case 4: - return ((id(^)(id, id, id, id))block)(args[0], args[1], args[2], args[3]); - case 5: - return ((id(^)(id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4]); - case 6: - return ((id(^)(id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5]); - case 7: - return ((id(^)(id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); - case 8: - return ((id(^)(id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); - case 9: - return ((id(^)(id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); - case 10: - return ((id(^)(id, id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); - case 11: - return ((id(^)(id, id, id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); - case 12: - return ((id(^)(id, id, id, id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); - case 13: - return ((id(^)(id, id, id, id, id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]); - case 14: - return ((id(^)(id, id, id, id, id, id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]); - case 15: - return ((id(^)(id, id, id, id, id, id, id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14]); - } + switch (args.count) { + case 0: + return nil; + case 1: + return ((id(^)(id))block)(args[0]); + case 2: + return ((id(^)(id, id))block)(args[0], args[1]); + case 3: + return ((id(^)(id, id, id))block)(args[0], args[1], args[2]); + case 4: + return ((id(^)(id, id, id, id))block)(args[0], args[1], args[2], args[3]); + case 5: + return ((id(^)(id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4]); + case 6: + return ((id(^)(id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5]); + case 7: + return ((id(^)(id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); + case 8: + return ((id(^)(id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); + case 9: + return ((id(^)(id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); + case 10: + return ((id(^)(id, id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); + case 11: + return ((id(^)(id, id, id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); + case 12: + return ((id(^)(id, id, id, id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); + case 13: + return ((id(^)(id, id, id, id, id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]); + case 14: + return ((id(^)(id, id, id, id, id, id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]); + case 15: + return ((id(^)(id, id, id, id, id, id, id, id, id, id, id, id, id, id, id))block)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14]); + } - NSCAssert(NO, @"The argument count is too damn high! Only blocks of up to 15 arguments are currently supported."); - return nil; + NSCAssert(NO, @"The argument count is too damn high! Only blocks of up to 15 arguments are currently supported."); + return nil; } diff --git a/ReactiveObjC/RACChannel.m b/ReactiveObjC/RACChannel.m index e8f617c9d..13fd05d5d 100644 --- a/ReactiveObjC/RACChannel.m +++ b/ReactiveObjC/RACChannel.m @@ -26,21 +26,21 @@ - (instancetype)initWithValues:(RACSignal *)values otherTerminal:(id< @implementation RACChannel - (instancetype)init { - self = [super init]; + self = [super init]; - // We don't want any starting value from the leadingSubject, but we do want - // error and completion to be replayed. - RACReplaySubject *leadingSubject = [[RACReplaySubject replaySubjectWithCapacity:0] setNameWithFormat:@"leadingSubject"]; - RACReplaySubject *followingSubject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"followingSubject"]; + // We don't want any starting value from the leadingSubject, but we do want + // error and completion to be replayed. + RACReplaySubject *leadingSubject = [[RACReplaySubject replaySubjectWithCapacity:0] setNameWithFormat:@"leadingSubject"]; + RACReplaySubject *followingSubject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"followingSubject"]; - // Propagate errors and completion to everything. - [[leadingSubject ignoreValues] subscribe:followingSubject]; - [[followingSubject ignoreValues] subscribe:leadingSubject]; + // Propagate errors and completion to everything. + [[leadingSubject ignoreValues] subscribe:followingSubject]; + [[followingSubject ignoreValues] subscribe:leadingSubject]; - _leadingTerminal = [[[RACChannelTerminal alloc] initWithValues:leadingSubject otherTerminal:followingSubject] setNameWithFormat:@"leadingTerminal"]; - _followingTerminal = [[[RACChannelTerminal alloc] initWithValues:followingSubject otherTerminal:leadingSubject] setNameWithFormat:@"followingTerminal"]; + _leadingTerminal = [[[RACChannelTerminal alloc] initWithValues:leadingSubject otherTerminal:followingSubject] setNameWithFormat:@"leadingTerminal"]; + _followingTerminal = [[[RACChannelTerminal alloc] initWithValues:followingSubject otherTerminal:leadingSubject] setNameWithFormat:@"followingTerminal"]; - return self; + return self; } @end @@ -50,39 +50,39 @@ @implementation RACChannelTerminal #pragma mark Lifecycle - (instancetype)initWithValues:(RACSignal *)values otherTerminal:(id)otherTerminal { - NSCParameterAssert(values != nil); - NSCParameterAssert(otherTerminal != nil); + NSCParameterAssert(values != nil); + NSCParameterAssert(otherTerminal != nil); - self = [super init]; + self = [super init]; - _values = values; - _otherTerminal = otherTerminal; + _values = values; + _otherTerminal = otherTerminal; - return self; + return self; } #pragma mark RACSignal - (RACDisposable *)subscribe:(id)subscriber { - return [self.values subscribe:subscriber]; + return [self.values subscribe:subscriber]; } #pragma mark - (void)sendNext:(id)value { - [self.otherTerminal sendNext:value]; + [self.otherTerminal sendNext:value]; } - (void)sendError:(NSError *)error { - [self.otherTerminal sendError:error]; + [self.otherTerminal sendError:error]; } - (void)sendCompleted { - [self.otherTerminal sendCompleted]; + [self.otherTerminal sendCompleted]; } - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable { - [self.otherTerminal didSubscribeWithDisposable:disposable]; + [self.otherTerminal didSubscribeWithDisposable:disposable]; } @end diff --git a/ReactiveObjC/RACCommand.m b/ReactiveObjC/RACCommand.m index 6cda3ac78..9377fb33b 100644 --- a/ReactiveObjC/RACCommand.m +++ b/ReactiveObjC/RACCommand.m @@ -25,8 +25,8 @@ const NSInteger RACCommandErrorNotEnabled = 1; @interface RACCommand () { - // Atomic backing variable for `allowsConcurrentExecution`. - volatile uint32_t _allowsConcurrentExecution; + // Atomic backing variable for `allowsConcurrentExecution`. + volatile uint32_t _allowsConcurrentExecution; } /// A subject that sends added execution signals. @@ -50,153 +50,153 @@ @implementation RACCommand #pragma mark Properties - (BOOL)allowsConcurrentExecution { - return _allowsConcurrentExecution != 0; + return _allowsConcurrentExecution != 0; } - (void)setAllowsConcurrentExecution:(BOOL)allowed { - if (allowed) { - OSAtomicOr32Barrier(1, &_allowsConcurrentExecution); - } else { - OSAtomicAnd32Barrier(0, &_allowsConcurrentExecution); - } + if (allowed) { + OSAtomicOr32Barrier(1, &_allowsConcurrentExecution); + } else { + OSAtomicAnd32Barrier(0, &_allowsConcurrentExecution); + } - [self.allowsConcurrentExecutionSubject sendNext:@(_allowsConcurrentExecution)]; + [self.allowsConcurrentExecutionSubject sendNext:@(_allowsConcurrentExecution)]; } #pragma mark Lifecycle - (instancetype)init { - NSCAssert(NO, @"Use -initWithSignalBlock: instead"); - return nil; + NSCAssert(NO, @"Use -initWithSignalBlock: instead"); + return nil; } - (instancetype)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock { - return [self initWithEnabled:nil signalBlock:signalBlock]; + return [self initWithEnabled:nil signalBlock:signalBlock]; } - (void)dealloc { - [_addedExecutionSignalsSubject sendCompleted]; - [_allowsConcurrentExecutionSubject sendCompleted]; + [_addedExecutionSignalsSubject sendCompleted]; + [_allowsConcurrentExecutionSubject sendCompleted]; } - (instancetype)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock { - NSCParameterAssert(signalBlock != nil); - - self = [super init]; - - _addedExecutionSignalsSubject = [RACSubject new]; - _allowsConcurrentExecutionSubject = [RACSubject new]; - _signalBlock = [signalBlock copy]; - - _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 *immediateExecuting = [[[[self.addedExecutionSignalsSubject - flattenMap:^(RACSignal *signal) { - return [[[signal - catchTo:[RACSignal empty]] - then:^{ - return [RACSignal return:@-1]; - }] - startWith:@1]; - }] - scanWithStart:@0 reduce:^(NSNumber *running, NSNumber *next) { - return @(running.integerValue + next.integerValue); - }] - map:^(NSNumber *count) { - return @(count.integerValue > 0); - }] - startWith:@NO]; - - _executing = [[[[[immediateExecuting - deliverOn:RACScheduler.mainThreadScheduler] - // This is useful before the first value arrives on the main thread. - startWith:@NO] - distinctUntilChanged] - replayLast] - setNameWithFormat:@"%@ -executing", self]; - - RACSignal *moreExecutionsAllowed = [RACSignal - if:[self.allowsConcurrentExecutionSubject startWith:@NO] - then:[RACSignal return:@YES] - else:[immediateExecuting not]]; - - if (enabledSignal == nil) { - enabledSignal = [RACSignal return:@YES]; - } else { - enabledSignal = [enabledSignal startWith:@YES]; - } - - _immediateEnabled = [[[[RACSignal - combineLatest:@[ enabledSignal, moreExecutionsAllowed ]] - and] - takeUntil:self.rac_willDeallocSignal] - replayLast]; - - _enabled = [[[[[self.immediateEnabled - take:1] - concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]] - distinctUntilChanged] - replayLast] - setNameWithFormat:@"%@ -enabled", self]; - - return self; + NSCParameterAssert(signalBlock != nil); + + self = [super init]; + + _addedExecutionSignalsSubject = [RACSubject new]; + _allowsConcurrentExecutionSubject = [RACSubject new]; + _signalBlock = [signalBlock copy]; + + _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 *immediateExecuting = [[[[self.addedExecutionSignalsSubject + flattenMap:^(RACSignal *signal) { + return [[[signal + catchTo:[RACSignal empty]] + then:^{ + return [RACSignal return:@-1]; + }] + startWith:@1]; + }] + scanWithStart:@0 reduce:^(NSNumber *running, NSNumber *next) { + return @(running.integerValue + next.integerValue); + }] + map:^(NSNumber *count) { + return @(count.integerValue > 0); + }] + startWith:@NO]; + + _executing = [[[[[immediateExecuting + deliverOn:RACScheduler.mainThreadScheduler] + // This is useful before the first value arrives on the main thread. + startWith:@NO] + distinctUntilChanged] + replayLast] + setNameWithFormat:@"%@ -executing", self]; + + RACSignal *moreExecutionsAllowed = [RACSignal + if:[self.allowsConcurrentExecutionSubject startWith:@NO] + then:[RACSignal return:@YES] + else:[immediateExecuting not]]; + + if (enabledSignal == nil) { + enabledSignal = [RACSignal return:@YES]; + } else { + enabledSignal = [enabledSignal startWith:@YES]; + } + + _immediateEnabled = [[[[RACSignal + combineLatest:@[ enabledSignal, moreExecutionsAllowed ]] + and] + takeUntil:self.rac_willDeallocSignal] + replayLast]; + + _enabled = [[[[[self.immediateEnabled + take:1] + concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]] + distinctUntilChanged] + replayLast] + setNameWithFormat:@"%@ -enabled", self]; + + return self; } #pragma mark Execution - (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]; - - [connection connect]; - return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, RACDescription(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]; + + [connection connect]; + return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, RACDescription(input)]; } @end diff --git a/ReactiveObjC/RACCompoundDisposable.m b/ReactiveObjC/RACCompoundDisposable.m index 5e38b7df4..c13372c18 100644 --- a/ReactiveObjC/RACCompoundDisposable.m +++ b/ReactiveObjC/RACCompoundDisposable.m @@ -21,37 +21,37 @@ #define RACCompoundDisposableInlineCount 2 static CFMutableArrayRef RACCreateDisposablesArray(void) { - // Compare values using only pointer equality. - CFArrayCallBacks callbacks = kCFTypeArrayCallBacks; - callbacks.equal = NULL; + // Compare values using only pointer equality. + CFArrayCallBacks callbacks = kCFTypeArrayCallBacks; + callbacks.equal = NULL; - return CFArrayCreateMutable(NULL, 0, &callbacks); + return CFArrayCreateMutable(NULL, 0, &callbacks); } @interface RACCompoundDisposable () { - // Used for synchronization. - pthread_mutex_t _mutex; - - #if RACCompoundDisposableInlineCount - // A fast array to the first N of the receiver's disposables. - // - // Once this is full, `_disposables` will be created and used for additional - // disposables. - // - // This array should only be manipulated while _mutex is held. - RACDisposable *_inlineDisposables[RACCompoundDisposableInlineCount]; - #endif - - // Contains the receiver's disposables. - // - // This array should only be manipulated while _mutex is held. If - // `_disposed` is YES, this may be NULL. - CFMutableArrayRef _disposables; - - // Whether the receiver has already been disposed. - // - // This ivar should only be accessed while _mutex is held. - BOOL _disposed; + // Used for synchronization. + pthread_mutex_t _mutex; + + #if RACCompoundDisposableInlineCount + // A fast array to the first N of the receiver's disposables. + // + // Once this is full, `_disposables` will be created and used for additional + // disposables. + // + // This array should only be manipulated while _mutex is held. + RACDisposable *_inlineDisposables[RACCompoundDisposableInlineCount]; + #endif + + // Contains the receiver's disposables. + // + // This array should only be manipulated while _mutex is held. If + // `_disposed` is YES, this may be NULL. + CFMutableArrayRef _disposables; + + // Whether the receiver has already been disposed. + // + // This ivar should only be accessed while _mutex is held. + BOOL _disposed; } @end @@ -61,190 +61,190 @@ @implementation RACCompoundDisposable #pragma mark Properties - (BOOL)isDisposed { - pthread_mutex_lock(&_mutex); - BOOL disposed = _disposed; - pthread_mutex_unlock(&_mutex); + pthread_mutex_lock(&_mutex); + BOOL disposed = _disposed; + pthread_mutex_unlock(&_mutex); - return disposed; + return disposed; } #pragma mark Lifecycle + (instancetype)compoundDisposable { - return [[self alloc] initWithDisposables:nil]; + return [[self alloc] initWithDisposables:nil]; } + (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables { - return [[self alloc] initWithDisposables:disposables]; + return [[self alloc] initWithDisposables:disposables]; } - (instancetype)init { - self = [super init]; + self = [super init]; - const int result __attribute__((unused)) = pthread_mutex_init(&_mutex, NULL); - NSCAssert(0 == result, @"Failed to initialize mutex with error %d.", result); + const int result __attribute__((unused)) = pthread_mutex_init(&_mutex, NULL); + NSCAssert(0 == result, @"Failed to initialize mutex with error %d.", result); - return self; + return self; } - (instancetype)initWithDisposables:(NSArray *)otherDisposables { - self = [self init]; + self = [self init]; - #if RACCompoundDisposableInlineCount - [otherDisposables enumerateObjectsUsingBlock:^(RACDisposable *disposable, NSUInteger index, BOOL *stop) { - self->_inlineDisposables[index] = disposable; + #if RACCompoundDisposableInlineCount + [otherDisposables enumerateObjectsUsingBlock:^(RACDisposable *disposable, NSUInteger index, BOOL *stop) { + self->_inlineDisposables[index] = disposable; - // Stop after this iteration if we've reached the end of the inlined - // array. - if (index == RACCompoundDisposableInlineCount - 1) *stop = YES; - }]; - #endif + // Stop after this iteration if we've reached the end of the inlined + // array. + if (index == RACCompoundDisposableInlineCount - 1) *stop = YES; + }]; + #endif - if (otherDisposables.count > RACCompoundDisposableInlineCount) { - _disposables = RACCreateDisposablesArray(); + if (otherDisposables.count > RACCompoundDisposableInlineCount) { + _disposables = RACCreateDisposablesArray(); - CFRange range = CFRangeMake(RACCompoundDisposableInlineCount, (CFIndex)otherDisposables.count - RACCompoundDisposableInlineCount); - CFArrayAppendArray(_disposables, (__bridge CFArrayRef)otherDisposables, range); - } + CFRange range = CFRangeMake(RACCompoundDisposableInlineCount, (CFIndex)otherDisposables.count - RACCompoundDisposableInlineCount); + CFArrayAppendArray(_disposables, (__bridge CFArrayRef)otherDisposables, range); + } - return self; + return self; } - (instancetype)initWithBlock:(void (^)(void))block { - RACDisposable *disposable = [RACDisposable disposableWithBlock:block]; - return [self initWithDisposables:@[ disposable ]]; + RACDisposable *disposable = [RACDisposable disposableWithBlock:block]; + return [self initWithDisposables:@[ disposable ]]; } - (void)dealloc { - #if RACCompoundDisposableInlineCount - for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { - _inlineDisposables[i] = nil; - } - #endif - - if (_disposables != NULL) { - CFRelease(_disposables); - _disposables = NULL; - } - - const int result __attribute__((unused)) = pthread_mutex_destroy(&_mutex); - NSCAssert(0 == result, @"Failed to destroy mutex with error %d.", result); + #if RACCompoundDisposableInlineCount + for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { + _inlineDisposables[i] = nil; + } + #endif + + if (_disposables != NULL) { + CFRelease(_disposables); + _disposables = NULL; + } + + const int result __attribute__((unused)) = pthread_mutex_destroy(&_mutex); + NSCAssert(0 == result, @"Failed to destroy mutex with error %d.", result); } #pragma mark Addition and Removal - (void)addDisposable:(RACDisposable *)disposable { - NSCParameterAssert(disposable != self); - if (disposable == nil || disposable.disposed) return; - - BOOL shouldDispose = NO; - - pthread_mutex_lock(&_mutex); - { - if (_disposed) { - shouldDispose = YES; - } else { - #if RACCompoundDisposableInlineCount - for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { - if (_inlineDisposables[i] == nil) { - _inlineDisposables[i] = disposable; - goto foundSlot; - } - } - #endif - - if (_disposables == NULL) _disposables = RACCreateDisposablesArray(); - CFArrayAppendValue(_disposables, (__bridge void *)disposable); - - if (RACCOMPOUNDDISPOSABLE_ADDED_ENABLED()) { - RACCOMPOUNDDISPOSABLE_ADDED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount); - } - - #if RACCompoundDisposableInlineCount - foundSlot:; - #endif - } - } - pthread_mutex_unlock(&_mutex); - - // Performed outside of the lock in case the compound disposable is used - // recursively. - if (shouldDispose) [disposable dispose]; + NSCParameterAssert(disposable != self); + if (disposable == nil || disposable.disposed) return; + + BOOL shouldDispose = NO; + + pthread_mutex_lock(&_mutex); + { + if (_disposed) { + shouldDispose = YES; + } else { + #if RACCompoundDisposableInlineCount + for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { + if (_inlineDisposables[i] == nil) { + _inlineDisposables[i] = disposable; + goto foundSlot; + } + } + #endif + + if (_disposables == NULL) _disposables = RACCreateDisposablesArray(); + CFArrayAppendValue(_disposables, (__bridge void *)disposable); + + if (RACCOMPOUNDDISPOSABLE_ADDED_ENABLED()) { + RACCOMPOUNDDISPOSABLE_ADDED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount); + } + + #if RACCompoundDisposableInlineCount + foundSlot:; + #endif + } + } + pthread_mutex_unlock(&_mutex); + + // Performed outside of the lock in case the compound disposable is used + // recursively. + if (shouldDispose) [disposable dispose]; } - (void)removeDisposable:(RACDisposable *)disposable { - if (disposable == nil) return; - - pthread_mutex_lock(&_mutex); - { - if (!_disposed) { - #if RACCompoundDisposableInlineCount - for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { - if (_inlineDisposables[i] == disposable) _inlineDisposables[i] = nil; - } - #endif - - if (_disposables != NULL) { - CFIndex count = CFArrayGetCount(_disposables); - for (CFIndex i = count - 1; i >= 0; i--) { - const void *item = CFArrayGetValueAtIndex(_disposables, i); - if (item == (__bridge void *)disposable) { - CFArrayRemoveValueAtIndex(_disposables, i); - } - } - - if (RACCOMPOUNDDISPOSABLE_REMOVED_ENABLED()) { - RACCOMPOUNDDISPOSABLE_REMOVED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount); - } - } - } - } - pthread_mutex_unlock(&_mutex); + if (disposable == nil) return; + + pthread_mutex_lock(&_mutex); + { + if (!_disposed) { + #if RACCompoundDisposableInlineCount + for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { + if (_inlineDisposables[i] == disposable) _inlineDisposables[i] = nil; + } + #endif + + if (_disposables != NULL) { + CFIndex count = CFArrayGetCount(_disposables); + for (CFIndex i = count - 1; i >= 0; i--) { + const void *item = CFArrayGetValueAtIndex(_disposables, i); + if (item == (__bridge void *)disposable) { + CFArrayRemoveValueAtIndex(_disposables, i); + } + } + + if (RACCOMPOUNDDISPOSABLE_REMOVED_ENABLED()) { + RACCOMPOUNDDISPOSABLE_REMOVED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount); + } + } + } + } + pthread_mutex_unlock(&_mutex); } #pragma mark RACDisposable static void disposeEach(const void *value, void *context) { - RACDisposable *disposable = (__bridge id)value; - [disposable dispose]; + RACDisposable *disposable = (__bridge id)value; + [disposable dispose]; } - (void)dispose { - #if RACCompoundDisposableInlineCount - RACDisposable *inlineCopy[RACCompoundDisposableInlineCount]; - #endif - - CFArrayRef remainingDisposables = NULL; - - pthread_mutex_lock(&_mutex); - { - _disposed = YES; - - #if RACCompoundDisposableInlineCount - for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { - inlineCopy[i] = _inlineDisposables[i]; - _inlineDisposables[i] = nil; - } - #endif - - remainingDisposables = _disposables; - _disposables = NULL; - } - pthread_mutex_unlock(&_mutex); - - #if RACCompoundDisposableInlineCount - // Dispose outside of the lock in case the compound disposable is used - // recursively. - for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { - [inlineCopy[i] dispose]; - } - #endif - - if (remainingDisposables == NULL) return; - - CFIndex count = CFArrayGetCount(remainingDisposables); - CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL); - CFRelease(remainingDisposables); + #if RACCompoundDisposableInlineCount + RACDisposable *inlineCopy[RACCompoundDisposableInlineCount]; + #endif + + CFArrayRef remainingDisposables = NULL; + + pthread_mutex_lock(&_mutex); + { + _disposed = YES; + + #if RACCompoundDisposableInlineCount + for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { + inlineCopy[i] = _inlineDisposables[i]; + _inlineDisposables[i] = nil; + } + #endif + + remainingDisposables = _disposables; + _disposables = NULL; + } + pthread_mutex_unlock(&_mutex); + + #if RACCompoundDisposableInlineCount + // Dispose outside of the lock in case the compound disposable is used + // recursively. + for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { + [inlineCopy[i] dispose]; + } + #endif + + if (remainingDisposables == NULL) return; + + CFIndex count = CFArrayGetCount(remainingDisposables); + CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL); + CFRelease(remainingDisposables); } @end diff --git a/ReactiveObjC/RACDelegateProxy.m b/ReactiveObjC/RACDelegateProxy.m index c1f07c3e6..6b918549f 100644 --- a/ReactiveObjC/RACDelegateProxy.m +++ b/ReactiveObjC/RACDelegateProxy.m @@ -11,8 +11,8 @@ #import @interface RACDelegateProxy () { - // Declared as an ivar to avoid method naming conflicts. - Protocol *_protocol; + // Declared as an ivar to avoid method naming conflicts. + Protocol *_protocol; } @end @@ -22,54 +22,54 @@ @implementation RACDelegateProxy #pragma mark Lifecycle - (instancetype)initWithProtocol:(Protocol *)protocol { - NSCParameterAssert(protocol != NULL); + NSCParameterAssert(protocol != NULL); - self = [super init]; + self = [super init]; - class_addProtocol(self.class, protocol); + class_addProtocol(self.class, protocol); - _protocol = protocol; + _protocol = protocol; - return self; + return self; } #pragma mark API - (RACSignal *)signalForSelector:(SEL)selector { - return [self rac_signalForSelector:selector fromProtocol:_protocol]; + return [self rac_signalForSelector:selector fromProtocol:_protocol]; } #pragma mark NSObject - (BOOL)isProxy { - return YES; + return YES; } - (void)forwardInvocation:(NSInvocation *)invocation { - [invocation invokeWithTarget:self.rac_proxiedDelegate]; + [invocation invokeWithTarget:self.rac_proxiedDelegate]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { - // Look for the selector as an optional instance method. - struct objc_method_description methodDescription = protocol_getMethodDescription(_protocol, selector, NO, YES); + // Look for the selector as an optional instance method. + struct objc_method_description methodDescription = protocol_getMethodDescription(_protocol, selector, NO, YES); - if (methodDescription.name == NULL) { - // Then fall back to looking for a required instance - // method. - methodDescription = protocol_getMethodDescription(_protocol, selector, YES, YES); - if (methodDescription.name == NULL) return [super methodSignatureForSelector:selector]; - } + if (methodDescription.name == NULL) { + // Then fall back to looking for a required instance + // method. + methodDescription = protocol_getMethodDescription(_protocol, selector, YES, YES); + if (methodDescription.name == NULL) return [super methodSignatureForSelector:selector]; + } - return [NSMethodSignature signatureWithObjCTypes:methodDescription.types]; + return [NSMethodSignature signatureWithObjCTypes:methodDescription.types]; } - (BOOL)respondsToSelector:(SEL)selector { - // Add the delegate to the autorelease pool, so it doesn't get deallocated - // between this method call and -forwardInvocation:. - __autoreleasing id delegate = self.rac_proxiedDelegate; - if ([delegate respondsToSelector:selector]) return YES; + // Add the delegate to the autorelease pool, so it doesn't get deallocated + // between this method call and -forwardInvocation:. + __autoreleasing id delegate = self.rac_proxiedDelegate; + if ([delegate respondsToSelector:selector]) return YES; - return [super respondsToSelector:selector]; + return [super respondsToSelector:selector]; } @end diff --git a/ReactiveObjC/RACDisposable.m b/ReactiveObjC/RACDisposable.m index 25f266977..9a6ef2c96 100644 --- a/ReactiveObjC/RACDisposable.m +++ b/ReactiveObjC/RACDisposable.m @@ -11,12 +11,12 @@ #import @interface RACDisposable () { - // A copied block of type void (^)(void) containing the logic for disposal, - // a pointer to `self` if no logic should be performed upon disposal, or - // NULL if the receiver is already disposed. - // - // This should only be used atomically. - void * volatile _disposeBlock; + // A copied block of type void (^)(void) containing the logic for disposal, + // a pointer to `self` if no logic should be performed upon disposal, or + // NULL if the receiver is already disposed. + // + // This should only be used atomically. + void * volatile _disposeBlock; } @end @@ -26,65 +26,65 @@ @implementation RACDisposable #pragma mark Properties - (BOOL)isDisposed { - return _disposeBlock == NULL; + return _disposeBlock == NULL; } #pragma mark Lifecycle - (instancetype)init { - self = [super init]; + self = [super init]; - _disposeBlock = (__bridge void *)self; - OSMemoryBarrier(); + _disposeBlock = (__bridge void *)self; + OSMemoryBarrier(); - return self; + return self; } - (instancetype)initWithBlock:(void (^)(void))block { - NSCParameterAssert(block != nil); + NSCParameterAssert(block != nil); - self = [super init]; + self = [super init]; - _disposeBlock = (void *)CFBridgingRetain([block copy]); - OSMemoryBarrier(); + _disposeBlock = (void *)CFBridgingRetain([block copy]); + OSMemoryBarrier(); - return self; + return self; } + (instancetype)disposableWithBlock:(void (^)(void))block { - return [[self alloc] initWithBlock:block]; + return [[self alloc] initWithBlock:block]; } - (void)dealloc { - if (_disposeBlock == NULL || _disposeBlock == (__bridge void *)self) return; + if (_disposeBlock == NULL || _disposeBlock == (__bridge void *)self) return; - CFRelease(_disposeBlock); - _disposeBlock = NULL; + CFRelease(_disposeBlock); + _disposeBlock = NULL; } #pragma mark Disposal - (void)dispose { - void (^disposeBlock)(void) = NULL; + void (^disposeBlock)(void) = NULL; - while (YES) { - void *blockPtr = _disposeBlock; - if (OSAtomicCompareAndSwapPtrBarrier(blockPtr, NULL, &_disposeBlock)) { - if (blockPtr != (__bridge void *)self) { - disposeBlock = CFBridgingRelease(blockPtr); - } + while (YES) { + void *blockPtr = _disposeBlock; + if (OSAtomicCompareAndSwapPtrBarrier(blockPtr, NULL, &_disposeBlock)) { + if (blockPtr != (__bridge void *)self) { + disposeBlock = CFBridgingRelease(blockPtr); + } - break; - } - } + break; + } + } - if (disposeBlock != nil) disposeBlock(); + if (disposeBlock != nil) disposeBlock(); } #pragma mark Scoped Disposables - (RACScopedDisposable *)asScopedDisposable { - return [RACScopedDisposable scopedDisposableWithDisposable:self]; + return [RACScopedDisposable scopedDisposableWithDisposable:self]; } @end diff --git a/ReactiveObjC/RACDynamicSequence.m b/ReactiveObjC/RACDynamicSequence.m index 177a22514..2e8f290aa 100644 --- a/ReactiveObjC/RACDynamicSequence.m +++ b/ReactiveObjC/RACDynamicSequence.m @@ -17,29 +17,29 @@ #define DEALLOC_OVERFLOW_GUARD 100 @interface RACDynamicSequence () { - // The value for the "head" property, if it's been evaluated already. - // - // Because it's legal for head to be nil, this ivar is valid any time - // headBlock is nil. - // - // This ivar should only be accessed while synchronized on self. - id _head; - - // The value for the "tail" property, if it's been evaluated already. - // - // Because it's legal for tail to be nil, this ivar is valid any time - // tailBlock is nil. - // - // This ivar should only be accessed while synchronized on self. - RACSequence *_tail; - - // The result of an evaluated `dependencyBlock`. - // - // This ivar is valid any time `hasDependency` is YES and `dependencyBlock` - // is nil. - // - // This ivar should only be accessed while synchronized on self. - id _dependency; + // The value for the "head" property, if it's been evaluated already. + // + // Because it's legal for head to be nil, this ivar is valid any time + // headBlock is nil. + // + // This ivar should only be accessed while synchronized on self. + id _head; + + // The value for the "tail" property, if it's been evaluated already. + // + // Because it's legal for tail to be nil, this ivar is valid any time + // tailBlock is nil. + // + // This ivar should only be accessed while synchronized on self. + RACSequence *_tail; + + // The result of an evaluated `dependencyBlock`. + // + // This ivar is valid any time `hasDependency` is YES and `dependencyBlock` + // is nil. + // + // This ivar should only be accessed while synchronized on self. + id _dependency; } // A block used to evaluate head. This should be set to nil after `_head` has been @@ -92,106 +92,106 @@ @implementation RACDynamicSequence #pragma mark Lifecycle + (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock { - NSCParameterAssert(headBlock != nil); + NSCParameterAssert(headBlock != nil); - RACDynamicSequence *seq = [[RACDynamicSequence alloc] init]; - seq.headBlock = [headBlock copy]; - seq.tailBlock = [tailBlock copy]; - seq.hasDependency = NO; - return seq; + RACDynamicSequence *seq = [[RACDynamicSequence alloc] init]; + seq.headBlock = [headBlock copy]; + seq.tailBlock = [tailBlock copy]; + seq.hasDependency = NO; + return seq; } + (RACSequence *)sequenceWithLazyDependency:(id (^)(void))dependencyBlock headBlock:(id (^)(id dependency))headBlock tailBlock:(RACSequence *(^)(id dependency))tailBlock { - NSCParameterAssert(dependencyBlock != nil); - NSCParameterAssert(headBlock != nil); - - RACDynamicSequence *seq = [[RACDynamicSequence alloc] init]; - seq.headBlock = [headBlock copy]; - seq.tailBlock = [tailBlock copy]; - seq.dependencyBlock = [dependencyBlock copy]; - seq.hasDependency = YES; - return seq; + NSCParameterAssert(dependencyBlock != nil); + NSCParameterAssert(headBlock != nil); + + RACDynamicSequence *seq = [[RACDynamicSequence alloc] init]; + seq.headBlock = [headBlock copy]; + seq.tailBlock = [tailBlock copy]; + seq.dependencyBlock = [dependencyBlock copy]; + seq.hasDependency = YES; + return seq; } - (void)dealloc { - static volatile int32_t directDeallocCount = 0; + static volatile int32_t directDeallocCount = 0; - if (OSAtomicIncrement32(&directDeallocCount) >= DEALLOC_OVERFLOW_GUARD) { - OSAtomicAdd32(-DEALLOC_OVERFLOW_GUARD, &directDeallocCount); + if (OSAtomicIncrement32(&directDeallocCount) >= DEALLOC_OVERFLOW_GUARD) { + OSAtomicAdd32(-DEALLOC_OVERFLOW_GUARD, &directDeallocCount); - // Put this sequence's tail onto the autorelease pool so we stop - // recursing. - __autoreleasing RACSequence *tail __attribute__((unused)) = _tail; - } - - _tail = nil; + // Put this sequence's tail onto the autorelease pool so we stop + // recursing. + __autoreleasing RACSequence *tail __attribute__((unused)) = _tail; + } + + _tail = nil; } #pragma mark RACSequence - (id)head { - @synchronized (self) { - id untypedHeadBlock = self.headBlock; - if (untypedHeadBlock == nil) return _head; - - if (self.hasDependency) { - if (self.dependencyBlock != nil) { - _dependency = self.dependencyBlock(); - self.dependencyBlock = nil; - } - - id (^headBlock)(id) = untypedHeadBlock; - _head = headBlock(_dependency); - } else { - id (^headBlock)(void) = untypedHeadBlock; - _head = headBlock(); - } - - self.headBlock = nil; - return _head; - } + @synchronized (self) { + id untypedHeadBlock = self.headBlock; + if (untypedHeadBlock == nil) return _head; + + if (self.hasDependency) { + if (self.dependencyBlock != nil) { + _dependency = self.dependencyBlock(); + self.dependencyBlock = nil; + } + + id (^headBlock)(id) = untypedHeadBlock; + _head = headBlock(_dependency); + } else { + id (^headBlock)(void) = untypedHeadBlock; + _head = headBlock(); + } + + self.headBlock = nil; + return _head; + } } - (RACSequence *)tail { - @synchronized (self) { - id untypedTailBlock = self.tailBlock; - if (untypedTailBlock == nil) return _tail; - - if (self.hasDependency) { - if (self.dependencyBlock != nil) { - _dependency = self.dependencyBlock(); - self.dependencyBlock = nil; - } - - RACSequence * (^tailBlock)(id) = untypedTailBlock; - _tail = tailBlock(_dependency); - } else { - RACSequence * (^tailBlock)(void) = untypedTailBlock; - _tail = tailBlock(); - } - - if (_tail.name == nil) _tail.name = self.name; - - self.tailBlock = nil; - return _tail; - } + @synchronized (self) { + id untypedTailBlock = self.tailBlock; + if (untypedTailBlock == nil) return _tail; + + if (self.hasDependency) { + if (self.dependencyBlock != nil) { + _dependency = self.dependencyBlock(); + self.dependencyBlock = nil; + } + + RACSequence * (^tailBlock)(id) = untypedTailBlock; + _tail = tailBlock(_dependency); + } else { + RACSequence * (^tailBlock)(void) = untypedTailBlock; + _tail = tailBlock(); + } + + if (_tail.name == nil) _tail.name = self.name; + + self.tailBlock = nil; + return _tail; + } } #pragma mark NSObject - (NSString *)description { - id head = @"(unresolved)"; - id tail = @"(unresolved)"; - - @synchronized (self) { - if (self.headBlock == nil) head = _head; - if (self.tailBlock == nil) { - tail = _tail; - if (tail == self) tail = @"(self)"; - } - } - - return [NSString stringWithFormat:@"<%@: %p>{ name = %@, head = %@, tail = %@ }", self.class, self, self.name, head, tail]; + id head = @"(unresolved)"; + id tail = @"(unresolved)"; + + @synchronized (self) { + if (self.headBlock == nil) head = _head; + if (self.tailBlock == nil) { + tail = _tail; + if (tail == self) tail = @"(self)"; + } + } + + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, head = %@, tail = %@ }", self.class, self, self.name, head, tail]; } @end diff --git a/ReactiveObjC/RACDynamicSignal.m b/ReactiveObjC/RACDynamicSignal.m index 3bee2f7c2..5192e643c 100644 --- a/ReactiveObjC/RACDynamicSignal.m +++ b/ReactiveObjC/RACDynamicSignal.m @@ -26,29 +26,29 @@ @implementation RACDynamicSignal #pragma mark Lifecycle + (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe { - RACDynamicSignal *signal = [[self alloc] init]; - signal->_didSubscribe = [didSubscribe copy]; - return [signal setNameWithFormat:@"+createSignal:"]; + RACDynamicSignal *signal = [[self alloc] init]; + signal->_didSubscribe = [didSubscribe copy]; + return [signal setNameWithFormat:@"+createSignal:"]; } #pragma mark Managing Subscribers - (RACDisposable *)subscribe:(id)subscriber { - NSCParameterAssert(subscriber != nil); + NSCParameterAssert(subscriber != nil); - RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; - subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; - if (self.didSubscribe != NULL) { - RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ - RACDisposable *innerDisposable = self.didSubscribe(subscriber); - [disposable addDisposable:innerDisposable]; - }]; + if (self.didSubscribe != NULL) { + RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ + RACDisposable *innerDisposable = self.didSubscribe(subscriber); + [disposable addDisposable:innerDisposable]; + }]; - [disposable addDisposable:schedulingDisposable]; - } - - return disposable; + [disposable addDisposable:schedulingDisposable]; + } + + return disposable; } @end diff --git a/ReactiveObjC/RACEagerSequence.m b/ReactiveObjC/RACEagerSequence.m index b1f94fc7a..fa59fd2c3 100644 --- a/ReactiveObjC/RACEagerSequence.m +++ b/ReactiveObjC/RACEagerSequence.m @@ -15,52 +15,52 @@ @implementation RACEagerSequence #pragma mark RACStream + (RACSequence *)return:(id)value { - return [[self sequenceWithArray:@[ value ] offset:0] setNameWithFormat:@"+return: %@", RACDescription(value)]; + return [[self sequenceWithArray:@[ value ] offset:0] setNameWithFormat:@"+return: %@", RACDescription(value)]; } - (RACSequence *)bind:(RACSequenceBindBlock (^)(void))block { - NSCParameterAssert(block != nil); - RACStreamBindBlock bindBlock = block(); - NSArray *currentArray = self.array; - NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:currentArray.count]; - - for (id value in currentArray) { - BOOL stop = NO; - RACSequence *boundValue = (id)bindBlock(value, &stop); - if (boundValue == nil) break; + NSCParameterAssert(block != nil); + RACStreamBindBlock bindBlock = block(); + NSArray *currentArray = self.array; + NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:currentArray.count]; + + for (id value in currentArray) { + BOOL stop = NO; + RACSequence *boundValue = (id)bindBlock(value, &stop); + if (boundValue == nil) break; - for (id x in boundValue) { - [resultArray addObject:x]; - } + for (id x in boundValue) { + [resultArray addObject:x]; + } - if (stop) break; - } - - return [[self.class sequenceWithArray:resultArray offset:0] setNameWithFormat:@"[%@] -bind:", self.name]; + if (stop) break; + } + + return [[self.class sequenceWithArray:resultArray offset:0] setNameWithFormat:@"[%@] -bind:", self.name]; } - (RACSequence *)concat:(RACSequence *)sequence { - NSCParameterAssert(sequence != nil); - NSCParameterAssert([sequence isKindOfClass:RACSequence.class]); + NSCParameterAssert(sequence != nil); + NSCParameterAssert([sequence isKindOfClass:RACSequence.class]); - NSArray *array = [self.array arrayByAddingObjectsFromArray:sequence.array]; - return [[self.class sequenceWithArray:array offset:0] setNameWithFormat:@"[%@] -concat: %@", self.name, sequence]; + NSArray *array = [self.array arrayByAddingObjectsFromArray:sequence.array]; + return [[self.class sequenceWithArray:array offset:0] setNameWithFormat:@"[%@] -concat: %@", self.name, sequence]; } #pragma mark Extended methods - (RACSequence *)eagerSequence { - return self; + return self; } - (RACSequence *)lazySequence { - return [RACArraySequence sequenceWithArray:self.array offset:0]; + return [RACArraySequence sequenceWithArray:self.array offset:0]; } - (id)foldRightWithStart:(id)start reduce:(id (^)(id, RACSequence *rest))reduce { - return [super foldRightWithStart:start reduce:^(id first, RACSequence *rest) { - return reduce(first, rest.eagerSequence); - }]; + return [super foldRightWithStart:start reduce:^(id first, RACSequence *rest) { + return reduce(first, rest.eagerSequence); + }]; } @end diff --git a/ReactiveObjC/RACEmptySequence.m b/ReactiveObjC/RACEmptySequence.m index bd3dc2e7f..2f8824c30 100644 --- a/ReactiveObjC/RACEmptySequence.m +++ b/ReactiveObjC/RACEmptySequence.m @@ -13,40 +13,40 @@ @implementation RACEmptySequence #pragma mark Lifecycle + (instancetype)empty { - static id singleton; - static dispatch_once_t pred; + static id singleton; + static dispatch_once_t pred; - dispatch_once(&pred, ^{ - singleton = [[self alloc] init]; - }); + dispatch_once(&pred, ^{ + singleton = [[self alloc] init]; + }); - return singleton; + return singleton; } #pragma mark RACSequence - (id)head { - return nil; + return nil; } - (RACSequence *)tail { - return nil; + return nil; } - (RACSequence *)bind:(RACStreamBindBlock)bindBlock passingThroughValuesFromSequence:(RACSequence *)passthroughSequence { - return passthroughSequence ?: self; + return passthroughSequence ?: self; } #pragma mark NSCoding - (Class)classForCoder { - // Empty sequences should be encoded as themselves, not array sequences. - return self.class; + // Empty sequences should be encoded as themselves, not array sequences. + return self.class; } - (instancetype)initWithCoder:(NSCoder *)coder { - // Return the singleton. - return self.class.empty; + // Return the singleton. + return self.class.empty; } - (void)encodeWithCoder:(NSCoder *)coder { @@ -55,17 +55,17 @@ - (void)encodeWithCoder:(NSCoder *)coder { #pragma mark NSObject - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p>{ name = %@ }", self.class, self, self.name]; + return [NSString stringWithFormat:@"<%@: %p>{ name = %@ }", self.class, self, self.name]; } - (NSUInteger)hash { - // This hash isn't ideal, but it's better than -[RACSequence hash], which - // would just be zero because we have no head. - return (NSUInteger)(__bridge void *)self; + // This hash isn't ideal, but it's better than -[RACSequence hash], which + // would just be zero because we have no head. + return (NSUInteger)(__bridge void *)self; } - (BOOL)isEqual:(RACSequence *)seq { - return (self == seq); + return (self == seq); } @end diff --git a/ReactiveObjC/RACEmptySignal.m b/ReactiveObjC/RACEmptySignal.m index d47ac2733..30b54d07b 100644 --- a/ReactiveObjC/RACEmptySignal.m +++ b/ReactiveObjC/RACEmptySignal.m @@ -18,15 +18,15 @@ @implementation RACEmptySignal // a singleton in release builds (see +empty). - (void)setName:(NSString *)name { #ifdef DEBUG - [super setName:name]; + [super setName:name]; #endif } - (NSString *)name { #ifdef DEBUG - return super.name; + return super.name; #else - return @"+empty"; + return @"+empty"; #endif } @@ -34,29 +34,29 @@ - (NSString *)name { + (RACSignal *)empty { #ifdef DEBUG - // Create multiple instances of this class in DEBUG so users can set custom - // names on each. - return [[[self alloc] init] setNameWithFormat:@"+empty"]; + // Create multiple instances of this class in DEBUG so users can set custom + // names on each. + return [[[self alloc] init] setNameWithFormat:@"+empty"]; #else - static id singleton; - static dispatch_once_t pred; + static id singleton; + static dispatch_once_t pred; - dispatch_once(&pred, ^{ - singleton = [[self alloc] init]; - }); + dispatch_once(&pred, ^{ + singleton = [[self alloc] init]; + }); - return singleton; + return singleton; #endif } #pragma mark Subscription - (RACDisposable *)subscribe:(id)subscriber { - NSCParameterAssert(subscriber != nil); + NSCParameterAssert(subscriber != nil); - return [RACScheduler.subscriptionScheduler schedule:^{ - [subscriber sendCompleted]; - }]; + return [RACScheduler.subscriptionScheduler schedule:^{ + [subscriber sendCompleted]; + }]; } @end diff --git a/ReactiveObjC/RACErrorSignal.m b/ReactiveObjC/RACErrorSignal.m index 1d08a2f93..128d5073f 100644 --- a/ReactiveObjC/RACErrorSignal.m +++ b/ReactiveObjC/RACErrorSignal.m @@ -22,26 +22,26 @@ @implementation RACErrorSignal #pragma mark Lifecycle + (RACSignal *)error:(NSError *)error { - RACErrorSignal *signal = [[self alloc] init]; - signal->_error = error; + RACErrorSignal *signal = [[self alloc] init]; + signal->_error = error; #ifdef DEBUG - [signal setNameWithFormat:@"+error: %@", error]; + [signal setNameWithFormat:@"+error: %@", error]; #else - signal.name = @"+error:"; + signal.name = @"+error:"; #endif - return signal; + return signal; } #pragma mark Subscription - (RACDisposable *)subscribe:(id)subscriber { - NSCParameterAssert(subscriber != nil); + NSCParameterAssert(subscriber != nil); - return [RACScheduler.subscriptionScheduler schedule:^{ - [subscriber sendError:self.error]; - }]; + return [RACScheduler.subscriptionScheduler schedule:^{ + [subscriber sendError:self.error]; + }]; } @end diff --git a/ReactiveObjC/RACEvent.m b/ReactiveObjC/RACEvent.m index 01f9fff29..641e584a1 100644 --- a/ReactiveObjC/RACEvent.m +++ b/ReactiveObjC/RACEvent.m @@ -24,89 +24,89 @@ @implementation RACEvent #pragma mark Properties - (BOOL)isFinished { - return self.eventType == RACEventTypeCompleted || self.eventType == RACEventTypeError; + return self.eventType == RACEventTypeCompleted || self.eventType == RACEventTypeError; } - (NSError *)error { - return (self.eventType == RACEventTypeError ? self.object : nil); + return (self.eventType == RACEventTypeError ? self.object : nil); } - (id)value { - return (self.eventType == RACEventTypeNext ? self.object : nil); + return (self.eventType == RACEventTypeNext ? self.object : nil); } #pragma mark Lifecycle + (instancetype)completedEvent { - static dispatch_once_t pred; - static id singleton; + static dispatch_once_t pred; + static id singleton; - dispatch_once(&pred, ^{ - singleton = [[self alloc] initWithEventType:RACEventTypeCompleted object:nil]; - }); + dispatch_once(&pred, ^{ + singleton = [[self alloc] initWithEventType:RACEventTypeCompleted object:nil]; + }); - return singleton; + return singleton; } + (instancetype)eventWithError:(NSError *)error { - return [[self alloc] initWithEventType:RACEventTypeError object:error]; + return [[self alloc] initWithEventType:RACEventTypeError object:error]; } + (instancetype)eventWithValue:(id)value { - return [[self alloc] initWithEventType:RACEventTypeNext object:value]; + return [[self alloc] initWithEventType:RACEventTypeNext object:value]; } - (instancetype)initWithEventType:(RACEventType)type object:(id)object { - self = [super init]; + self = [super init]; - _eventType = type; - _object = object; + _eventType = type; + _object = object; - return self; + return self; } #pragma mark NSCopying - (id)copyWithZone:(NSZone *)zone { - return self; + return self; } #pragma mark NSObject - (NSString *)description { - NSString *eventDescription = nil; + NSString *eventDescription = nil; - switch (self.eventType) { - case RACEventTypeCompleted: - eventDescription = @"completed"; - break; + switch (self.eventType) { + case RACEventTypeCompleted: + eventDescription = @"completed"; + break; - case RACEventTypeError: - eventDescription = [NSString stringWithFormat:@"error = %@", self.object]; - break; + case RACEventTypeError: + eventDescription = [NSString stringWithFormat:@"error = %@", self.object]; + break; - case RACEventTypeNext: - eventDescription = [NSString stringWithFormat:@"next = %@", self.object]; - break; + case RACEventTypeNext: + eventDescription = [NSString stringWithFormat:@"next = %@", self.object]; + break; - default: - NSCAssert(NO, @"Unrecognized event type: %i", (int)self.eventType); - } + default: + NSCAssert(NO, @"Unrecognized event type: %i", (int)self.eventType); + } - return [NSString stringWithFormat:@"<%@: %p>{ %@ }", self.class, self, eventDescription]; + return [NSString stringWithFormat:@"<%@: %p>{ %@ }", self.class, self, eventDescription]; } - (NSUInteger)hash { - return self.eventType ^ [self.object hash]; + return self.eventType ^ [self.object hash]; } - (BOOL)isEqual:(id)event { - if (event == self) return YES; - if (![event isKindOfClass:RACEvent.class]) return NO; - if (self.eventType != [event eventType]) return NO; + if (event == self) return YES; + if (![event isKindOfClass:RACEvent.class]) return NO; + if (self.eventType != [event eventType]) return NO; - // Catches the nil case too. - return self.object == [event object] || [self.object isEqual:[event object]]; + // Catches the nil case too. + return self.object == [event object] || [self.object isEqual:[event object]]; } @end diff --git a/ReactiveObjC/RACGroupedSignal.m b/ReactiveObjC/RACGroupedSignal.m index a391ef25b..27da4595a 100644 --- a/ReactiveObjC/RACGroupedSignal.m +++ b/ReactiveObjC/RACGroupedSignal.m @@ -17,9 +17,9 @@ @implementation RACGroupedSignal #pragma mark API + (instancetype)signalWithKey:(id)key { - RACGroupedSignal *subject = [self subject]; - subject.key = key; - return subject; + RACGroupedSignal *subject = [self subject]; + subject.key = key; + return subject; } @end diff --git a/ReactiveObjC/RACImmediateScheduler.m b/ReactiveObjC/RACImmediateScheduler.m index 7237801c8..8af1ab22e 100644 --- a/ReactiveObjC/RACImmediateScheduler.m +++ b/ReactiveObjC/RACImmediateScheduler.m @@ -14,41 +14,41 @@ @implementation RACImmediateScheduler #pragma mark Lifecycle - (instancetype)init { - return [super initWithName:@"org.reactivecocoa.ReactiveObjC.RACScheduler.immediateScheduler"]; + return [super initWithName:@"org.reactivecocoa.ReactiveObjC.RACScheduler.immediateScheduler"]; } #pragma mark RACScheduler - (RACDisposable *)schedule:(void (^)(void))block { - NSCParameterAssert(block != NULL); + NSCParameterAssert(block != NULL); - block(); - return nil; + block(); + return nil; } - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { - NSCParameterAssert(date != nil); - NSCParameterAssert(block != NULL); + NSCParameterAssert(date != nil); + NSCParameterAssert(block != NULL); - [NSThread sleepUntilDate:date]; - block(); + [NSThread sleepUntilDate:date]; + block(); - return nil; + return nil; } - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { - NSCAssert(NO, @"+[RACScheduler immediateScheduler] does not support %@.", NSStringFromSelector(_cmd)); - return nil; + NSCAssert(NO, @"+[RACScheduler immediateScheduler] does not support %@.", NSStringFromSelector(_cmd)); + return nil; } - (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock { - for (__block NSUInteger remaining = 1; remaining > 0; remaining--) { - recursiveBlock(^{ - remaining++; - }); - } + for (__block NSUInteger remaining = 1; remaining > 0; remaining--) { + recursiveBlock(^{ + remaining++; + }); + } - return nil; + return nil; } @end diff --git a/ReactiveObjC/RACIndexSetSequence.m b/ReactiveObjC/RACIndexSetSequence.m index 8931c6cee..a6abbb53a 100644 --- a/ReactiveObjC/RACIndexSetSequence.m +++ b/ReactiveObjC/RACIndexSetSequence.m @@ -29,83 +29,83 @@ @implementation RACIndexSetSequence #pragma mark Lifecycle + (RACSequence *)sequenceWithIndexSet:(NSIndexSet *)indexSet { - NSUInteger count = indexSet.count; - - if (count == 0) return self.empty; - - NSUInteger sizeInBytes = sizeof(NSUInteger) * count; - - NSMutableData *data = [[NSMutableData alloc] initWithCapacity:sizeInBytes]; - [indexSet getIndexes:data.mutableBytes maxCount:count inIndexRange:NULL]; - - RACIndexSetSequence *seq = [[self alloc] init]; - seq->_data = data; - seq->_indexes = data.bytes; - seq->_count = count; - return seq; + NSUInteger count = indexSet.count; + + if (count == 0) return self.empty; + + NSUInteger sizeInBytes = sizeof(NSUInteger) * count; + + NSMutableData *data = [[NSMutableData alloc] initWithCapacity:sizeInBytes]; + [indexSet getIndexes:data.mutableBytes maxCount:count inIndexRange:NULL]; + + RACIndexSetSequence *seq = [[self alloc] init]; + seq->_data = data; + seq->_indexes = data.bytes; + seq->_count = count; + return seq; } + (instancetype)sequenceWithIndexSetSequence:(RACIndexSetSequence *)indexSetSequence offset:(NSUInteger)offset { - NSCParameterAssert(offset < indexSetSequence.count); + NSCParameterAssert(offset < indexSetSequence.count); - RACIndexSetSequence *seq = [[self alloc] init]; - seq->_data = indexSetSequence.data; - seq->_indexes = indexSetSequence.indexes + offset; - seq->_count = indexSetSequence.count - offset; - return seq; + RACIndexSetSequence *seq = [[self alloc] init]; + seq->_data = indexSetSequence.data; + seq->_indexes = indexSetSequence.indexes + offset; + seq->_count = indexSetSequence.count - offset; + return seq; } #pragma mark RACSequence - (id)head { - return @(self.indexes[0]); + return @(self.indexes[0]); } - (RACSequence *)tail { - if (self.count <= 1) return [RACSequence empty]; + if (self.count <= 1) return [RACSequence empty]; - return [self.class sequenceWithIndexSetSequence:self offset:1]; + return [self.class sequenceWithIndexSetSequence:self offset:1]; } #pragma mark NSFastEnumeration - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id[])stackbuf count:(NSUInteger)len { - NSCParameterAssert(len > 0); - - if (state->state >= self.count) { - // Enumeration has completed. - return 0; - } - - if (state->state == 0) { - // Enumeration begun, mark the mutation flag. - state->mutationsPtr = state->extra; - } - - state->itemsPtr = stackbuf; - - unsigned long index = 0; - while (index < MIN(self.count - state->state, len)) { - stackbuf[index] = @(self.indexes[index + state->state]); - ++index; - } - - state->state += index; - return index; + NSCParameterAssert(len > 0); + + if (state->state >= self.count) { + // Enumeration has completed. + return 0; + } + + if (state->state == 0) { + // Enumeration begun, mark the mutation flag. + state->mutationsPtr = state->extra; + } + + state->itemsPtr = stackbuf; + + unsigned long index = 0; + while (index < MIN(self.count - state->state, len)) { + stackbuf[index] = @(self.indexes[index + state->state]); + ++index; + } + + state->state += index; + return index; } #pragma mark NSObject - (NSString *)description { - NSMutableString *indexesStr = [NSMutableString string]; + NSMutableString *indexesStr = [NSMutableString string]; - for (unsigned int i = 0; i < self.count; ++i) { - if (i > 0) [indexesStr appendString:@", "]; + for (unsigned int i = 0; i < self.count; ++i) { + if (i > 0) [indexesStr appendString:@", "]; - [indexesStr appendFormat:@"%lu", (unsigned long)self.indexes[i]]; - } + [indexesStr appendFormat:@"%lu", (unsigned long)self.indexes[i]]; + } - return [NSString stringWithFormat:@"<%@: %p>{ name = %@, indexes = %@ }", self.class, self, self.name, indexesStr]; + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, indexes = %@ }", self.class, self, self.name, indexesStr]; } @end diff --git a/ReactiveObjC/RACKVOChannel.m b/ReactiveObjC/RACKVOChannel.m index 3ac18cbfc..289088598 100644 --- a/ReactiveObjC/RACKVOChannel.m +++ b/ReactiveObjC/RACKVOChannel.m @@ -58,119 +58,119 @@ @implementation RACKVOChannel #pragma mark Properties - (RACKVOChannelData *)currentThreadData { - NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; + NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; - for (RACKVOChannelData *data in dataArray) { - if (data.owner == (__bridge void *)self) return data; - } + for (RACKVOChannelData *data in dataArray) { + if (data.owner == (__bridge void *)self) return data; + } - return nil; + return nil; } #pragma mark Lifecycle - (instancetype)initWithTarget:(__weak NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue { - NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0); - - NSObject *strongTarget = target; - - self = [super init]; - - _target = target; - _keyPath = [keyPath copy]; - - [self.leadingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -leadingTerminal", target, keyPath, nilValue]; - [self.followingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -followingTerminal", target, keyPath, nilValue]; - - if (strongTarget == nil) { - [self.leadingTerminal sendCompleted]; - return self; - } - - // Observe the key path on target for changes and forward the changes to the - // terminal. - // - // Intentionally capturing `self` strongly in the blocks below, so the - // channel object stays alive while observing. - RACDisposable *observationDisposable = [strongTarget rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - // If the change wasn't triggered by deallocation, only affects the last - // path component, and ignoreNextUpdate is set, then it was triggered by - // this channel and should not be forwarded. - if (!causedByDealloc && affectedOnlyLastComponent && self.currentThreadData.ignoreNextUpdate) { - [self destroyCurrentThreadData]; - return; - } - - [self.leadingTerminal sendNext:value]; - }]; - - NSString *keyPathByDeletingLastKeyPathComponent = keyPath.rac_keyPathByDeletingLastKeyPathComponent; - NSArray *keyPathComponents = keyPath.rac_keyPathComponents; - NSUInteger keyPathComponentsCount = keyPathComponents.count; - NSString *lastKeyPathComponent = keyPathComponents.lastObject; - - // Update the value of the property with the values received. - [[self.leadingTerminal - finally:^{ - [observationDisposable dispose]; - }] - subscribeNext:^(id x) { - // Check the value of the second to last key path component. Since the - // channel can only update the value of a property on an object, and not - // update intermediate objects, it can only update the value of the whole - // key path if this object is not nil. - NSObject *object = (keyPathComponentsCount > 1 ? [self.target valueForKeyPath:keyPathByDeletingLastKeyPathComponent] : self.target); - if (object == nil) return; - - // Set the ignoreNextUpdate flag before setting the value so this channel - // ignores the value in the subsequent -didChangeValueForKey: callback. - [self createCurrentThreadData]; - self.currentThreadData.ignoreNextUpdate = YES; - - [object setValue:x ?: nilValue forKey:lastKeyPathComponent]; - } error:^(NSError *error) { - NSCAssert(NO, @"Received error in %@: %@", self, error); - - // Log the error if we're running with assertions disabled. - NSLog(@"Received error in %@: %@", self, error); - }]; - - // Capture `self` weakly for the target's deallocation disposable, so we can - // freely deallocate if we complete before then. - @rac_weakify(self); - - [strongTarget.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - @rac_strongify(self); - [self.leadingTerminal sendCompleted]; - self.target = nil; - }]]; - - return self; + NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0); + + NSObject *strongTarget = target; + + self = [super init]; + + _target = target; + _keyPath = [keyPath copy]; + + [self.leadingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -leadingTerminal", target, keyPath, nilValue]; + [self.followingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -followingTerminal", target, keyPath, nilValue]; + + if (strongTarget == nil) { + [self.leadingTerminal sendCompleted]; + return self; + } + + // Observe the key path on target for changes and forward the changes to the + // terminal. + // + // Intentionally capturing `self` strongly in the blocks below, so the + // channel object stays alive while observing. + RACDisposable *observationDisposable = [strongTarget rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + // If the change wasn't triggered by deallocation, only affects the last + // path component, and ignoreNextUpdate is set, then it was triggered by + // this channel and should not be forwarded. + if (!causedByDealloc && affectedOnlyLastComponent && self.currentThreadData.ignoreNextUpdate) { + [self destroyCurrentThreadData]; + return; + } + + [self.leadingTerminal sendNext:value]; + }]; + + NSString *keyPathByDeletingLastKeyPathComponent = keyPath.rac_keyPathByDeletingLastKeyPathComponent; + NSArray *keyPathComponents = keyPath.rac_keyPathComponents; + NSUInteger keyPathComponentsCount = keyPathComponents.count; + NSString *lastKeyPathComponent = keyPathComponents.lastObject; + + // Update the value of the property with the values received. + [[self.leadingTerminal + finally:^{ + [observationDisposable dispose]; + }] + subscribeNext:^(id x) { + // Check the value of the second to last key path component. Since the + // channel can only update the value of a property on an object, and not + // update intermediate objects, it can only update the value of the whole + // key path if this object is not nil. + NSObject *object = (keyPathComponentsCount > 1 ? [self.target valueForKeyPath:keyPathByDeletingLastKeyPathComponent] : self.target); + if (object == nil) return; + + // Set the ignoreNextUpdate flag before setting the value so this channel + // ignores the value in the subsequent -didChangeValueForKey: callback. + [self createCurrentThreadData]; + self.currentThreadData.ignoreNextUpdate = YES; + + [object setValue:x ?: nilValue forKey:lastKeyPathComponent]; + } error:^(NSError *error) { + NSCAssert(NO, @"Received error in %@: %@", self, error); + + // Log the error if we're running with assertions disabled. + NSLog(@"Received error in %@: %@", self, error); + }]; + + // Capture `self` weakly for the target's deallocation disposable, so we can + // freely deallocate if we complete before then. + @rac_weakify(self); + + [strongTarget.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + @rac_strongify(self); + [self.leadingTerminal sendCompleted]; + self.target = nil; + }]]; + + return self; } - (void)createCurrentThreadData { - NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; - if (dataArray == nil) { - dataArray = [NSMutableArray array]; - NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey] = dataArray; - [dataArray addObject:[RACKVOChannelData dataForChannel:self]]; - return; - } - - for (RACKVOChannelData *data in dataArray) { - if (data.owner == (__bridge void *)self) return; - } - - [dataArray addObject:[RACKVOChannelData dataForChannel:self]]; + NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; + if (dataArray == nil) { + dataArray = [NSMutableArray array]; + NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey] = dataArray; + [dataArray addObject:[RACKVOChannelData dataForChannel:self]]; + return; + } + + for (RACKVOChannelData *data in dataArray) { + if (data.owner == (__bridge void *)self) return; + } + + [dataArray addObject:[RACKVOChannelData dataForChannel:self]]; } - (void)destroyCurrentThreadData { - NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; - NSUInteger index = [dataArray indexOfObjectPassingTest:^ BOOL (RACKVOChannelData *data, NSUInteger idx, BOOL *stop) { - return data.owner == (__bridge void *)self; - }]; + NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; + NSUInteger index = [dataArray indexOfObjectPassingTest:^ BOOL (RACKVOChannelData *data, NSUInteger idx, BOOL *stop) { + return data.owner == (__bridge void *)self; + }]; - if (index != NSNotFound) [dataArray removeObjectAtIndex:index]; + if (index != NSNotFound) [dataArray removeObjectAtIndex:index]; } @end @@ -178,20 +178,20 @@ - (void)destroyCurrentThreadData { @implementation RACKVOChannel (RACChannelTo) - (RACChannelTerminal *)objectForKeyedSubscript:(NSString *)key { - NSCParameterAssert(key != nil); + NSCParameterAssert(key != nil); - RACChannelTerminal *terminal = [self valueForKey:key]; - NSCAssert([terminal isKindOfClass:RACChannelTerminal.class], @"Key \"%@\" does not identify a channel terminal", key); + RACChannelTerminal *terminal = [self valueForKey:key]; + NSCAssert([terminal isKindOfClass:RACChannelTerminal.class], @"Key \"%@\" does not identify a channel terminal", key); - return terminal; + return terminal; } - (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key { - NSCParameterAssert(otherTerminal != nil); + NSCParameterAssert(otherTerminal != nil); - RACChannelTerminal *selfTerminal = [self objectForKeyedSubscript:key]; - [otherTerminal subscribe:selfTerminal]; - [[selfTerminal skip:1] subscribe:otherTerminal]; + RACChannelTerminal *selfTerminal = [self objectForKeyedSubscript:key]; + [otherTerminal subscribe:selfTerminal]; + [[selfTerminal skip:1] subscribe:otherTerminal]; } @end @@ -199,9 +199,9 @@ - (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSStrin @implementation RACKVOChannelData + (instancetype)dataForChannel:(RACKVOChannel *)channel { - RACKVOChannelData *data = [[self alloc] init]; - data->_owner = (__bridge void *)channel; - return data; + RACKVOChannelData *data = [[self alloc] init]; + data->_owner = (__bridge void *)channel; + return data; } @end diff --git a/ReactiveObjC/RACKVOProxy.m b/ReactiveObjC/RACKVOProxy.m index 706b3a10e..b8bdb1493 100644 --- a/ReactiveObjC/RACKVOProxy.m +++ b/ReactiveObjC/RACKVOProxy.m @@ -18,52 +18,52 @@ @interface RACKVOProxy() @implementation RACKVOProxy + (instancetype)sharedProxy { - static RACKVOProxy *proxy; - static dispatch_once_t onceToken; + static RACKVOProxy *proxy; + static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - proxy = [[self alloc] init]; - }); + dispatch_once(&onceToken, ^{ + proxy = [[self alloc] init]; + }); - return proxy; + return proxy; } - (instancetype)init { - self = [super init]; + self = [super init]; - _queue = dispatch_queue_create("org.reactivecocoa.ReactiveObjC.RACKVOProxy", DISPATCH_QUEUE_SERIAL); - _trampolines = [NSMapTable strongToWeakObjectsMapTable]; + _queue = dispatch_queue_create("org.reactivecocoa.ReactiveObjC.RACKVOProxy", DISPATCH_QUEUE_SERIAL); + _trampolines = [NSMapTable strongToWeakObjectsMapTable]; - return self; + return self; } - (void)addObserver:(__weak NSObject *)observer forContext:(void *)context { - NSValue *valueContext = [NSValue valueWithPointer:context]; + NSValue *valueContext = [NSValue valueWithPointer:context]; - dispatch_sync(self.queue, ^{ - [self.trampolines setObject:observer forKey:valueContext]; - }); + dispatch_sync(self.queue, ^{ + [self.trampolines setObject:observer forKey:valueContext]; + }); } - (void)removeObserver:(NSObject *)observer forContext:(void *)context { - NSValue *valueContext = [NSValue valueWithPointer:context]; + NSValue *valueContext = [NSValue valueWithPointer:context]; - dispatch_sync(self.queue, ^{ - [self.trampolines removeObjectForKey:valueContext]; - }); + dispatch_sync(self.queue, ^{ + [self.trampolines removeObjectForKey:valueContext]; + }); } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - NSValue *valueContext = [NSValue valueWithPointer:context]; - __block NSObject *trueObserver; + NSValue *valueContext = [NSValue valueWithPointer:context]; + __block NSObject *trueObserver; - dispatch_sync(self.queue, ^{ - trueObserver = [self.trampolines objectForKey:valueContext]; - }); + dispatch_sync(self.queue, ^{ + trueObserver = [self.trampolines objectForKey:valueContext]; + }); - if (trueObserver != nil) { - [trueObserver observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - } + if (trueObserver != nil) { + [trueObserver observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } } @end diff --git a/ReactiveObjC/RACKVOTrampoline.m b/ReactiveObjC/RACKVOTrampoline.m index 22428d84a..f3b84bab1 100644 --- a/ReactiveObjC/RACKVOTrampoline.m +++ b/ReactiveObjC/RACKVOTrampoline.m @@ -30,80 +30,80 @@ @implementation RACKVOTrampoline #pragma mark Lifecycle - (instancetype)initWithTarget:(__weak NSObject *)target observer:(__weak NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block { - NSCParameterAssert(keyPath != nil); - NSCParameterAssert(block != nil); + NSCParameterAssert(keyPath != nil); + NSCParameterAssert(block != nil); - NSObject *strongTarget = target; - if (strongTarget == nil) return nil; + NSObject *strongTarget = target; + if (strongTarget == nil) return nil; - self = [super init]; + self = [super init]; - _keyPath = [keyPath copy]; + _keyPath = [keyPath copy]; - _block = [block copy]; - _weakTarget = target; - _unsafeTarget = strongTarget; - _observer = observer; + _block = [block copy]; + _weakTarget = target; + _unsafeTarget = strongTarget; + _observer = observer; - [RACKVOProxy.sharedProxy addObserver:self forContext:(__bridge void *)self]; - [strongTarget addObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath options:options context:(__bridge void *)self]; + [RACKVOProxy.sharedProxy addObserver:self forContext:(__bridge void *)self]; + [strongTarget addObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath options:options context:(__bridge void *)self]; - [strongTarget.rac_deallocDisposable addDisposable:self]; - [self.observer.rac_deallocDisposable addDisposable:self]; + [strongTarget.rac_deallocDisposable addDisposable:self]; + [self.observer.rac_deallocDisposable addDisposable:self]; - return self; + return self; } - (void)dealloc { - [self dispose]; + [self dispose]; } #pragma mark Observation - (void)dispose { - NSObject *target; - NSObject *observer; + NSObject *target; + NSObject *observer; - @synchronized (self) { - _block = nil; + @synchronized (self) { + _block = nil; - // The target should still exist at this point, because we still need to - // tear down its KVO observation. Therefore, we can use the unsafe - // reference (and need to, because the weak one will have been zeroed by - // now). - target = self.unsafeTarget; - observer = self.observer; + // The target should still exist at this point, because we still need to + // tear down its KVO observation. Therefore, we can use the unsafe + // reference (and need to, because the weak one will have been zeroed by + // now). + target = self.unsafeTarget; + observer = self.observer; - _unsafeTarget = nil; - _observer = nil; - } + _unsafeTarget = nil; + _observer = nil; + } - [target.rac_deallocDisposable removeDisposable:self]; - [observer.rac_deallocDisposable removeDisposable:self]; + [target.rac_deallocDisposable removeDisposable:self]; + [observer.rac_deallocDisposable removeDisposable:self]; - [target removeObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath context:(__bridge void *)self]; - [RACKVOProxy.sharedProxy removeObserver:self forContext:(__bridge void *)self]; + [target removeObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath context:(__bridge void *)self]; + [RACKVOProxy.sharedProxy removeObserver:self forContext:(__bridge void *)self]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if (context != (__bridge void *)self) { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - return; - } + if (context != (__bridge void *)self) { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + return; + } - RACKVOBlock block; - id observer; - id target; + RACKVOBlock block; + id observer; + id target; - @synchronized (self) { - block = self.block; - observer = self.observer; - target = self.weakTarget; - } + @synchronized (self) { + block = self.block; + observer = self.observer; + target = self.weakTarget; + } - if (block == nil || target == nil) return; + if (block == nil || target == nil) return; - block(target, observer, change); + block(target, observer, change); } @end diff --git a/ReactiveObjC/RACMulticastConnection.m b/ReactiveObjC/RACMulticastConnection.m index f59824500..35e98dd71 100644 --- a/ReactiveObjC/RACMulticastConnection.m +++ b/ReactiveObjC/RACMulticastConnection.m @@ -14,17 +14,17 @@ #import @interface RACMulticastConnection () { - RACSubject *_signal; - - // When connecting, a caller should attempt to atomically swap the value of this - // from `0` to `1`. - // - // If the swap is successful the caller is resposible for subscribing `_signal` - // to `sourceSignal` and storing the returned disposable in `serialDisposable`. - // - // If the swap is unsuccessful it means that `_sourceSignal` has already been - // connected and the caller has no action to take. - int32_t volatile _hasConnected; + RACSubject *_signal; + + // When connecting, a caller should attempt to atomically swap the value of this + // from `0` to `1`. + // + // If the swap is successful the caller is resposible for subscribing `_signal` + // to `sourceSignal` and storing the returned disposable in `serialDisposable`. + // + // If the swap is unsuccessful it means that `_sourceSignal` has already been + // connected and the caller has no action to take. + int32_t volatile _hasConnected; } @property (nonatomic, readonly, strong) RACSignal *sourceSignal; @@ -36,49 +36,49 @@ @implementation RACMulticastConnection #pragma mark Lifecycle - (instancetype)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject { - NSCParameterAssert(source != nil); - NSCParameterAssert(subject != nil); + NSCParameterAssert(source != nil); + NSCParameterAssert(subject != nil); - self = [super init]; + self = [super init]; - _sourceSignal = source; - _serialDisposable = [[RACSerialDisposable alloc] init]; - _signal = subject; - - return self; + _sourceSignal = source; + _serialDisposable = [[RACSerialDisposable alloc] init]; + _signal = subject; + + return self; } #pragma mark Connecting - (RACDisposable *)connect { - BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected); + BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected); - if (shouldConnect) { - self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal]; - } + if (shouldConnect) { + self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal]; + } - return self.serialDisposable; + return self.serialDisposable; } - (RACSignal *)autoconnect { - __block volatile int32_t subscriberCount = 0; + __block volatile int32_t subscriberCount = 0; - return [[RACSignal - createSignal:^(id subscriber) { - OSAtomicIncrement32Barrier(&subscriberCount); + return [[RACSignal + createSignal:^(id subscriber) { + OSAtomicIncrement32Barrier(&subscriberCount); - RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber]; - RACDisposable *connectionDisposable = [self connect]; + RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber]; + RACDisposable *connectionDisposable = [self connect]; - return [RACDisposable disposableWithBlock:^{ - [subscriptionDisposable dispose]; + return [RACDisposable disposableWithBlock:^{ + [subscriptionDisposable dispose]; - if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) { - [connectionDisposable dispose]; - } - }]; - }] - setNameWithFormat:@"[%@] -autoconnect", self.signal.name]; + if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) { + [connectionDisposable dispose]; + } + }]; + }] + setNameWithFormat:@"[%@] -autoconnect", self.signal.name]; } @end diff --git a/ReactiveObjC/RACPassthroughSubscriber.m b/ReactiveObjC/RACPassthroughSubscriber.m index c8a7ab589..2c9c3aa70 100644 --- a/ReactiveObjC/RACPassthroughSubscriber.m +++ b/ReactiveObjC/RACPassthroughSubscriber.m @@ -14,18 +14,18 @@ #if !defined(DTRACE_PROBES_DISABLED) || !DTRACE_PROBES_DISABLED static const char *cleanedDTraceString(NSString *original) { - return [original stringByReplacingOccurrencesOfString:@"\\s+" withString:@" " options:NSRegularExpressionSearch range:NSMakeRange(0, original.length)].UTF8String; + return [original stringByReplacingOccurrencesOfString:@"\\s+" withString:@" " options:NSRegularExpressionSearch range:NSMakeRange(0, original.length)].UTF8String; } static const char *cleanedSignalDescription(RACSignal *signal) { - NSString *desc = signal.description; + NSString *desc = signal.description; - NSRange range = [desc rangeOfString:@" name:"]; - if (range.location != NSNotFound) { - desc = [desc stringByReplacingCharactersInRange:range withString:@""]; - } + NSRange range = [desc rangeOfString:@" name:"]; + if (range.location != NSNotFound) { + desc = [desc stringByReplacingCharactersInRange:range withString:@""]; + } - return cleanedDTraceString(desc); + return cleanedDTraceString(desc); } #endif @@ -53,54 +53,54 @@ @implementation RACPassthroughSubscriber #pragma mark Lifecycle - (instancetype)initWithSubscriber:(id)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable { - NSCParameterAssert(subscriber != nil); + NSCParameterAssert(subscriber != nil); - self = [super init]; + self = [super init]; - _innerSubscriber = subscriber; - _signal = signal; - _disposable = disposable; + _innerSubscriber = subscriber; + _signal = signal; + _disposable = disposable; - [self.innerSubscriber didSubscribeWithDisposable:self.disposable]; - return self; + [self.innerSubscriber didSubscribeWithDisposable:self.disposable]; + return self; } #pragma mark RACSubscriber - (void)sendNext:(id)value { - if (self.disposable.disposed) return; + if (self.disposable.disposed) return; - if (RACSIGNAL_NEXT_ENABLED()) { - RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description])); - } + if (RACSIGNAL_NEXT_ENABLED()) { + RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description])); + } - [self.innerSubscriber sendNext:value]; + [self.innerSubscriber sendNext:value]; } - (void)sendError:(NSError *)error { - if (self.disposable.disposed) return; + if (self.disposable.disposed) return; - if (RACSIGNAL_ERROR_ENABLED()) { - RACSIGNAL_ERROR(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString(error.description)); - } + if (RACSIGNAL_ERROR_ENABLED()) { + RACSIGNAL_ERROR(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString(error.description)); + } - [self.innerSubscriber sendError:error]; + [self.innerSubscriber sendError:error]; } - (void)sendCompleted { - if (self.disposable.disposed) return; + if (self.disposable.disposed) return; - if (RACSIGNAL_COMPLETED_ENABLED()) { - RACSIGNAL_COMPLETED(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description)); - } + if (RACSIGNAL_COMPLETED_ENABLED()) { + RACSIGNAL_COMPLETED(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description)); + } - [self.innerSubscriber sendCompleted]; + [self.innerSubscriber sendCompleted]; } - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable { - if (disposable != self.disposable) { - [self.disposable addDisposable:disposable]; - } + if (disposable != self.disposable) { + [self.disposable addDisposable:disposable]; + } } @end diff --git a/ReactiveObjC/RACQueueScheduler.m b/ReactiveObjC/RACQueueScheduler.m index d9dd189b8..91dbb1ad8 100644 --- a/ReactiveObjC/RACQueueScheduler.m +++ b/ReactiveObjC/RACQueueScheduler.m @@ -16,25 +16,25 @@ @implementation RACQueueScheduler #pragma mark Lifecycle - (instancetype)initWithName:(NSString *)name queue:(dispatch_queue_t)queue { - NSCParameterAssert(queue != NULL); + NSCParameterAssert(queue != NULL); - self = [super initWithName:name]; + self = [super initWithName:name]; - _queue = queue; + _queue = queue; #if !OS_OBJECT_USE_OBJC - dispatch_retain(_queue); + dispatch_retain(_queue); #endif - return self; + return self; } #if !OS_OBJECT_USE_OBJC - (void)dealloc { - if (_queue != NULL) { - dispatch_release(_queue); - _queue = NULL; - } + if (_queue != NULL) { + dispatch_release(_queue); + _queue = NULL; + } } #endif @@ -42,65 +42,65 @@ - (void)dealloc { #pragma mark Date Conversions + (dispatch_time_t)wallTimeWithDate:(NSDate *)date { - NSCParameterAssert(date != nil); + NSCParameterAssert(date != nil); - double seconds = 0; - double frac = modf(date.timeIntervalSince1970, &seconds); + double seconds = 0; + double frac = modf(date.timeIntervalSince1970, &seconds); - struct timespec walltime = { - .tv_sec = (time_t)fmin(fmax(seconds, LONG_MIN), LONG_MAX), - .tv_nsec = (long)fmin(fmax(frac * NSEC_PER_SEC, LONG_MIN), LONG_MAX) - }; + struct timespec walltime = { + .tv_sec = (time_t)fmin(fmax(seconds, LONG_MIN), LONG_MAX), + .tv_nsec = (long)fmin(fmax(frac * NSEC_PER_SEC, LONG_MIN), LONG_MAX) + }; - return dispatch_walltime(&walltime, 0); + return dispatch_walltime(&walltime, 0); } #pragma mark RACScheduler - (RACDisposable *)schedule:(void (^)(void))block { - NSCParameterAssert(block != NULL); + NSCParameterAssert(block != NULL); - RACDisposable *disposable = [[RACDisposable alloc] init]; + RACDisposable *disposable = [[RACDisposable alloc] init]; - dispatch_async(self.queue, ^{ - if (disposable.disposed) return; - [self performAsCurrentScheduler:block]; - }); + dispatch_async(self.queue, ^{ + if (disposable.disposed) return; + [self performAsCurrentScheduler:block]; + }); - return disposable; + return disposable; } - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { - NSCParameterAssert(date != nil); - NSCParameterAssert(block != NULL); + NSCParameterAssert(date != nil); + NSCParameterAssert(block != NULL); - RACDisposable *disposable = [[RACDisposable alloc] init]; + RACDisposable *disposable = [[RACDisposable alloc] init]; - dispatch_after([self.class wallTimeWithDate:date], self.queue, ^{ - if (disposable.disposed) return; - [self performAsCurrentScheduler:block]; - }); + dispatch_after([self.class wallTimeWithDate:date], self.queue, ^{ + if (disposable.disposed) return; + [self performAsCurrentScheduler:block]; + }); - return disposable; + return disposable; } - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { - NSCParameterAssert(date != nil); - NSCParameterAssert(interval > 0.0 && interval < INT64_MAX / NSEC_PER_SEC); - NSCParameterAssert(leeway >= 0.0 && leeway < INT64_MAX / NSEC_PER_SEC); - NSCParameterAssert(block != NULL); - - uint64_t intervalInNanoSecs = (uint64_t)(interval * NSEC_PER_SEC); - uint64_t leewayInNanoSecs = (uint64_t)(leeway * NSEC_PER_SEC); - - dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue); - dispatch_source_set_timer(timer, [self.class wallTimeWithDate:date], intervalInNanoSecs, leewayInNanoSecs); - dispatch_source_set_event_handler(timer, block); - dispatch_resume(timer); - - return [RACDisposable disposableWithBlock:^{ - dispatch_source_cancel(timer); - }]; + NSCParameterAssert(date != nil); + NSCParameterAssert(interval > 0.0 && interval < INT64_MAX / NSEC_PER_SEC); + NSCParameterAssert(leeway >= 0.0 && leeway < INT64_MAX / NSEC_PER_SEC); + NSCParameterAssert(block != NULL); + + uint64_t intervalInNanoSecs = (uint64_t)(interval * NSEC_PER_SEC); + uint64_t leewayInNanoSecs = (uint64_t)(leeway * NSEC_PER_SEC); + + dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue); + dispatch_source_set_timer(timer, [self.class wallTimeWithDate:date], intervalInNanoSecs, leewayInNanoSecs); + dispatch_source_set_event_handler(timer, block); + dispatch_resume(timer); + + return [RACDisposable disposableWithBlock:^{ + dispatch_source_cancel(timer); + }]; } @end diff --git a/ReactiveObjC/RACReplaySubject.m b/ReactiveObjC/RACReplaySubject.m index 30475fa9e..8881fe822 100644 --- a/ReactiveObjC/RACReplaySubject.m +++ b/ReactiveObjC/RACReplaySubject.m @@ -33,79 +33,79 @@ @implementation RACReplaySubject #pragma mark Lifecycle + (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity { - return [(RACReplaySubject *)[self alloc] initWithCapacity:capacity]; + return [(RACReplaySubject *)[self alloc] initWithCapacity:capacity]; } - (instancetype)init { - return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity]; + return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity]; } - (instancetype)initWithCapacity:(NSUInteger)capacity { - self = [super init]; - - _capacity = capacity; - _valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]); - - return self; + self = [super init]; + + _capacity = capacity; + _valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]); + + return self; } #pragma mark RACSignal - (RACDisposable *)subscribe:(id)subscriber { - RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; + RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; - RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ - @synchronized (self) { - for (id value in self.valuesReceived) { - if (compoundDisposable.disposed) return; + RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ + @synchronized (self) { + for (id value in self.valuesReceived) { + if (compoundDisposable.disposed) return; - [subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)]; - } + [subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)]; + } - if (compoundDisposable.disposed) return; + if (compoundDisposable.disposed) return; - if (self.hasCompleted) { - [subscriber sendCompleted]; - } else if (self.hasError) { - [subscriber sendError:self.error]; - } else { - RACDisposable *subscriptionDisposable = [super subscribe:subscriber]; - [compoundDisposable addDisposable:subscriptionDisposable]; - } - } - }]; + if (self.hasCompleted) { + [subscriber sendCompleted]; + } else if (self.hasError) { + [subscriber sendError:self.error]; + } else { + RACDisposable *subscriptionDisposable = [super subscribe:subscriber]; + [compoundDisposable addDisposable:subscriptionDisposable]; + } + } + }]; - [compoundDisposable addDisposable:schedulingDisposable]; + [compoundDisposable addDisposable:schedulingDisposable]; - return compoundDisposable; + return compoundDisposable; } #pragma mark RACSubscriber - (void)sendNext:(id)value { - @synchronized (self) { - [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil]; - [super sendNext:value]; - - if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) { - [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)]; - } - } + @synchronized (self) { + [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil]; + [super sendNext:value]; + + if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) { + [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)]; + } + } } - (void)sendCompleted { - @synchronized (self) { - self.hasCompleted = YES; - [super sendCompleted]; - } + @synchronized (self) { + self.hasCompleted = YES; + [super sendCompleted]; + } } - (void)sendError:(NSError *)e { - @synchronized (self) { - self.hasError = YES; - self.error = e; - [super sendError:e]; - } + @synchronized (self) { + self.hasError = YES; + self.error = e; + [super sendError:e]; + } } @end diff --git a/ReactiveObjC/RACReturnSignal.m b/ReactiveObjC/RACReturnSignal.m index dca319d08..e0834398d 100644 --- a/ReactiveObjC/RACReturnSignal.m +++ b/ReactiveObjC/RACReturnSignal.m @@ -26,15 +26,15 @@ @implementation RACReturnSignal // potentially a singleton in release builds (see +return:). - (void)setName:(NSString *)name { #ifdef DEBUG - [super setName:name]; + [super setName:name]; #endif } - (NSString *)name { #ifdef DEBUG - return super.name; + return super.name; #else - return @"+return:"; + return @"+return:"; #endif } @@ -42,49 +42,49 @@ - (NSString *)name { + (RACSignal *)return:(id)value { #ifndef DEBUG - // In release builds, use singletons for two very common cases. - if (value == RACUnit.defaultUnit) { - static RACReturnSignal *unitSingleton; - static dispatch_once_t unitPred; - - dispatch_once(&unitPred, ^{ - unitSingleton = [[self alloc] init]; - unitSingleton->_value = RACUnit.defaultUnit; - }); - - return unitSingleton; - } else if (value == nil) { - static RACReturnSignal *nilSingleton; - static dispatch_once_t nilPred; - - dispatch_once(&nilPred, ^{ - nilSingleton = [[self alloc] init]; - nilSingleton->_value = nil; - }); - - return nilSingleton; - } + // In release builds, use singletons for two very common cases. + if (value == RACUnit.defaultUnit) { + static RACReturnSignal *unitSingleton; + static dispatch_once_t unitPred; + + dispatch_once(&unitPred, ^{ + unitSingleton = [[self alloc] init]; + unitSingleton->_value = RACUnit.defaultUnit; + }); + + return unitSingleton; + } else if (value == nil) { + static RACReturnSignal *nilSingleton; + static dispatch_once_t nilPred; + + dispatch_once(&nilPred, ^{ + nilSingleton = [[self alloc] init]; + nilSingleton->_value = nil; + }); + + return nilSingleton; + } #endif - RACReturnSignal *signal = [[self alloc] init]; - signal->_value = value; + RACReturnSignal *signal = [[self alloc] init]; + signal->_value = value; #ifdef DEBUG - [signal setNameWithFormat:@"+return: %@", value]; + [signal setNameWithFormat:@"+return: %@", value]; #endif - return signal; + return signal; } #pragma mark Subscription - (RACDisposable *)subscribe:(id)subscriber { - NSCParameterAssert(subscriber != nil); + NSCParameterAssert(subscriber != nil); - return [RACScheduler.subscriptionScheduler schedule:^{ - [subscriber sendNext:self.value]; - [subscriber sendCompleted]; - }]; + return [RACScheduler.subscriptionScheduler schedule:^{ + [subscriber sendNext:self.value]; + [subscriber sendCompleted]; + }]; } @end diff --git a/ReactiveObjC/RACScheduler.h b/ReactiveObjC/RACScheduler.h index c780e337b..21d4cbb44 100644 --- a/ReactiveObjC/RACScheduler.h +++ b/ReactiveObjC/RACScheduler.h @@ -17,10 +17,10 @@ NS_ASSUME_NONNULL_BEGIN /// RACSchedulerPriorityLow - Low priority. /// RACSchedulerPriorityBackground - Background priority. typedef enum : long { - RACSchedulerPriorityHigh = DISPATCH_QUEUE_PRIORITY_HIGH, - RACSchedulerPriorityDefault = DISPATCH_QUEUE_PRIORITY_DEFAULT, - RACSchedulerPriorityLow = DISPATCH_QUEUE_PRIORITY_LOW, - RACSchedulerPriorityBackground = DISPATCH_QUEUE_PRIORITY_BACKGROUND, + RACSchedulerPriorityHigh = DISPATCH_QUEUE_PRIORITY_HIGH, + RACSchedulerPriorityDefault = DISPATCH_QUEUE_PRIORITY_DEFAULT, + RACSchedulerPriorityLow = DISPATCH_QUEUE_PRIORITY_LOW, + RACSchedulerPriorityBackground = DISPATCH_QUEUE_PRIORITY_BACKGROUND, } RACSchedulerPriority; /// Scheduled with -scheduleRecursiveBlock:, this type of block is passed a block diff --git a/ReactiveObjC/RACScheduler.m b/ReactiveObjC/RACScheduler.m index e260e1396..5fe0f19e7 100644 --- a/ReactiveObjC/RACScheduler.m +++ b/ReactiveObjC/RACScheduler.m @@ -26,186 +26,186 @@ @implementation RACScheduler #pragma mark NSObject - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.name]; + return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.name]; } #pragma mark Initializers - (instancetype)initWithName:(NSString *)name { - self = [super init]; + self = [super init]; - if (name == nil) { - _name = [NSString stringWithFormat:@"org.reactivecocoa.ReactiveObjC.%@.anonymousScheduler", self.class]; - } else { - _name = [name copy]; - } + if (name == nil) { + _name = [NSString stringWithFormat:@"org.reactivecocoa.ReactiveObjC.%@.anonymousScheduler", self.class]; + } else { + _name = [name copy]; + } - return self; + return self; } #pragma mark Schedulers + (RACScheduler *)immediateScheduler { - static dispatch_once_t onceToken; - static RACScheduler *immediateScheduler; - dispatch_once(&onceToken, ^{ - immediateScheduler = [[RACImmediateScheduler alloc] init]; - }); - - return immediateScheduler; + static dispatch_once_t onceToken; + static RACScheduler *immediateScheduler; + dispatch_once(&onceToken, ^{ + immediateScheduler = [[RACImmediateScheduler alloc] init]; + }); + + return immediateScheduler; } + (RACScheduler *)mainThreadScheduler { - static dispatch_once_t onceToken; - static RACScheduler *mainThreadScheduler; - dispatch_once(&onceToken, ^{ - mainThreadScheduler = [[RACTargetQueueScheduler alloc] initWithName:@"org.reactivecocoa.ReactiveObjC.RACScheduler.mainThreadScheduler" targetQueue:dispatch_get_main_queue()]; - }); - - return mainThreadScheduler; + static dispatch_once_t onceToken; + static RACScheduler *mainThreadScheduler; + dispatch_once(&onceToken, ^{ + mainThreadScheduler = [[RACTargetQueueScheduler alloc] initWithName:@"org.reactivecocoa.ReactiveObjC.RACScheduler.mainThreadScheduler" targetQueue:dispatch_get_main_queue()]; + }); + + return mainThreadScheduler; } + (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name { - return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)]; + return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)]; } + (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority { - return [self schedulerWithPriority:priority name:@"org.reactivecocoa.ReactiveObjC.RACScheduler.backgroundScheduler"]; + return [self schedulerWithPriority:priority name:@"org.reactivecocoa.ReactiveObjC.RACScheduler.backgroundScheduler"]; } + (RACScheduler *)scheduler { - return [self schedulerWithPriority:RACSchedulerPriorityDefault]; + return [self schedulerWithPriority:RACSchedulerPriorityDefault]; } + (RACScheduler *)subscriptionScheduler { - static dispatch_once_t onceToken; - static RACScheduler *subscriptionScheduler; - dispatch_once(&onceToken, ^{ - subscriptionScheduler = [[RACSubscriptionScheduler alloc] init]; - }); + static dispatch_once_t onceToken; + static RACScheduler *subscriptionScheduler; + dispatch_once(&onceToken, ^{ + subscriptionScheduler = [[RACSubscriptionScheduler alloc] init]; + }); - return subscriptionScheduler; + return subscriptionScheduler; } + (BOOL)isOnMainThread { - return [NSOperationQueue.currentQueue isEqual:NSOperationQueue.mainQueue] || [NSThread isMainThread]; + return [NSOperationQueue.currentQueue isEqual:NSOperationQueue.mainQueue] || [NSThread isMainThread]; } + (RACScheduler *)currentScheduler { - RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey]; - if (scheduler != nil) return scheduler; - if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler; + RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey]; + if (scheduler != nil) return scheduler; + if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler; - return nil; + return nil; } #pragma mark Scheduling - (RACDisposable *)schedule:(void (^)(void))block { - NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); - return nil; + NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); + return nil; } - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { - NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); - return nil; + NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); + return nil; } - (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block { - return [self after:[NSDate dateWithTimeIntervalSinceNow:delay] schedule:block]; + return [self after:[NSDate dateWithTimeIntervalSinceNow:delay] schedule:block]; } - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { - NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); - return nil; + NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); + return nil; } - (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock { - RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; - [self scheduleRecursiveBlock:[recursiveBlock copy] addingToDisposable:disposable]; - return disposable; + [self scheduleRecursiveBlock:[recursiveBlock copy] addingToDisposable:disposable]; + return disposable; } - (void)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock addingToDisposable:(RACCompoundDisposable *)disposable { - @autoreleasepool { - RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable]; - [disposable addDisposable:selfDisposable]; + @autoreleasepool { + RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable]; + [disposable addDisposable:selfDisposable]; - __weak RACDisposable *weakSelfDisposable = selfDisposable; + __weak RACDisposable *weakSelfDisposable = selfDisposable; - RACDisposable *schedulingDisposable = [self schedule:^{ - @autoreleasepool { - // At this point, we've been invoked, so our disposable is now useless. - [disposable removeDisposable:weakSelfDisposable]; - } + RACDisposable *schedulingDisposable = [self schedule:^{ + @autoreleasepool { + // At this point, we've been invoked, so our disposable is now useless. + [disposable removeDisposable:weakSelfDisposable]; + } - if (disposable.disposed) return; + if (disposable.disposed) return; - void (^reallyReschedule)(void) = ^{ - if (disposable.disposed) return; - [self scheduleRecursiveBlock:recursiveBlock addingToDisposable:disposable]; - }; + void (^reallyReschedule)(void) = ^{ + if (disposable.disposed) return; + [self scheduleRecursiveBlock:recursiveBlock addingToDisposable:disposable]; + }; - // Protects the variables below. - // - // This doesn't actually need to be __block qualified, but Clang - // complains otherwise. :C - __block NSLock *lock = [[NSLock alloc] init]; - lock.name = [NSString stringWithFormat:@"%@ %s", self, sel_getName(_cmd)]; + // Protects the variables below. + // + // This doesn't actually need to be __block qualified, but Clang + // complains otherwise. :C + __block NSLock *lock = [[NSLock alloc] init]; + lock.name = [NSString stringWithFormat:@"%@ %s", self, sel_getName(_cmd)]; - __block NSUInteger rescheduleCount = 0; + __block NSUInteger rescheduleCount = 0; - // Set to YES once synchronous execution has finished. Further - // rescheduling should occur immediately (rather than being - // flattened). - __block BOOL rescheduleImmediately = NO; + // Set to YES once synchronous execution has finished. Further + // rescheduling should occur immediately (rather than being + // flattened). + __block BOOL rescheduleImmediately = NO; - @autoreleasepool { - recursiveBlock(^{ - [lock lock]; - BOOL immediate = rescheduleImmediately; - if (!immediate) ++rescheduleCount; - [lock unlock]; + @autoreleasepool { + recursiveBlock(^{ + [lock lock]; + BOOL immediate = rescheduleImmediately; + if (!immediate) ++rescheduleCount; + [lock unlock]; - if (immediate) reallyReschedule(); - }); - } + if (immediate) reallyReschedule(); + }); + } - [lock lock]; - NSUInteger synchronousCount = rescheduleCount; - rescheduleImmediately = YES; - [lock unlock]; + [lock lock]; + NSUInteger synchronousCount = rescheduleCount; + rescheduleImmediately = YES; + [lock unlock]; - for (NSUInteger i = 0; i < synchronousCount; i++) { - reallyReschedule(); - } - }]; + for (NSUInteger i = 0; i < synchronousCount; i++) { + reallyReschedule(); + } + }]; - [selfDisposable addDisposable:schedulingDisposable]; - } + [selfDisposable addDisposable:schedulingDisposable]; + } } - (void)performAsCurrentScheduler:(void (^)(void))block { - NSCParameterAssert(block != NULL); + NSCParameterAssert(block != NULL); - // If we're using a concurrent queue, we could end up in here concurrently, - // in which case we *don't* want to clear the current scheduler immediately - // after our block is done executing, but only *after* all our concurrent - // invocations are done. + // If we're using a concurrent queue, we could end up in here concurrently, + // in which case we *don't* want to clear the current scheduler immediately + // after our block is done executing, but only *after* all our concurrent + // invocations are done. - RACScheduler *previousScheduler = RACScheduler.currentScheduler; - NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self; + RACScheduler *previousScheduler = RACScheduler.currentScheduler; + NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self; - @autoreleasepool { - block(); - } + @autoreleasepool { + block(); + } - if (previousScheduler != nil) { - NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler; - } else { - [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey]; - } + if (previousScheduler != nil) { + NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler; + } else { + [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey]; + } } @end diff --git a/ReactiveObjC/RACScopedDisposable.m b/ReactiveObjC/RACScopedDisposable.m index 2da50cb91..4f0529978 100644 --- a/ReactiveObjC/RACScopedDisposable.m +++ b/ReactiveObjC/RACScopedDisposable.m @@ -13,20 +13,20 @@ @implementation RACScopedDisposable #pragma mark Lifecycle + (instancetype)scopedDisposableWithDisposable:(RACDisposable *)disposable { - return [self disposableWithBlock:^{ - [disposable dispose]; - }]; + return [self disposableWithBlock:^{ + [disposable dispose]; + }]; } - (void)dealloc { - [self dispose]; + [self dispose]; } #pragma mark RACDisposable - (RACScopedDisposable *)asScopedDisposable { - // totally already are - return self; + // totally already are + return self; } @end diff --git a/ReactiveObjC/RACSequence.m b/ReactiveObjC/RACSequence.m index baf830294..71389892f 100644 --- a/ReactiveObjC/RACSequence.m +++ b/ReactiveObjC/RACSequence.m @@ -43,14 +43,14 @@ - (RACSequence *)bind:(RACSequenceBindBlock)block passingThroughValuesFromSequen @implementation RACSequenceEnumerator - (id)nextObject { - id object = nil; - - @synchronized (self) { - object = self.sequence.head; - self.sequence = self.sequence.tail; - } - - return object; + id object = nil; + + @synchronized (self) { + object = self.sequence.head; + self.sequence = self.sequence.tail; + } + + return object; } @end @@ -60,312 +60,312 @@ @implementation RACSequence #pragma mark Lifecycle + (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock { - return [[RACDynamicSequence sequenceWithHeadBlock:headBlock tailBlock:tailBlock] setNameWithFormat:@"+sequenceWithHeadBlock:tailBlock:"]; + return [[RACDynamicSequence sequenceWithHeadBlock:headBlock tailBlock:tailBlock] setNameWithFormat:@"+sequenceWithHeadBlock:tailBlock:"]; } #pragma mark Class cluster primitives - (id)head { - NSCAssert(NO, @"%s must be overridden by subclasses", __func__); - return nil; + NSCAssert(NO, @"%s must be overridden by subclasses", __func__); + return nil; } - (RACSequence *)tail { - NSCAssert(NO, @"%s must be overridden by subclasses", __func__); - return nil; + NSCAssert(NO, @"%s must be overridden by subclasses", __func__); + return nil; } #pragma mark RACStream + (RACSequence *)empty { - return RACEmptySequence.empty; + return RACEmptySequence.empty; } + (RACSequence *)return:(id)value { - return [RACUnarySequence return:value]; + return [RACUnarySequence return:value]; } - (RACSequence *)bind:(RACSequenceBindBlock (^)(void))block { - RACSequenceBindBlock bindBlock = block(); - return [[self bind:bindBlock passingThroughValuesFromSequence:nil] setNameWithFormat:@"[%@] -bind:", self.name]; + RACSequenceBindBlock bindBlock = block(); + return [[self bind:bindBlock passingThroughValuesFromSequence:nil] setNameWithFormat:@"[%@] -bind:", self.name]; } - (RACSequence *)bind:(RACSequenceBindBlock)bindBlock passingThroughValuesFromSequence:(RACSequence *)passthroughSequence { - // Store values calculated in the dependency here instead, avoiding any kind - // of temporary collection and boxing. - // - // This relies on the implementation of RACDynamicSequence synchronizing - // access to its head, tail, and dependency, and we're only doing it because - // we really need the performance. - __block RACSequence *valuesSeq = self; - __block RACSequence *current = passthroughSequence; - __block BOOL stop = NO; - - RACSequence *sequence = [RACDynamicSequence sequenceWithLazyDependency:^ id { - while (current.head == nil) { - if (stop) return nil; - - // We've exhausted the current sequence, create a sequence from the - // next value. - id value = valuesSeq.head; - - if (value == nil) { - // We've exhausted all the sequences. - stop = YES; - return nil; - } - - current = (id)bindBlock(value, &stop); - if (current == nil) { - stop = YES; - return nil; - } - - valuesSeq = valuesSeq.tail; - } - - NSCAssert([current isKindOfClass:RACSequence.class], @"-bind: block returned an object that is not a sequence: %@", current); - return nil; - } headBlock:^(id _) { - return current.head; - } tailBlock:^ id (id _) { - if (stop) return nil; - - return [valuesSeq bind:bindBlock passingThroughValuesFromSequence:current.tail]; - }]; - - sequence.name = self.name; - return sequence; + // Store values calculated in the dependency here instead, avoiding any kind + // of temporary collection and boxing. + // + // This relies on the implementation of RACDynamicSequence synchronizing + // access to its head, tail, and dependency, and we're only doing it because + // we really need the performance. + __block RACSequence *valuesSeq = self; + __block RACSequence *current = passthroughSequence; + __block BOOL stop = NO; + + RACSequence *sequence = [RACDynamicSequence sequenceWithLazyDependency:^ id { + while (current.head == nil) { + if (stop) return nil; + + // We've exhausted the current sequence, create a sequence from the + // next value. + id value = valuesSeq.head; + + if (value == nil) { + // We've exhausted all the sequences. + stop = YES; + return nil; + } + + current = (id)bindBlock(value, &stop); + if (current == nil) { + stop = YES; + return nil; + } + + valuesSeq = valuesSeq.tail; + } + + NSCAssert([current isKindOfClass:RACSequence.class], @"-bind: block returned an object that is not a sequence: %@", current); + return nil; + } headBlock:^(id _) { + return current.head; + } tailBlock:^ id (id _) { + if (stop) return nil; + + return [valuesSeq bind:bindBlock passingThroughValuesFromSequence:current.tail]; + }]; + + sequence.name = self.name; + return sequence; } - (RACSequence *)concat:(RACSequence *)sequence { - NSCParameterAssert(sequence != nil); + NSCParameterAssert(sequence != nil); - return [[[RACArraySequence sequenceWithArray:@[ self, sequence ] offset:0] - flatten] - setNameWithFormat:@"[%@] -concat: %@", self.name, sequence]; + return [[[RACArraySequence sequenceWithArray:@[ self, sequence ] offset:0] + flatten] + setNameWithFormat:@"[%@] -concat: %@", self.name, sequence]; } - (RACSequence *)zipWith:(RACSequence *)sequence { - NSCParameterAssert(sequence != nil); - - return [[RACSequence - sequenceWithHeadBlock:^ id { - if (self.head == nil || sequence.head == nil) return nil; - return RACTuplePack(self.head, sequence.head); - } tailBlock:^ id { - if (self.tail == nil || [[RACSequence empty] isEqual:self.tail]) return nil; - if (sequence.tail == nil || [[RACSequence empty] isEqual:sequence.tail]) return nil; - - return [self.tail zipWith:sequence.tail]; - }] - setNameWithFormat:@"[%@] -zipWith: %@", self.name, sequence]; + NSCParameterAssert(sequence != nil); + + return [[RACSequence + sequenceWithHeadBlock:^ id { + if (self.head == nil || sequence.head == nil) return nil; + return RACTuplePack(self.head, sequence.head); + } tailBlock:^ id { + if (self.tail == nil || [[RACSequence empty] isEqual:self.tail]) return nil; + if (sequence.tail == nil || [[RACSequence empty] isEqual:sequence.tail]) return nil; + + return [self.tail zipWith:sequence.tail]; + }] + setNameWithFormat:@"[%@] -zipWith: %@", self.name, sequence]; } #pragma mark Extended methods - (NSArray *)array { - NSMutableArray *array = [NSMutableArray array]; - for (id obj in self) { - [array addObject:obj]; - } + NSMutableArray *array = [NSMutableArray array]; + for (id obj in self) { + [array addObject:obj]; + } - return [array copy]; + return [array copy]; } - (NSEnumerator *)objectEnumerator { - RACSequenceEnumerator *enumerator = [[RACSequenceEnumerator alloc] init]; - enumerator.sequence = self; - return enumerator; + RACSequenceEnumerator *enumerator = [[RACSequenceEnumerator alloc] init]; + enumerator.sequence = self; + return enumerator; } - (RACSignal *)signal { - return [[self signalWithScheduler:[RACScheduler scheduler]] setNameWithFormat:@"[%@] -signal", self.name]; + return [[self signalWithScheduler:[RACScheduler scheduler]] setNameWithFormat:@"[%@] -signal", self.name]; } - (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler { - return [[RACSignal createSignal:^(id subscriber) { - __block RACSequence *sequence = self; + return [[RACSignal createSignal:^(id subscriber) { + __block RACSequence *sequence = self; - return [scheduler scheduleRecursiveBlock:^(void (^reschedule)(void)) { - if (sequence.head == nil) { - [subscriber sendCompleted]; - return; - } + return [scheduler scheduleRecursiveBlock:^(void (^reschedule)(void)) { + if (sequence.head == nil) { + [subscriber sendCompleted]; + return; + } - [subscriber sendNext:sequence.head]; + [subscriber sendNext:sequence.head]; - sequence = sequence.tail; - reschedule(); - }]; - }] setNameWithFormat:@"[%@] -signalWithScheduler: %@", self.name, scheduler]; + sequence = sequence.tail; + reschedule(); + }]; + }] setNameWithFormat:@"[%@] -signalWithScheduler: %@", self.name, scheduler]; } - (id)foldLeftWithStart:(id)start reduce:(id (^)(id, id))reduce { - NSCParameterAssert(reduce != NULL); - - if (self.head == nil) return start; - - for (id value in self) { - start = reduce(start, value); - } - - return start; + NSCParameterAssert(reduce != NULL); + + if (self.head == nil) return start; + + for (id value in self) { + start = reduce(start, value); + } + + return start; } - (id)foldRightWithStart:(id)start reduce:(id (^)(id, RACSequence *))reduce { - NSCParameterAssert(reduce != NULL); - - if (self.head == nil) return start; - - RACSequence *rest = [RACSequence sequenceWithHeadBlock:^{ - if (self.tail) { - return [self.tail foldRightWithStart:start reduce:reduce]; - } else { - return start; - } - } tailBlock:nil]; - - return reduce(self.head, rest); + NSCParameterAssert(reduce != NULL); + + if (self.head == nil) return start; + + RACSequence *rest = [RACSequence sequenceWithHeadBlock:^{ + if (self.tail) { + return [self.tail foldRightWithStart:start reduce:reduce]; + } else { + return start; + } + } tailBlock:nil]; + + return reduce(self.head, rest); } - (BOOL)any:(BOOL (^)(id))block { - NSCParameterAssert(block != NULL); + NSCParameterAssert(block != NULL); - return [self objectPassingTest:block] != nil; + return [self objectPassingTest:block] != nil; } - (BOOL)all:(BOOL (^)(id))block { - NSCParameterAssert(block != NULL); - - NSNumber *result = [self foldLeftWithStart:@YES reduce:^(NSNumber *accumulator, id value) { - return @(accumulator.boolValue && block(value)); - }]; - - return result.boolValue; + NSCParameterAssert(block != NULL); + + NSNumber *result = [self foldLeftWithStart:@YES reduce:^(NSNumber *accumulator, id value) { + return @(accumulator.boolValue && block(value)); + }]; + + return result.boolValue; } - (id)objectPassingTest:(BOOL (^)(id))block { - NSCParameterAssert(block != NULL); + NSCParameterAssert(block != NULL); - return [self filter:block].head; + return [self filter:block].head; } - (RACSequence *)eagerSequence { - return [RACEagerSequence sequenceWithArray:self.array offset:0]; + return [RACEagerSequence sequenceWithArray:self.array offset:0]; } - (RACSequence *)lazySequence { - return self; + return self; } #pragma mark NSCopying - (id)copyWithZone:(NSZone *)zone { - return self; + return self; } #pragma mark NSCoding - (Class)classForCoder { - // Most sequences should be archived as RACArraySequences. - return RACArraySequence.class; + // Most sequences should be archived as RACArraySequences. + return RACArraySequence.class; } - (id)initWithCoder:(NSCoder *)coder { - if (![self isKindOfClass:RACArraySequence.class]) return [[RACArraySequence alloc] initWithCoder:coder]; + if (![self isKindOfClass:RACArraySequence.class]) return [[RACArraySequence alloc] initWithCoder:coder]; - // Decoding is handled in RACArraySequence. - return [super init]; + // Decoding is handled in RACArraySequence. + return [super init]; } - (void)encodeWithCoder:(NSCoder *)coder { - [coder encodeObject:self.array forKey:@"array"]; + [coder encodeObject:self.array forKey:@"array"]; } #pragma mark NSFastEnumeration - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id *)stackbuf count:(NSUInteger)len { - if (state->state == ULONG_MAX) { - // Enumeration has completed. - return 0; - } - - // We need to traverse the sequence itself on repeated calls to this - // method, so use the 'state' field to track the current head. - RACSequence *(^getSequence)(void) = ^{ - return (__bridge RACSequence *)(void *)state->state; - }; - - void (^setSequence)(RACSequence *) = ^(RACSequence *sequence) { - // Release the old sequence and retain the new one. - CFBridgingRelease((void *)state->state); - - state->state = (unsigned long)CFBridgingRetain(sequence); - }; - - void (^complete)(void) = ^{ - // Release any stored sequence. - setSequence(nil); - state->state = ULONG_MAX; - }; - - if (state->state == 0) { - // Since a sequence doesn't mutate, this just needs to be set to - // something non-NULL. - state->mutationsPtr = state->extra; - - setSequence(self); - } - - state->itemsPtr = stackbuf; - - NSUInteger enumeratedCount = 0; - while (enumeratedCount < len) { - RACSequence *seq = getSequence(); - - // Because the objects in a sequence may be generated lazily, we want to - // prevent them from being released until the enumerator's used them. - __autoreleasing id obj = seq.head; - if (obj == nil) { - complete(); - break; - } - - stackbuf[enumeratedCount++] = obj; - - if (seq.tail == nil) { - complete(); - break; - } - - setSequence(seq.tail); - } - - return enumeratedCount; + if (state->state == ULONG_MAX) { + // Enumeration has completed. + return 0; + } + + // We need to traverse the sequence itself on repeated calls to this + // method, so use the 'state' field to track the current head. + RACSequence *(^getSequence)(void) = ^{ + return (__bridge RACSequence *)(void *)state->state; + }; + + void (^setSequence)(RACSequence *) = ^(RACSequence *sequence) { + // Release the old sequence and retain the new one. + CFBridgingRelease((void *)state->state); + + state->state = (unsigned long)CFBridgingRetain(sequence); + }; + + void (^complete)(void) = ^{ + // Release any stored sequence. + setSequence(nil); + state->state = ULONG_MAX; + }; + + if (state->state == 0) { + // Since a sequence doesn't mutate, this just needs to be set to + // something non-NULL. + state->mutationsPtr = state->extra; + + setSequence(self); + } + + state->itemsPtr = stackbuf; + + NSUInteger enumeratedCount = 0; + while (enumeratedCount < len) { + RACSequence *seq = getSequence(); + + // Because the objects in a sequence may be generated lazily, we want to + // prevent them from being released until the enumerator's used them. + __autoreleasing id obj = seq.head; + if (obj == nil) { + complete(); + break; + } + + stackbuf[enumeratedCount++] = obj; + + if (seq.tail == nil) { + complete(); + break; + } + + setSequence(seq.tail); + } + + return enumeratedCount; } #pragma mark NSObject - (NSUInteger)hash { - return [self.head hash]; + return [self.head hash]; } - (BOOL)isEqual:(RACSequence *)seq { - if (self == seq) return YES; - if (![seq isKindOfClass:RACSequence.class]) return NO; + if (self == seq) return YES; + if (![seq isKindOfClass:RACSequence.class]) return NO; - for (id selfObj in self) { - id seqObj = seq.head; + for (id selfObj in self) { + id seqObj = seq.head; - // Handles the nil case too. - if (![seqObj isEqual:selfObj]) return NO; + // Handles the nil case too. + if (![seqObj isEqual:selfObj]) return NO; - seq = seq.tail; - } + seq = seq.tail; + } - // self is now depleted -- the argument should be too. - return (seq.head == nil); + // self is now depleted -- the argument should be too. + return (seq.head == nil); } @end diff --git a/ReactiveObjC/RACSerialDisposable.m b/ReactiveObjC/RACSerialDisposable.m index 38a9f1a85..380d5e0a3 100644 --- a/ReactiveObjC/RACSerialDisposable.m +++ b/ReactiveObjC/RACSerialDisposable.m @@ -10,16 +10,16 @@ #import @interface RACSerialDisposable () { - // The receiver's `disposable`. This variable must only be referenced while - // _mutex is held. - RACDisposable * _disposable; + // The receiver's `disposable`. This variable must only be referenced while + // _mutex is held. + RACDisposable * _disposable; - // YES if the receiver has been disposed. This variable must only be accessed - // while _mutex is held. - BOOL _disposed; + // YES if the receiver has been disposed. This variable must only be accessed + // while _mutex is held. + BOOL _disposed; - // A mutex to protect access to _disposable and _disposed. - pthread_mutex_t _mutex; + // A mutex to protect access to _disposable and _disposed. + pthread_mutex_t _mutex; } @end @@ -29,93 +29,93 @@ @implementation RACSerialDisposable #pragma mark Properties - (BOOL)isDisposed { - pthread_mutex_lock(&_mutex); - const BOOL disposed = _disposed; - pthread_mutex_unlock(&_mutex); + pthread_mutex_lock(&_mutex); + const BOOL disposed = _disposed; + pthread_mutex_unlock(&_mutex); - return disposed; + return disposed; } - (RACDisposable *)disposable { - pthread_mutex_lock(&_mutex); - RACDisposable * const result = _disposable; - pthread_mutex_unlock(&_mutex); + pthread_mutex_lock(&_mutex); + RACDisposable * const result = _disposable; + pthread_mutex_unlock(&_mutex); - return result; + return result; } - (void)setDisposable:(RACDisposable *)disposable { - [self swapInDisposable:disposable]; + [self swapInDisposable:disposable]; } #pragma mark Lifecycle + (instancetype)serialDisposableWithDisposable:(RACDisposable *)disposable { - RACSerialDisposable *serialDisposable = [[self alloc] init]; - serialDisposable.disposable = disposable; - return serialDisposable; + RACSerialDisposable *serialDisposable = [[self alloc] init]; + serialDisposable.disposable = disposable; + return serialDisposable; } - (instancetype)init { - self = [super init]; - if (self == nil) return nil; + self = [super init]; + if (self == nil) return nil; - const int result __attribute__((unused)) = pthread_mutex_init(&_mutex, NULL); - NSCAssert(0 == result, @"Failed to initialize mutex with error %d", result); + const int result __attribute__((unused)) = pthread_mutex_init(&_mutex, NULL); + NSCAssert(0 == result, @"Failed to initialize mutex with error %d", result); - return self; + return self; } - (instancetype)initWithBlock:(void (^)(void))block { - self = [self init]; - if (self == nil) return nil; + self = [self init]; + if (self == nil) return nil; - self.disposable = [RACDisposable disposableWithBlock:block]; + self.disposable = [RACDisposable disposableWithBlock:block]; - return self; + return self; } - (void)dealloc { - const int result __attribute__((unused)) = pthread_mutex_destroy(&_mutex); - NSCAssert(0 == result, @"Failed to destroy mutex with error %d", result); + const int result __attribute__((unused)) = pthread_mutex_destroy(&_mutex); + NSCAssert(0 == result, @"Failed to destroy mutex with error %d", result); } #pragma mark Inner Disposable - (RACDisposable *)swapInDisposable:(RACDisposable *)newDisposable { - RACDisposable *existingDisposable; - BOOL alreadyDisposed; - - pthread_mutex_lock(&_mutex); - alreadyDisposed = _disposed; - if (!alreadyDisposed) { - existingDisposable = _disposable; - _disposable = newDisposable; - } - pthread_mutex_unlock(&_mutex); - - if (alreadyDisposed) { - [newDisposable dispose]; - return nil; - } - - return existingDisposable; + RACDisposable *existingDisposable; + BOOL alreadyDisposed; + + pthread_mutex_lock(&_mutex); + alreadyDisposed = _disposed; + if (!alreadyDisposed) { + existingDisposable = _disposable; + _disposable = newDisposable; + } + pthread_mutex_unlock(&_mutex); + + if (alreadyDisposed) { + [newDisposable dispose]; + return nil; + } + + return existingDisposable; } #pragma mark Disposal - (void)dispose { - RACDisposable *existingDisposable; - - pthread_mutex_lock(&_mutex); - if (!_disposed) { - existingDisposable = _disposable; - _disposed = YES; - _disposable = nil; - } - pthread_mutex_unlock(&_mutex); - - [existingDisposable dispose]; + RACDisposable *existingDisposable; + + pthread_mutex_lock(&_mutex); + if (!_disposed) { + existingDisposable = _disposable; + _disposed = YES; + _disposable = nil; + } + pthread_mutex_unlock(&_mutex); + + [existingDisposable dispose]; } @end diff --git a/ReactiveObjC/RACSignal+Operations.m b/ReactiveObjC/RACSignal+Operations.m index 23db23036..8df7a1b62 100644 --- a/ReactiveObjC/RACSignal+Operations.m +++ b/ReactiveObjC/RACSignal+Operations.m @@ -1197,20 +1197,20 @@ - (RACSignal *)any:(BOOL (^)(id object))predicateBlock { - (RACSignal *)all:(BOOL (^)(id object))predicateBlock { NSCParameterAssert(predicateBlock != NULL); - return [[RACSignal createSignal:^RACDisposable *(id subscriber) { - return [self subscribeNext:^(id x) { - if (!predicateBlock(x)) { - [subscriber sendNext:@NO]; - [subscriber sendCompleted]; - } - } error:^(NSError *error) { - [subscriber sendNext:@NO]; - [subscriber sendCompleted]; - } completed:^{ - [subscriber sendNext:@YES]; - [subscriber sendCompleted]; - }]; - }] setNameWithFormat:@"[%@] -all:", self.name]; + return [[RACSignal createSignal:^RACDisposable *(id subscriber) { + return [self subscribeNext:^(id x) { + if (!predicateBlock(x)) { + [subscriber sendNext:@NO]; + [subscriber sendCompleted]; + } + } error:^(NSError *error) { + [subscriber sendNext:@NO]; + [subscriber sendCompleted]; + } completed:^{ + [subscriber sendNext:@YES]; + [subscriber sendCompleted]; + }]; + }] setNameWithFormat:@"[%@] -all:", self.name]; } - (RACSignal *)retry:(NSInteger)retryCount { @@ -1329,20 +1329,20 @@ - (RACSignal *)materialize { - (RACSignal *)dematerialize { return [RACSignal createSignal:^RACDisposable *(id subscriber) { - return [self subscribeNext:^(RACEvent *event) { - switch (event.eventType) { - case RACEventTypeCompleted: - [subscriber sendCompleted]; - case RACEventTypeError: - [subscriber sendError:event.error]; - case RACEventTypeNext: - [subscriber sendNext:event.value]; - } - } error:^(NSError *error) { - [subscriber sendError:error]; - } completed:^{ - [subscriber sendCompleted]; - }]; + return [self subscribeNext:^(RACEvent *event) { + switch (event.eventType) { + case RACEventTypeCompleted: + [subscriber sendCompleted]; + case RACEventTypeError: + [subscriber sendError:event.error]; + case RACEventTypeNext: + [subscriber sendNext:event.value]; + } + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [subscriber sendCompleted]; + }]; }]; } diff --git a/ReactiveObjC/RACSignalSequence.m b/ReactiveObjC/RACSignalSequence.m index 14cea6b14..cd0df6efa 100644 --- a/ReactiveObjC/RACSignalSequence.m +++ b/ReactiveObjC/RACSignalSequence.m @@ -23,57 +23,57 @@ @implementation RACSignalSequence #pragma mark Lifecycle + (RACSequence *)sequenceWithSignal:(RACSignal *)signal { - RACSignalSequence *seq = [[self alloc] init]; - - RACReplaySubject *subject = [RACReplaySubject subject]; - [signal subscribeNext:^(id value) { - [subject sendNext:value]; - } error:^(NSError *error) { - [subject sendError:error]; - } completed:^{ - [subject sendCompleted]; - }]; - - seq->_subject = subject; - return seq; + RACSignalSequence *seq = [[self alloc] init]; + + RACReplaySubject *subject = [RACReplaySubject subject]; + [signal subscribeNext:^(id value) { + [subject sendNext:value]; + } error:^(NSError *error) { + [subject sendError:error]; + } completed:^{ + [subject sendCompleted]; + }]; + + seq->_subject = subject; + return seq; } #pragma mark RACSequence - (id)head { - id value = [self.subject firstOrDefault:self]; + id value = [self.subject firstOrDefault:self]; - if (value == self) { - return nil; - } else { - return value ?: NSNull.null; - } + if (value == self) { + return nil; + } else { + return value ?: NSNull.null; + } } - (RACSequence *)tail { - RACSequence *sequence = [self.class sequenceWithSignal:[self.subject skip:1]]; - sequence.name = self.name; - return sequence; + RACSequence *sequence = [self.class sequenceWithSignal:[self.subject skip:1]]; + sequence.name = self.name; + return sequence; } - (NSArray *)array { - return self.subject.toArray; + return self.subject.toArray; } #pragma mark NSObject - (NSString *)description { - // Synchronously accumulate the values that have been sent so far. - NSMutableArray *values = [NSMutableArray array]; - RACDisposable *disposable = [self.subject subscribeNext:^(id value) { - @synchronized (values) { - [values addObject:value ?: NSNull.null]; - } - }]; + // Synchronously accumulate the values that have been sent so far. + NSMutableArray *values = [NSMutableArray array]; + RACDisposable *disposable = [self.subject subscribeNext:^(id value) { + @synchronized (values) { + [values addObject:value ?: NSNull.null]; + } + }]; - [disposable dispose]; + [disposable dispose]; - return [NSString stringWithFormat:@"<%@: %p>{ name = %@, values = %@ … }", self.class, self, self.name, values]; + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, values = %@ … }", self.class, self, self.name, values]; } @end diff --git a/ReactiveObjC/RACStream.m b/ReactiveObjC/RACStream.m index 86164d93a..3e98ba962 100644 --- a/ReactiveObjC/RACStream.m +++ b/ReactiveObjC/RACStream.m @@ -16,54 +16,54 @@ @implementation RACStream #pragma mark Lifecycle - (instancetype)init { - self = [super init]; + self = [super init]; - self.name = @""; - return self; + self.name = @""; + return self; } #pragma mark Abstract methods + (__kindof RACStream *)empty { - NSString *reason = [NSString stringWithFormat:@"%@ must be overridden by subclasses", NSStringFromSelector(_cmd)]; - @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil]; + NSString *reason = [NSString stringWithFormat:@"%@ must be overridden by subclasses", NSStringFromSelector(_cmd)]; + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil]; } - (__kindof RACStream *)bind:(RACStreamBindBlock (^)(void))block { - NSString *reason = [NSString stringWithFormat:@"%@ must be overridden by subclasses", NSStringFromSelector(_cmd)]; - @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil]; + NSString *reason = [NSString stringWithFormat:@"%@ must be overridden by subclasses", NSStringFromSelector(_cmd)]; + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil]; } + (__kindof RACStream *)return:(id)value { - NSString *reason = [NSString stringWithFormat:@"%@ must be overridden by subclasses", NSStringFromSelector(_cmd)]; - @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil]; + NSString *reason = [NSString stringWithFormat:@"%@ must be overridden by subclasses", NSStringFromSelector(_cmd)]; + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil]; } - (__kindof RACStream *)concat:(RACStream *)stream { - NSString *reason = [NSString stringWithFormat:@"%@ must be overridden by subclasses", NSStringFromSelector(_cmd)]; - @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil]; + NSString *reason = [NSString stringWithFormat:@"%@ must be overridden by subclasses", NSStringFromSelector(_cmd)]; + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil]; } - (__kindof RACStream *)zipWith:(RACStream *)stream { - NSString *reason = [NSString stringWithFormat:@"%@ must be overridden by subclasses", NSStringFromSelector(_cmd)]; - @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil]; + NSString *reason = [NSString stringWithFormat:@"%@ must be overridden by subclasses", NSStringFromSelector(_cmd)]; + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil]; } #pragma mark Naming - (instancetype)setNameWithFormat:(NSString *)format, ... { - if (getenv("RAC_DEBUG_SIGNAL_NAMES") == NULL) return self; + if (getenv("RAC_DEBUG_SIGNAL_NAMES") == NULL) return self; - NSCParameterAssert(format != nil); + NSCParameterAssert(format != nil); - va_list args; - va_start(args, format); + va_list args; + va_start(args, format); - NSString *str = [[NSString alloc] initWithFormat:format arguments:args]; - va_end(args); + NSString *str = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); - self.name = str; - return self; + self.name = str; + return self; } @end @@ -71,285 +71,285 @@ - (instancetype)setNameWithFormat:(NSString *)format, ... { @implementation RACStream (Operations) - (__kindof RACStream *)flattenMap:(__kindof RACStream * (^)(id value))block { - Class class = self.class; + Class class = self.class; - return [[self bind:^{ - return ^(id value, BOOL *stop) { - id stream = block(value) ?: [class empty]; - NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream); + return [[self bind:^{ + return ^(id value, BOOL *stop) { + id stream = block(value) ?: [class empty]; + NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream); - return stream; - }; - }] setNameWithFormat:@"[%@] -flattenMap:", self.name]; + return stream; + }; + }] setNameWithFormat:@"[%@] -flattenMap:", self.name]; } - (__kindof RACStream *)flatten { - return [[self flattenMap:^(id value) { - return value; - }] setNameWithFormat:@"[%@] -flatten", self.name]; + return [[self flattenMap:^(id value) { + return value; + }] setNameWithFormat:@"[%@] -flatten", self.name]; } - (__kindof RACStream *)map:(id (^)(id value))block { - NSCParameterAssert(block != nil); + NSCParameterAssert(block != nil); - Class class = self.class; - - return [[self flattenMap:^(id value) { - return [class return:block(value)]; - }] setNameWithFormat:@"[%@] -map:", self.name]; + Class class = self.class; + + return [[self flattenMap:^(id value) { + return [class return:block(value)]; + }] setNameWithFormat:@"[%@] -map:", self.name]; } - (__kindof RACStream *)mapReplace:(id)object { - return [[self map:^(id _) { - return object; - }] setNameWithFormat:@"[%@] -mapReplace: %@", self.name, RACDescription(object)]; + return [[self map:^(id _) { + return object; + }] setNameWithFormat:@"[%@] -mapReplace: %@", self.name, RACDescription(object)]; } - (__kindof RACStream *)combinePreviousWithStart:(id)start reduce:(id (^)(id previous, id next))reduceBlock { - NSCParameterAssert(reduceBlock != NULL); - return [[[self - scanWithStart:RACTuplePack(start) - reduce:^(RACTuple *previousTuple, id next) { - id value = reduceBlock(previousTuple[0], next); - return RACTuplePack(next, value); - }] - map:^(RACTuple *tuple) { - return tuple[1]; - }] - setNameWithFormat:@"[%@] -combinePreviousWithStart: %@ reduce:", self.name, RACDescription(start)]; + NSCParameterAssert(reduceBlock != NULL); + return [[[self + scanWithStart:RACTuplePack(start) + reduce:^(RACTuple *previousTuple, id next) { + id value = reduceBlock(previousTuple[0], next); + return RACTuplePack(next, value); + }] + map:^(RACTuple *tuple) { + return tuple[1]; + }] + setNameWithFormat:@"[%@] -combinePreviousWithStart: %@ reduce:", self.name, RACDescription(start)]; } - (__kindof RACStream *)filter:(BOOL (^)(id value))block { - NSCParameterAssert(block != nil); - - Class class = self.class; - - return [[self flattenMap:^ id (id value) { - if (block(value)) { - return [class return:value]; - } else { - return class.empty; - } - }] setNameWithFormat:@"[%@] -filter:", self.name]; + NSCParameterAssert(block != nil); + + Class class = self.class; + + return [[self flattenMap:^ id (id value) { + if (block(value)) { + return [class return:value]; + } else { + return class.empty; + } + }] setNameWithFormat:@"[%@] -filter:", self.name]; } - (__kindof RACStream *)ignore:(id)value { - return [[self filter:^ BOOL (id innerValue) { - return innerValue != value && ![innerValue isEqual:value]; - }] setNameWithFormat:@"[%@] -ignore: %@", self.name, RACDescription(value)]; + return [[self filter:^ BOOL (id innerValue) { + return innerValue != value && ![innerValue isEqual:value]; + }] setNameWithFormat:@"[%@] -ignore: %@", self.name, RACDescription(value)]; } - (__kindof RACStream *)reduceEach:(RACReduceBlock)reduceBlock { - NSCParameterAssert(reduceBlock != nil); + NSCParameterAssert(reduceBlock != nil); - __weak RACStream *stream __attribute__((unused)) = self; - return [[self map:^(RACTuple *t) { - NSCAssert([t isKindOfClass:RACTuple.class], @"Value from stream %@ is not a tuple: %@", stream, t); - return RACInvokeBlock(reduceBlock, t); - }] setNameWithFormat:@"[%@] -reduceEach:", self.name]; + __weak RACStream *stream __attribute__((unused)) = self; + return [[self map:^(RACTuple *t) { + NSCAssert([t isKindOfClass:RACTuple.class], @"Value from stream %@ is not a tuple: %@", stream, t); + return RACInvokeBlock(reduceBlock, t); + }] setNameWithFormat:@"[%@] -reduceEach:", self.name]; } - (__kindof RACStream *)startWith:(id)value { - return [[[self.class return:value] - concat:self] - setNameWithFormat:@"[%@] -startWith: %@", self.name, RACDescription(value)]; + return [[[self.class return:value] + concat:self] + setNameWithFormat:@"[%@] -startWith: %@", self.name, RACDescription(value)]; } - (__kindof RACStream *)skip:(NSUInteger)skipCount { - Class class = self.class; - - return [[self bind:^{ - __block NSUInteger skipped = 0; - - return ^(id value, BOOL *stop) { - if (skipped >= skipCount) return [class return:value]; - - skipped++; - return class.empty; - }; - }] setNameWithFormat:@"[%@] -skip: %lu", self.name, (unsigned long)skipCount]; + Class class = self.class; + + return [[self bind:^{ + __block NSUInteger skipped = 0; + + return ^(id value, BOOL *stop) { + if (skipped >= skipCount) return [class return:value]; + + skipped++; + return class.empty; + }; + }] setNameWithFormat:@"[%@] -skip: %lu", self.name, (unsigned long)skipCount]; } - (__kindof RACStream *)take:(NSUInteger)count { - Class class = self.class; - - if (count == 0) return class.empty; - - return [[self bind:^{ - __block NSUInteger taken = 0; - - return ^ id (id value, BOOL *stop) { - if (taken < count) { - ++taken; - if (taken == count) *stop = YES; - return [class return:value]; - } else { - return nil; - } - }; - }] setNameWithFormat:@"[%@] -take: %lu", self.name, (unsigned long)count]; + Class class = self.class; + + if (count == 0) return class.empty; + + return [[self bind:^{ + __block NSUInteger taken = 0; + + return ^ id (id value, BOOL *stop) { + if (taken < count) { + ++taken; + if (taken == count) *stop = YES; + return [class return:value]; + } else { + return nil; + } + }; + }] setNameWithFormat:@"[%@] -take: %lu", self.name, (unsigned long)count]; } + (__kindof RACStream *)join:(id)streams block:(RACStream * (^)(id, id))block { - RACStream *current = nil; - - // Creates streams of successively larger tuples by combining the input - // streams one-by-one. - for (RACStream *stream in streams) { - // For the first stream, just wrap its values in a RACTuple. That way, - // if only one stream is given, the result is still a stream of tuples. - if (current == nil) { - current = [stream map:^(id x) { - return RACTuplePack(x); - }]; - - continue; - } - - current = block(current, stream); - } - - if (current == nil) return [self empty]; - - return [current map:^(RACTuple *xs) { - // Right now, each value is contained in its own tuple, sorta like: - // - // (((1), 2), 3) - // - // We need to unwrap all the layers and create a tuple out of the result. - NSMutableArray *values = [[NSMutableArray alloc] init]; - - while (xs != nil) { - [values insertObject:xs.last ?: RACTupleNil.tupleNil atIndex:0]; - xs = (xs.count > 1 ? xs.first : nil); - } - - return [RACTuple tupleWithObjectsFromArray:values]; - }]; + RACStream *current = nil; + + // Creates streams of successively larger tuples by combining the input + // streams one-by-one. + for (RACStream *stream in streams) { + // For the first stream, just wrap its values in a RACTuple. That way, + // if only one stream is given, the result is still a stream of tuples. + if (current == nil) { + current = [stream map:^(id x) { + return RACTuplePack(x); + }]; + + continue; + } + + current = block(current, stream); + } + + if (current == nil) return [self empty]; + + return [current map:^(RACTuple *xs) { + // Right now, each value is contained in its own tuple, sorta like: + // + // (((1), 2), 3) + // + // We need to unwrap all the layers and create a tuple out of the result. + NSMutableArray *values = [[NSMutableArray alloc] init]; + + while (xs != nil) { + [values insertObject:xs.last ?: RACTupleNil.tupleNil atIndex:0]; + xs = (xs.count > 1 ? xs.first : nil); + } + + return [RACTuple tupleWithObjectsFromArray:values]; + }]; } + (__kindof RACStream *)zip:(id)streams { - return [[self join:streams block:^(RACStream *left, RACStream *right) { - return [left zipWith:right]; - }] setNameWithFormat:@"+zip: %@", streams]; + return [[self join:streams block:^(RACStream *left, RACStream *right) { + return [left zipWith:right]; + }] setNameWithFormat:@"+zip: %@", streams]; } + (__kindof RACStream *)zip:(id)streams reduce:(RACReduceBlock)reduceBlock { - NSCParameterAssert(reduceBlock != nil); + NSCParameterAssert(reduceBlock != nil); - RACStream *result = [self zip:streams]; + RACStream *result = [self zip:streams]; - // Although we assert this condition above, older versions of this method - // supported this argument being nil. Avoid crashing Release builds of - // apps that depended on that. - if (reduceBlock != nil) result = [result reduceEach:reduceBlock]; + // Although we assert this condition above, older versions of this method + // supported this argument being nil. Avoid crashing Release builds of + // apps that depended on that. + if (reduceBlock != nil) result = [result reduceEach:reduceBlock]; - return [result setNameWithFormat:@"+zip: %@ reduce:", streams]; + return [result setNameWithFormat:@"+zip: %@ reduce:", streams]; } + (__kindof RACStream *)concat:(id)streams { - RACStream *result = self.empty; - for (RACStream *stream in streams) { - result = [result concat:stream]; - } + RACStream *result = self.empty; + for (RACStream *stream in streams) { + result = [result concat:stream]; + } - return [result setNameWithFormat:@"+concat: %@", streams]; + return [result setNameWithFormat:@"+concat: %@", streams]; } - (__kindof RACStream *)scanWithStart:(id)startingValue reduce:(id (^)(id running, id next))reduceBlock { - NSCParameterAssert(reduceBlock != nil); - - return [[self - scanWithStart:startingValue - reduceWithIndex:^(id running, id next, NSUInteger index) { - return reduceBlock(running, next); - }] - setNameWithFormat:@"[%@] -scanWithStart: %@ reduce:", self.name, RACDescription(startingValue)]; + NSCParameterAssert(reduceBlock != nil); + + return [[self + scanWithStart:startingValue + reduceWithIndex:^(id running, id next, NSUInteger index) { + return reduceBlock(running, next); + }] + setNameWithFormat:@"[%@] -scanWithStart: %@ reduce:", self.name, RACDescription(startingValue)]; } - (__kindof RACStream *)scanWithStart:(id)startingValue reduceWithIndex:(id (^)(id, id, NSUInteger))reduceBlock { - NSCParameterAssert(reduceBlock != nil); + NSCParameterAssert(reduceBlock != nil); - Class class = self.class; + Class class = self.class; - return [[self bind:^{ - __block id running = startingValue; - __block NSUInteger index = 0; + return [[self bind:^{ + __block id running = startingValue; + __block NSUInteger index = 0; - return ^(id value, BOOL *stop) { - running = reduceBlock(running, value, index++); - return [class return:running]; - }; - }] setNameWithFormat:@"[%@] -scanWithStart: %@ reduceWithIndex:", self.name, RACDescription(startingValue)]; + return ^(id value, BOOL *stop) { + running = reduceBlock(running, value, index++); + return [class return:running]; + }; + }] setNameWithFormat:@"[%@] -scanWithStart: %@ reduceWithIndex:", self.name, RACDescription(startingValue)]; } - (__kindof RACStream *)takeUntilBlock:(BOOL (^)(id x))predicate { - NSCParameterAssert(predicate != nil); + NSCParameterAssert(predicate != nil); - Class class = self.class; - - return [[self bind:^{ - return ^ id (id value, BOOL *stop) { - if (predicate(value)) return nil; + Class class = self.class; + + return [[self bind:^{ + return ^ id (id value, BOOL *stop) { + if (predicate(value)) return nil; - return [class return:value]; - }; - }] setNameWithFormat:@"[%@] -takeUntilBlock:", self.name]; + return [class return:value]; + }; + }] setNameWithFormat:@"[%@] -takeUntilBlock:", self.name]; } - (__kindof RACStream *)takeWhileBlock:(BOOL (^)(id x))predicate { - NSCParameterAssert(predicate != nil); + NSCParameterAssert(predicate != nil); - return [[self takeUntilBlock:^ BOOL (id x) { - return !predicate(x); - }] setNameWithFormat:@"[%@] -takeWhileBlock:", self.name]; + return [[self takeUntilBlock:^ BOOL (id x) { + return !predicate(x); + }] setNameWithFormat:@"[%@] -takeWhileBlock:", self.name]; } - (__kindof RACStream *)skipUntilBlock:(BOOL (^)(id x))predicate { - NSCParameterAssert(predicate != nil); - - Class class = self.class; - - return [[self bind:^{ - __block BOOL skipping = YES; - - return ^ id (id value, BOOL *stop) { - if (skipping) { - if (predicate(value)) { - skipping = NO; - } else { - return class.empty; - } - } - - return [class return:value]; - }; - }] setNameWithFormat:@"[%@] -skipUntilBlock:", self.name]; + NSCParameterAssert(predicate != nil); + + Class class = self.class; + + return [[self bind:^{ + __block BOOL skipping = YES; + + return ^ id (id value, BOOL *stop) { + if (skipping) { + if (predicate(value)) { + skipping = NO; + } else { + return class.empty; + } + } + + return [class return:value]; + }; + }] setNameWithFormat:@"[%@] -skipUntilBlock:", self.name]; } - (__kindof RACStream *)skipWhileBlock:(BOOL (^)(id x))predicate { - NSCParameterAssert(predicate != nil); + NSCParameterAssert(predicate != nil); - return [[self skipUntilBlock:^ BOOL (id x) { - return !predicate(x); - }] setNameWithFormat:@"[%@] -skipWhileBlock:", self.name]; + return [[self skipUntilBlock:^ BOOL (id x) { + return !predicate(x); + }] setNameWithFormat:@"[%@] -skipWhileBlock:", self.name]; } - (__kindof RACStream *)distinctUntilChanged { - Class class = self.class; + Class class = self.class; - return [[self bind:^{ - __block id lastValue = nil; - __block BOOL initial = YES; + return [[self bind:^{ + __block id lastValue = nil; + __block BOOL initial = YES; - return ^(id x, BOOL *stop) { - if (!initial && (lastValue == x || [x isEqual:lastValue])) return [class empty]; + return ^(id x, BOOL *stop) { + if (!initial && (lastValue == x || [x isEqual:lastValue])) return [class empty]; - initial = NO; - lastValue = x; - return [class return:x]; - }; - }] setNameWithFormat:@"[%@] -distinctUntilChanged", self.name]; + initial = NO; + lastValue = x; + return [class return:x]; + }; + }] setNameWithFormat:@"[%@] -distinctUntilChanged", self.name]; } @end diff --git a/ReactiveObjC/RACStringSequence.m b/ReactiveObjC/RACStringSequence.m index 809db5d30..37d2c422b 100644 --- a/ReactiveObjC/RACStringSequence.m +++ b/ReactiveObjC/RACStringSequence.m @@ -23,43 +23,43 @@ @implementation RACStringSequence #pragma mark Lifecycle + (RACSequence *)sequenceWithString:(NSString *)string offset:(NSUInteger)offset { - NSCParameterAssert(offset <= string.length); + NSCParameterAssert(offset <= string.length); - if (offset == string.length) return self.empty; + if (offset == string.length) return self.empty; - RACStringSequence *seq = [[self alloc] init]; - seq->_string = [string copy]; - seq->_offset = offset; - return seq; + RACStringSequence *seq = [[self alloc] init]; + seq->_string = [string copy]; + seq->_offset = offset; + return seq; } #pragma mark RACSequence - (id)head { - return [self.string substringWithRange:NSMakeRange(self.offset, 1)]; + return [self.string substringWithRange:NSMakeRange(self.offset, 1)]; } - (RACSequence *)tail { - RACSequence *sequence = [self.class sequenceWithString:self.string offset:self.offset + 1]; - sequence.name = self.name; - return sequence; + RACSequence *sequence = [self.class sequenceWithString:self.string offset:self.offset + 1]; + sequence.name = self.name; + return sequence; } - (NSArray *)array { - NSUInteger substringLength = self.string.length - self.offset; - NSMutableArray *array = [NSMutableArray arrayWithCapacity:substringLength]; + NSUInteger substringLength = self.string.length - self.offset; + NSMutableArray *array = [NSMutableArray arrayWithCapacity:substringLength]; - [self.string enumerateSubstringsInRange:NSMakeRange(self.offset, substringLength) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { - [array addObject:substring]; - }]; + [self.string enumerateSubstringsInRange:NSMakeRange(self.offset, substringLength) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { + [array addObject:substring]; + }]; - return [array copy]; + return [array copy]; } #pragma mark NSObject - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p>{ name = %@, string = %@ }", self.class, self, self.name, [self.string substringFromIndex:self.offset]]; + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, string = %@ }", self.class, self, self.name, [self.string substringFromIndex:self.offset]]; } @end diff --git a/ReactiveObjC/RACSubject.m b/ReactiveObjC/RACSubject.m index b9280fb9d..6201364af 100644 --- a/ReactiveObjC/RACSubject.m +++ b/ReactiveObjC/RACSubject.m @@ -32,95 +32,95 @@ @implementation RACSubject #pragma mark Lifecycle + (instancetype)subject { - return [[self alloc] init]; + return [[self alloc] init]; } - (instancetype)init { - self = [super init]; - if (self == nil) return nil; + self = [super init]; + if (self == nil) return nil; - _disposable = [RACCompoundDisposable compoundDisposable]; - _subscribers = [[NSMutableArray alloc] initWithCapacity:1]; - - return self; + _disposable = [RACCompoundDisposable compoundDisposable]; + _subscribers = [[NSMutableArray alloc] initWithCapacity:1]; + + return self; } - (void)dealloc { - [self.disposable dispose]; + [self.disposable dispose]; } #pragma mark Subscription - (RACDisposable *)subscribe:(id)subscriber { - NSCParameterAssert(subscriber != nil); - - RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; - subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; - - NSMutableArray *subscribers = self.subscribers; - @synchronized (subscribers) { - [subscribers addObject:subscriber]; - } - - [disposable addDisposable:[RACDisposable disposableWithBlock:^{ - @synchronized (subscribers) { - // Since newer subscribers are generally shorter-lived, search - // starting from the end of the list. - NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id obj, NSUInteger index, BOOL *stop) { - return obj == subscriber; - }]; - - if (index != NSNotFound) [subscribers removeObjectAtIndex:index]; - } - }]]; - - return disposable; + NSCParameterAssert(subscriber != nil); + + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; + + NSMutableArray *subscribers = self.subscribers; + @synchronized (subscribers) { + [subscribers addObject:subscriber]; + } + + [disposable addDisposable:[RACDisposable disposableWithBlock:^{ + @synchronized (subscribers) { + // Since newer subscribers are generally shorter-lived, search + // starting from the end of the list. + NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id obj, NSUInteger index, BOOL *stop) { + return obj == subscriber; + }]; + + if (index != NSNotFound) [subscribers removeObjectAtIndex:index]; + } + }]]; + + return disposable; } - (void)enumerateSubscribersUsingBlock:(void (^)(id subscriber))block { - NSArray *subscribers; - @synchronized (self.subscribers) { - subscribers = [self.subscribers copy]; - } - - for (id subscriber in subscribers) { - block(subscriber); - } + NSArray *subscribers; + @synchronized (self.subscribers) { + subscribers = [self.subscribers copy]; + } + + for (id subscriber in subscribers) { + block(subscriber); + } } #pragma mark RACSubscriber - (void)sendNext:(nullable id)value { - [self enumerateSubscribersUsingBlock:^(id subscriber) { - [subscriber sendNext:value]; - }]; + [self enumerateSubscribersUsingBlock:^(id subscriber) { + [subscriber sendNext:value]; + }]; } - (void)sendError:(NSError *)error { - [self.disposable dispose]; - - [self enumerateSubscribersUsingBlock:^(id subscriber) { - [subscriber sendError:error]; - }]; + [self.disposable dispose]; + + [self enumerateSubscribersUsingBlock:^(id subscriber) { + [subscriber sendError:error]; + }]; } - (void)sendCompleted { - [self.disposable dispose]; - - [self enumerateSubscribersUsingBlock:^(id subscriber) { - [subscriber sendCompleted]; - }]; + [self.disposable dispose]; + + [self enumerateSubscribersUsingBlock:^(id subscriber) { + [subscriber sendCompleted]; + }]; } - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)d { - if (d.disposed) return; - [self.disposable addDisposable:d]; - - @rac_weakify(self, d); - [d addDisposable:[RACDisposable disposableWithBlock:^{ - @rac_strongify(self, d); - [self.disposable removeDisposable:d]; - }]]; + if (d.disposed) return; + [self.disposable addDisposable:d]; + + @rac_weakify(self, d); + [d addDisposable:[RACDisposable disposableWithBlock:^{ + @rac_strongify(self, d); + [self.disposable removeDisposable:d]; + }]]; } @end diff --git a/ReactiveObjC/RACSubscriber.m b/ReactiveObjC/RACSubscriber.m index 2a7899724..2d81e9f77 100644 --- a/ReactiveObjC/RACSubscriber.m +++ b/ReactiveObjC/RACSubscriber.m @@ -27,85 +27,85 @@ @implementation RACSubscriber #pragma mark Lifecycle + (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed { - RACSubscriber *subscriber = [[self alloc] init]; + RACSubscriber *subscriber = [[self alloc] init]; - subscriber->_next = [next copy]; - subscriber->_error = [error copy]; - subscriber->_completed = [completed copy]; + subscriber->_next = [next copy]; + subscriber->_error = [error copy]; + subscriber->_completed = [completed copy]; - return subscriber; + return subscriber; } - (instancetype)init { - self = [super init]; + self = [super init]; - @rac_unsafeify(self); + @rac_unsafeify(self); - RACDisposable *selfDisposable = [RACDisposable disposableWithBlock:^{ - @rac_strongify(self); + RACDisposable *selfDisposable = [RACDisposable disposableWithBlock:^{ + @rac_strongify(self); - @synchronized (self) { - self.next = nil; - self.error = nil; - self.completed = nil; - } - }]; + @synchronized (self) { + self.next = nil; + self.error = nil; + self.completed = nil; + } + }]; - _disposable = [RACCompoundDisposable compoundDisposable]; - [_disposable addDisposable:selfDisposable]; + _disposable = [RACCompoundDisposable compoundDisposable]; + [_disposable addDisposable:selfDisposable]; - return self; + return self; } - (void)dealloc { - [self.disposable dispose]; + [self.disposable dispose]; } #pragma mark RACSubscriber - (void)sendNext:(id)value { - @synchronized (self) { - void (^nextBlock)(id) = [self.next copy]; - if (nextBlock == nil) return; + @synchronized (self) { + void (^nextBlock)(id) = [self.next copy]; + if (nextBlock == nil) return; - nextBlock(value); - } + nextBlock(value); + } } - (void)sendError:(NSError *)e { - @synchronized (self) { - void (^errorBlock)(NSError *) = [self.error copy]; - [self.disposable dispose]; + @synchronized (self) { + void (^errorBlock)(NSError *) = [self.error copy]; + [self.disposable dispose]; - if (errorBlock == nil) return; - errorBlock(e); - } + if (errorBlock == nil) return; + errorBlock(e); + } } - (void)sendCompleted { - @synchronized (self) { - void (^completedBlock)(void) = [self.completed copy]; - [self.disposable dispose]; + @synchronized (self) { + void (^completedBlock)(void) = [self.completed copy]; + [self.disposable dispose]; - if (completedBlock == nil) return; - completedBlock(); - } + if (completedBlock == nil) return; + completedBlock(); + } } - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)otherDisposable { - if (otherDisposable.disposed) return; + if (otherDisposable.disposed) return; - RACCompoundDisposable *selfDisposable = self.disposable; - [selfDisposable addDisposable:otherDisposable]; + RACCompoundDisposable *selfDisposable = self.disposable; + [selfDisposable addDisposable:otherDisposable]; - @rac_unsafeify(otherDisposable); + @rac_unsafeify(otherDisposable); - // If this subscription terminates, purge its disposable to avoid unbounded - // memory growth. - [otherDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - @rac_strongify(otherDisposable); - [selfDisposable removeDisposable:otherDisposable]; - }]]; + // If this subscription terminates, purge its disposable to avoid unbounded + // memory growth. + [otherDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + @rac_strongify(otherDisposable); + [selfDisposable removeDisposable:otherDisposable]; + }]]; } @end diff --git a/ReactiveObjC/RACSubscriptingAssignmentTrampoline.m b/ReactiveObjC/RACSubscriptingAssignmentTrampoline.m index 085ab0f9d..fe03bdc44 100644 --- a/ReactiveObjC/RACSubscriptingAssignmentTrampoline.m +++ b/ReactiveObjC/RACSubscriptingAssignmentTrampoline.m @@ -22,21 +22,21 @@ @interface RACSubscriptingAssignmentTrampoline () @implementation RACSubscriptingAssignmentTrampoline - (instancetype)initWithTarget:(id)target nilValue:(id)nilValue { - // This is often a programmer error, but this prevents crashes if the target - // object has unexpectedly deallocated. - if (target == nil) return nil; + // This is often a programmer error, but this prevents crashes if the target + // object has unexpectedly deallocated. + if (target == nil) return nil; - self = [super init]; - if (self == nil) return nil; + self = [super init]; + if (self == nil) return nil; - _target = target; - _nilValue = nilValue; + _target = target; + _nilValue = nilValue; - return self; + return self; } - (void)setObject:(RACSignal *)signal forKeyedSubscript:(NSString *)keyPath { - [signal setKeyPath:keyPath onObject:self.target nilValue:self.nilValue]; + [signal setKeyPath:keyPath onObject:self.target nilValue:self.nilValue]; } @end diff --git a/ReactiveObjC/RACSubscriptionScheduler.m b/ReactiveObjC/RACSubscriptionScheduler.m index d503dee7f..8c7b839d3 100644 --- a/ReactiveObjC/RACSubscriptionScheduler.m +++ b/ReactiveObjC/RACSubscriptionScheduler.m @@ -22,32 +22,32 @@ @implementation RACSubscriptionScheduler #pragma mark Lifecycle - (instancetype)init { - self = [super initWithName:@"org.reactivecocoa.ReactiveObjC.RACScheduler.subscriptionScheduler"]; + self = [super initWithName:@"org.reactivecocoa.ReactiveObjC.RACScheduler.subscriptionScheduler"]; - _backgroundScheduler = [RACScheduler scheduler]; + _backgroundScheduler = [RACScheduler scheduler]; - return self; + return self; } #pragma mark RACScheduler - (RACDisposable *)schedule:(void (^)(void))block { - NSCParameterAssert(block != NULL); + NSCParameterAssert(block != NULL); - if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block]; + if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block]; - block(); - return nil; + block(); + return nil; } - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { - RACScheduler *scheduler = RACScheduler.currentScheduler ?: self.backgroundScheduler; - return [scheduler after:date schedule:block]; + RACScheduler *scheduler = RACScheduler.currentScheduler ?: self.backgroundScheduler; + return [scheduler after:date schedule:block]; } - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { - RACScheduler *scheduler = RACScheduler.currentScheduler ?: self.backgroundScheduler; - return [scheduler after:date repeatingEvery:interval withLeeway:leeway schedule:block]; + RACScheduler *scheduler = RACScheduler.currentScheduler ?: self.backgroundScheduler; + return [scheduler after:date repeatingEvery:interval withLeeway:leeway schedule:block]; } @end diff --git a/ReactiveObjC/RACTargetQueueScheduler.m b/ReactiveObjC/RACTargetQueueScheduler.m index 6e1dcaf7d..e832267b0 100644 --- a/ReactiveObjC/RACTargetQueueScheduler.m +++ b/ReactiveObjC/RACTargetQueueScheduler.m @@ -14,18 +14,18 @@ @implementation RACTargetQueueScheduler #pragma mark Lifecycle - (instancetype)initWithName:(NSString *)name targetQueue:(dispatch_queue_t)targetQueue { - NSCParameterAssert(targetQueue != NULL); + NSCParameterAssert(targetQueue != NULL); - if (name == nil) { - name = [NSString stringWithFormat:@"org.reactivecocoa.ReactiveObjC.RACTargetQueueScheduler(%s)", dispatch_queue_get_label(targetQueue)]; - } + if (name == nil) { + name = [NSString stringWithFormat:@"org.reactivecocoa.ReactiveObjC.RACTargetQueueScheduler(%s)", dispatch_queue_get_label(targetQueue)]; + } - dispatch_queue_t queue = dispatch_queue_create(name.UTF8String, DISPATCH_QUEUE_SERIAL); - if (queue == NULL) return nil; + dispatch_queue_t queue = dispatch_queue_create(name.UTF8String, DISPATCH_QUEUE_SERIAL); + if (queue == NULL) return nil; - dispatch_set_target_queue(queue, targetQueue); + dispatch_set_target_queue(queue, targetQueue); - return [super initWithName:name queue:queue]; + return [super initWithName:name queue:queue]; } @end diff --git a/ReactiveObjC/RACTestScheduler.m b/ReactiveObjC/RACTestScheduler.m index 49b0abfab..e32cd4991 100644 --- a/ReactiveObjC/RACTestScheduler.m +++ b/ReactiveObjC/RACTestScheduler.m @@ -34,17 +34,17 @@ - (instancetype)initWithDate:(NSDate *)date block:(void (^)(void))block; @end static CFComparisonResult RACCompareScheduledActions(const void *ptr1, const void *ptr2, void *info) { - RACTestSchedulerAction *action1 = (__bridge id)ptr1; - RACTestSchedulerAction *action2 = (__bridge id)ptr2; - return CFDateCompare((__bridge CFDateRef)action1.date, (__bridge CFDateRef)action2.date, NULL); + RACTestSchedulerAction *action1 = (__bridge id)ptr1; + RACTestSchedulerAction *action2 = (__bridge id)ptr2; + return CFDateCompare((__bridge CFDateRef)action1.date, (__bridge CFDateRef)action2.date, NULL); } static const void *RACRetainScheduledAction(CFAllocatorRef allocator, const void *ptr) { - return CFRetain(ptr); + return CFRetain(ptr); } static void RACReleaseScheduledAction(CFAllocatorRef allocator, const void *ptr) { - CFRelease(ptr); + CFRelease(ptr); } @interface RACTestScheduler () @@ -73,124 +73,124 @@ @implementation RACTestScheduler #pragma mark Lifecycle - (instancetype)init { - self = [super initWithName:@"org.reactivecocoa.ReactiveObjC.RACTestScheduler"]; - - CFBinaryHeapCallBacks callbacks = (CFBinaryHeapCallBacks){ - .version = 0, - .retain = &RACRetainScheduledAction, - .release = &RACReleaseScheduledAction, - .copyDescription = &CFCopyDescription, - .compare = &RACCompareScheduledActions - }; - - _scheduledActions = CFBinaryHeapCreate(NULL, 0, &callbacks, NULL); - return self; + self = [super initWithName:@"org.reactivecocoa.ReactiveObjC.RACTestScheduler"]; + + CFBinaryHeapCallBacks callbacks = (CFBinaryHeapCallBacks){ + .version = 0, + .retain = &RACRetainScheduledAction, + .release = &RACReleaseScheduledAction, + .copyDescription = &CFCopyDescription, + .compare = &RACCompareScheduledActions + }; + + _scheduledActions = CFBinaryHeapCreate(NULL, 0, &callbacks, NULL); + return self; } - (void)dealloc { - [self stepAll]; + [self stepAll]; - if (_scheduledActions != NULL) { - CFBridgingRelease(_scheduledActions); - _scheduledActions = NULL; - } + if (_scheduledActions != NULL) { + CFBridgingRelease(_scheduledActions); + _scheduledActions = NULL; + } } #pragma mark Execution - (void)step { - [self step:1]; + [self step:1]; } - (void)step:(NSUInteger)ticks { - @synchronized (self) { - for (NSUInteger i = 0; i < ticks; i++) { - const void *actionPtr = NULL; - if (!CFBinaryHeapGetMinimumIfPresent(self.scheduledActions, &actionPtr)) break; + @synchronized (self) { + for (NSUInteger i = 0; i < ticks; i++) { + const void *actionPtr = NULL; + if (!CFBinaryHeapGetMinimumIfPresent(self.scheduledActions, &actionPtr)) break; - RACTestSchedulerAction *action = (__bridge id)actionPtr; - CFBinaryHeapRemoveMinimumValue(self.scheduledActions); + RACTestSchedulerAction *action = (__bridge id)actionPtr; + CFBinaryHeapRemoveMinimumValue(self.scheduledActions); - if (action.disposable.disposed) continue; + if (action.disposable.disposed) continue; - RACScheduler *previousScheduler = RACScheduler.currentScheduler; - NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self; + RACScheduler *previousScheduler = RACScheduler.currentScheduler; + NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self; - action.block(); + action.block(); - if (previousScheduler != nil) { - NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler; - } else { - [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey]; - } - } - } + if (previousScheduler != nil) { + NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler; + } else { + [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey]; + } + } + } } - (void)stepAll { - [self step:NSUIntegerMax]; + [self step:NSUIntegerMax]; } #pragma mark RACScheduler - (RACDisposable *)schedule:(void (^)(void))block { - NSCParameterAssert(block != nil); + NSCParameterAssert(block != nil); - @synchronized (self) { - NSDate *uniqueDate = [NSDate dateWithTimeIntervalSinceReferenceDate:self.numberOfDirectlyScheduledBlocks]; - self.numberOfDirectlyScheduledBlocks++; + @synchronized (self) { + NSDate *uniqueDate = [NSDate dateWithTimeIntervalSinceReferenceDate:self.numberOfDirectlyScheduledBlocks]; + self.numberOfDirectlyScheduledBlocks++; - RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:uniqueDate block:block]; - CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); + RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:uniqueDate block:block]; + CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); - return action.disposable; - } + return action.disposable; + } } - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { - NSCParameterAssert(date != nil); - NSCParameterAssert(block != nil); + NSCParameterAssert(date != nil); + NSCParameterAssert(block != nil); - @synchronized (self) { - RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:block]; - CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); + @synchronized (self) { + RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:block]; + CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); - return action.disposable; - } + return action.disposable; + } } - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { - NSCParameterAssert(date != nil); - NSCParameterAssert(block != nil); - NSCParameterAssert(interval >= 0); - NSCParameterAssert(leeway >= 0); + NSCParameterAssert(date != nil); + NSCParameterAssert(block != nil); + NSCParameterAssert(interval >= 0); + NSCParameterAssert(leeway >= 0); - RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; + RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; - @rac_weakify(self); - @synchronized (self) { - __block RACDisposable *thisDisposable = nil; + @rac_weakify(self); + @synchronized (self) { + __block RACDisposable *thisDisposable = nil; - void (^reschedulingBlock)(void) = ^{ - @rac_strongify(self); + void (^reschedulingBlock)(void) = ^{ + @rac_strongify(self); - [compoundDisposable removeDisposable:thisDisposable]; + [compoundDisposable removeDisposable:thisDisposable]; - // Schedule the next interval. - RACDisposable *schedulingDisposable = [self after:[date dateByAddingTimeInterval:interval] repeatingEvery:interval withLeeway:leeway schedule:block]; - [compoundDisposable addDisposable:schedulingDisposable]; + // Schedule the next interval. + RACDisposable *schedulingDisposable = [self after:[date dateByAddingTimeInterval:interval] repeatingEvery:interval withLeeway:leeway schedule:block]; + [compoundDisposable addDisposable:schedulingDisposable]; - block(); - }; + block(); + }; - RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:reschedulingBlock]; - CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); + RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:reschedulingBlock]; + CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); - thisDisposable = action.disposable; - [compoundDisposable addDisposable:thisDisposable]; - } + thisDisposable = action.disposable; + [compoundDisposable addDisposable:thisDisposable]; + } - return compoundDisposable; + return compoundDisposable; } @end @@ -200,22 +200,22 @@ @implementation RACTestSchedulerAction #pragma mark Lifecycle - (instancetype)initWithDate:(NSDate *)date block:(void (^)(void))block { - NSCParameterAssert(date != nil); - NSCParameterAssert(block != nil); + NSCParameterAssert(date != nil); + NSCParameterAssert(block != nil); - self = [super init]; + self = [super init]; - _date = [date copy]; - _block = [block copy]; - _disposable = [[RACDisposable alloc] init]; + _date = [date copy]; + _block = [block copy]; + _disposable = [[RACDisposable alloc] init]; - return self; + return self; } #pragma mark NSObject - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p>{ date: %@ }", self.class, self, self.date]; + return [NSString stringWithFormat:@"<%@: %p>{ date: %@ }", self.class, self, self.date]; } @end diff --git a/ReactiveObjC/RACTuple.m b/ReactiveObjC/RACTuple.m index 1b0f7a87c..9862b48ef 100644 --- a/ReactiveObjC/RACTuple.m +++ b/ReactiveObjC/RACTuple.m @@ -13,26 +13,26 @@ @implementation RACTupleNil + (RACTupleNil *)tupleNil { - static dispatch_once_t onceToken; - static RACTupleNil *tupleNil = nil; - dispatch_once(&onceToken, ^{ - tupleNil = [[self alloc] init]; - }); - - return tupleNil; + static dispatch_once_t onceToken; + static RACTupleNil *tupleNil = nil; + dispatch_once(&onceToken, ^{ + tupleNil = [[self alloc] init]; + }); + + return tupleNil; } #pragma mark NSCopying - (id)copyWithZone:(NSZone *)zone { - return self; + return self; } #pragma mark NSCoding - (instancetype)initWithCoder:(NSCoder *)coder { - // Always return the singleton. - return self.class.tupleNil; + // Always return the singleton. + return self.class.tupleNil; } - (void)encodeWithCoder:(NSCoder *)coder { @@ -53,152 +53,152 @@ - (instancetype)initWithBackingArray:(NSArray *)backingArray NS_DESIGNATED_INITI @implementation RACTuple - (instancetype)init { - return [self initWithBackingArray:@[]]; + return [self initWithBackingArray:@[]]; } - (instancetype)initWithBackingArray:(NSArray *)backingArray { - self = [super init]; - - _backingArray = [backingArray copy]; - - return self; + self = [super init]; + + _backingArray = [backingArray copy]; + + return self; } - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.allObjects]; + return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.allObjects]; } - (BOOL)isEqual:(RACTuple *)object { - if (object == self) return YES; - if (![object isKindOfClass:self.class]) return NO; - - return [self.backingArray isEqual:object.backingArray]; + if (object == self) return YES; + if (![object isKindOfClass:self.class]) return NO; + + return [self.backingArray isEqual:object.backingArray]; } - (NSUInteger)hash { - return self.backingArray.hash; + return self.backingArray.hash; } #pragma mark NSFastEnumeration - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len { - return [self.backingArray countByEnumeratingWithState:state objects:buffer count:len]; + return [self.backingArray countByEnumeratingWithState:state objects:buffer count:len]; } #pragma mark NSCopying - (instancetype)copyWithZone:(NSZone *)zone { - // we're immutable, bitches! - return self; + // we're immutable, bitches! + return self; } #pragma mark NSCoding - (instancetype)initWithCoder:(NSCoder *)coder { - self = [self init]; - - _backingArray = [coder decodeObjectForKey:@rac_keypath(self.backingArray)]; + self = [self init]; + + _backingArray = [coder decodeObjectForKey:@rac_keypath(self.backingArray)]; - return self; + return self; } - (void)encodeWithCoder:(NSCoder *)coder { - if (self.backingArray != nil) [coder encodeObject:self.backingArray forKey:@rac_keypath(self.backingArray)]; + if (self.backingArray != nil) [coder encodeObject:self.backingArray forKey:@rac_keypath(self.backingArray)]; } #pragma mark API + (instancetype)tupleWithObjectsFromArray:(NSArray *)array { - return [self tupleWithObjectsFromArray:array convertNullsToNils:NO]; + return [self tupleWithObjectsFromArray:array convertNullsToNils:NO]; } + (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert { - if (!convert) { - return [[self alloc] initWithBackingArray:array]; - } + if (!convert) { + return [[self alloc] initWithBackingArray:array]; + } - NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; - for (id object in array) { - [newArray addObject:(object == NSNull.null ? RACTupleNil.tupleNil : object)]; - } + NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; + for (id object in array) { + [newArray addObject:(object == NSNull.null ? RACTupleNil.tupleNil : object)]; + } - return [[self alloc] initWithBackingArray:newArray]; + return [[self alloc] initWithBackingArray:newArray]; } + (instancetype)tupleWithObjects:(id)object, ... { - va_list args; - va_start(args, object); + va_list args; + va_start(args, object); - NSUInteger count = 0; - for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) { - ++count; - } + NSUInteger count = 0; + for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) { + ++count; + } - va_end(args); + va_end(args); - if (count == 0) { - return [[self alloc] init]; - } - - NSMutableArray *objects = [[NSMutableArray alloc] initWithCapacity:count]; - - va_start(args, object); - for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) { - [objects addObject:currentObject]; - } + if (count == 0) { + return [[self alloc] init]; + } + + NSMutableArray *objects = [[NSMutableArray alloc] initWithCapacity:count]; + + va_start(args, object); + for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) { + [objects addObject:currentObject]; + } - va_end(args); + va_end(args); - return [[self alloc] initWithBackingArray:objects]; + return [[self alloc] initWithBackingArray:objects]; } - (id)objectAtIndex:(NSUInteger)index { - if (index >= self.count) return nil; - - id object = self.backingArray[index]; - return (object == RACTupleNil.tupleNil ? nil : object); + if (index >= self.count) return nil; + + id object = self.backingArray[index]; + return (object == RACTupleNil.tupleNil ? nil : object); } - (NSArray *)allObjects { - NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:self.backingArray.count]; - for (id object in self.backingArray) { - [newArray addObject:(object == RACTupleNil.tupleNil ? NSNull.null : object)]; - } - - return newArray; + NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:self.backingArray.count]; + for (id object in self.backingArray) { + [newArray addObject:(object == RACTupleNil.tupleNil ? NSNull.null : object)]; + } + + return newArray; } - (instancetype)tupleByAddingObject:(id)obj { - NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil]; - return [self.class tupleWithObjectsFromArray:newArray]; + NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil]; + return [self.class tupleWithObjectsFromArray:newArray]; } - (NSUInteger)count { - return self.backingArray.count; + return self.backingArray.count; } - (id)first { - return self[0]; + return self[0]; } - (id)second { - return self[1]; + return self[1]; } - (id)third { - return self[2]; + return self[2]; } - (id)fourth { - return self[3]; + return self[3]; } - (id)fifth { - return self[4]; + return self[4]; } - (id)last { - return self[self.count - 1]; + return self[self.count - 1]; } @end @@ -207,7 +207,7 @@ - (id)last { @implementation RACTuple (RACSequenceAdditions) - (RACSequence *)rac_sequence { - return [RACTupleSequence sequenceWithTupleBackingArray:self.backingArray offset:0]; + return [RACTupleSequence sequenceWithTupleBackingArray:self.backingArray offset:0]; } @end @@ -215,7 +215,7 @@ - (RACSequence *)rac_sequence { @implementation RACTuple (ObjectSubscripting) - (id)objectAtIndexedSubscript:(NSUInteger)idx { - return [self objectAtIndex:idx]; + return [self objectAtIndex:idx]; } @end @@ -223,32 +223,32 @@ - (id)objectAtIndexedSubscript:(NSUInteger)idx { @implementation RACOneTuple - (instancetype)init { - return [self initWithBackingArray:@[ RACTupleNil.tupleNil ]]; + return [self initWithBackingArray:@[ RACTupleNil.tupleNil ]]; } - (instancetype)initWithBackingArray:(NSArray *)backingArray { - NSParameterAssert(backingArray.count == 1); - return [super initWithBackingArray:backingArray]; + NSParameterAssert(backingArray.count == 1); + return [super initWithBackingArray:backingArray]; } - (RACTwoTuple *)tupleByAddingObject:(id)obj { - NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil]; - return [RACTwoTuple tupleWithObjectsFromArray:newArray]; + NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil]; + return [RACTwoTuple tupleWithObjectsFromArray:newArray]; } + (instancetype)pack:(id)first { - return [self tupleWithObjectsFromArray:@[ - first ?: RACTupleNil.tupleNil, - ]]; + return [self tupleWithObjectsFromArray:@[ + first ?: RACTupleNil.tupleNil, + ]]; } - (BOOL)isEqual:(RACTuple *)object { - if (object == self) return YES; + if (object == self) return YES; - // We consider a RACTuple with an identical backing array as equal. - if (![object isKindOfClass:RACTuple.class]) return NO; - - return [self.backingArray isEqual:object.backingArray]; + // We consider a RACTuple with an identical backing array as equal. + if (![object isKindOfClass:RACTuple.class]) return NO; + + return [self.backingArray isEqual:object.backingArray]; } @dynamic first; @@ -258,33 +258,33 @@ - (BOOL)isEqual:(RACTuple *)object { @implementation RACTwoTuple - (instancetype)init { - return [self initWithBackingArray:@[ RACTupleNil.tupleNil, RACTupleNil.tupleNil ]]; + return [self initWithBackingArray:@[ RACTupleNil.tupleNil, RACTupleNil.tupleNil ]]; } - (instancetype)initWithBackingArray:(NSArray *)backingArray { - NSParameterAssert(backingArray.count == 2); - return [super initWithBackingArray:backingArray]; + NSParameterAssert(backingArray.count == 2); + return [super initWithBackingArray:backingArray]; } - (RACThreeTuple *)tupleByAddingObject:(id)obj { - NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil]; - return [RACThreeTuple tupleWithObjectsFromArray:newArray]; + NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil]; + return [RACThreeTuple tupleWithObjectsFromArray:newArray]; } + (instancetype)pack:(id)first :(id)second { - return [self tupleWithObjectsFromArray:@[ - first ?: RACTupleNil.tupleNil, - second ?: RACTupleNil.tupleNil, - ]]; + return [self tupleWithObjectsFromArray:@[ + first ?: RACTupleNil.tupleNil, + second ?: RACTupleNil.tupleNil, + ]]; } - (BOOL)isEqual:(RACTuple *)object { - if (object == self) return YES; + if (object == self) return YES; - // We consider a RACTuple with an identical backing array as equal. - if (![object isKindOfClass:RACTuple.class]) return NO; - - return [self.backingArray isEqual:object.backingArray]; + // We consider a RACTuple with an identical backing array as equal. + if (![object isKindOfClass:RACTuple.class]) return NO; + + return [self.backingArray isEqual:object.backingArray]; } @dynamic first; @@ -295,34 +295,34 @@ - (BOOL)isEqual:(RACTuple *)object { @implementation RACThreeTuple - (instancetype)init { - return [super initWithBackingArray:@[ RACTupleNil.tupleNil, RACTupleNil.tupleNil, RACTupleNil.tupleNil ]]; + return [super initWithBackingArray:@[ RACTupleNil.tupleNil, RACTupleNil.tupleNil, RACTupleNil.tupleNil ]]; } - (instancetype)initWithBackingArray:(NSArray *)backingArray { - NSParameterAssert(backingArray.count == 3); - return [super initWithBackingArray:backingArray]; + NSParameterAssert(backingArray.count == 3); + return [super initWithBackingArray:backingArray]; } - (RACFourTuple *)tupleByAddingObject:(id)obj { - NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil]; - return [RACFourTuple tupleWithObjectsFromArray:newArray]; + NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil]; + return [RACFourTuple tupleWithObjectsFromArray:newArray]; } + (instancetype)pack:(id)first :(id)second :(id)third { - return [self tupleWithObjectsFromArray:@[ - first ?: RACTupleNil.tupleNil, - second ?: RACTupleNil.tupleNil, - third ?: RACTupleNil.tupleNil, - ]]; + return [self tupleWithObjectsFromArray:@[ + first ?: RACTupleNil.tupleNil, + second ?: RACTupleNil.tupleNil, + third ?: RACTupleNil.tupleNil, + ]]; } - (BOOL)isEqual:(RACTuple *)object { - if (object == self) return YES; + if (object == self) return YES; - // We consider a RACTuple with an identical backing array as equal. - if (![object isKindOfClass:RACTuple.class]) return NO; - - return [self.backingArray isEqual:object.backingArray]; + // We consider a RACTuple with an identical backing array as equal. + if (![object isKindOfClass:RACTuple.class]) return NO; + + return [self.backingArray isEqual:object.backingArray]; } @dynamic first; @@ -334,35 +334,35 @@ - (BOOL)isEqual:(RACTuple *)object { @implementation RACFourTuple - (instancetype)init { - return [self initWithBackingArray:@[ RACTupleNil.tupleNil, RACTupleNil.tupleNil, RACTupleNil.tupleNil, RACTupleNil.tupleNil ]]; + return [self initWithBackingArray:@[ RACTupleNil.tupleNil, RACTupleNil.tupleNil, RACTupleNil.tupleNil, RACTupleNil.tupleNil ]]; } - (instancetype)initWithBackingArray:(NSArray *)backingArray { - NSParameterAssert(backingArray.count == 4); - return [super initWithBackingArray:backingArray]; + NSParameterAssert(backingArray.count == 4); + return [super initWithBackingArray:backingArray]; } - (RACFiveTuple *)tupleByAddingObject:(id)obj { - NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil]; - return [RACFiveTuple tupleWithObjectsFromArray:newArray]; + NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil]; + return [RACFiveTuple tupleWithObjectsFromArray:newArray]; } + (instancetype)pack:(id)first :(id)second :(id)third :(id)fourth { - return [self tupleWithObjectsFromArray:@[ - first ?: RACTupleNil.tupleNil, - second ?: RACTupleNil.tupleNil, - third ?: RACTupleNil.tupleNil, - fourth ?: RACTupleNil.tupleNil, - ]]; + return [self tupleWithObjectsFromArray:@[ + first ?: RACTupleNil.tupleNil, + second ?: RACTupleNil.tupleNil, + third ?: RACTupleNil.tupleNil, + fourth ?: RACTupleNil.tupleNil, + ]]; } - (BOOL)isEqual:(RACTuple *)object { - if (object == self) return YES; + if (object == self) return YES; - // We consider a RACTuple with an identical backing array as equal. - if (![object isKindOfClass:RACTuple.class]) return NO; - - return [self.backingArray isEqual:object.backingArray]; + // We consider a RACTuple with an identical backing array as equal. + if (![object isKindOfClass:RACTuple.class]) return NO; + + return [self.backingArray isEqual:object.backingArray]; } @dynamic first; @@ -375,31 +375,31 @@ - (BOOL)isEqual:(RACTuple *)object { @implementation RACFiveTuple - (instancetype)init { - return [self initWithBackingArray:@[ RACTupleNil.tupleNil, RACTupleNil.tupleNil, RACTupleNil.tupleNil, RACTupleNil.tupleNil, RACTupleNil.tupleNil ]]; + return [self initWithBackingArray:@[ RACTupleNil.tupleNil, RACTupleNil.tupleNil, RACTupleNil.tupleNil, RACTupleNil.tupleNil, RACTupleNil.tupleNil ]]; } - (instancetype)initWithBackingArray:(NSArray *)backingArray { - NSParameterAssert(backingArray.count == 5); - return [super initWithBackingArray:backingArray]; + NSParameterAssert(backingArray.count == 5); + return [super initWithBackingArray:backingArray]; } + (instancetype)pack:(id)first :(id)second :(id)third :(id)fourth :(id)fifth { - return [self tupleWithObjectsFromArray:@[ - first ?: RACTupleNil.tupleNil, - second ?: RACTupleNil.tupleNil, - third ?: RACTupleNil.tupleNil, - fourth ?: RACTupleNil.tupleNil, - fifth ?: RACTupleNil.tupleNil, - ]]; + return [self tupleWithObjectsFromArray:@[ + first ?: RACTupleNil.tupleNil, + second ?: RACTupleNil.tupleNil, + third ?: RACTupleNil.tupleNil, + fourth ?: RACTupleNil.tupleNil, + fifth ?: RACTupleNil.tupleNil, + ]]; } - (BOOL)isEqual:(RACTuple *)object { - if (object == self) return YES; + if (object == self) return YES; - // We consider a RACTuple with an identical backing array as equal. - if (![object isKindOfClass:RACTuple.class]) return NO; - - return [self.backingArray isEqual:object.backingArray]; + // We consider a RACTuple with an identical backing array as equal. + if (![object isKindOfClass:RACTuple.class]) return NO; + + return [self.backingArray isEqual:object.backingArray]; } @dynamic first; @@ -415,22 +415,22 @@ @implementation RACTupleUnpackingTrampoline #pragma mark Lifecycle + (instancetype)trampoline { - static dispatch_once_t onceToken; - static id trampoline = nil; - dispatch_once(&onceToken, ^{ - trampoline = [[self alloc] init]; - }); - - return trampoline; + static dispatch_once_t onceToken; + static id trampoline = nil; + dispatch_once(&onceToken, ^{ + trampoline = [[self alloc] init]; + }); + + return trampoline; } - (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables { - NSCParameterAssert(variables != nil); - - [variables enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger index, BOOL *stop) { - __strong id *ptr = (__strong id *)value.pointerValue; - *ptr = tuple[index]; - }]; + NSCParameterAssert(variables != nil); + + [variables enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger index, BOOL *stop) { + __strong id *ptr = (__strong id *)value.pointerValue; + *ptr = tuple[index]; + }]; } @end diff --git a/ReactiveObjC/RACTupleSequence.m b/ReactiveObjC/RACTupleSequence.m index ee725ea4e..e33993701 100644 --- a/ReactiveObjC/RACTupleSequence.m +++ b/ReactiveObjC/RACTupleSequence.m @@ -24,45 +24,45 @@ @implementation RACTupleSequence #pragma mark Lifecycle + (RACSequence *)sequenceWithTupleBackingArray:(NSArray *)backingArray offset:(NSUInteger)offset { - NSCParameterAssert(offset <= backingArray.count); + NSCParameterAssert(offset <= backingArray.count); - if (offset == backingArray.count) return self.empty; + if (offset == backingArray.count) return self.empty; - RACTupleSequence *seq = [[self alloc] init]; - seq->_tupleBackingArray = backingArray; - seq->_offset = offset; - return seq; + RACTupleSequence *seq = [[self alloc] init]; + seq->_tupleBackingArray = backingArray; + seq->_offset = offset; + return seq; } #pragma mark RACSequence - (id)head { - id object = self.tupleBackingArray[self.offset]; - return (object == RACTupleNil.tupleNil ? NSNull.null : object); + id object = self.tupleBackingArray[self.offset]; + return (object == RACTupleNil.tupleNil ? NSNull.null : object); } - (RACSequence *)tail { - RACSequence *sequence = [self.class sequenceWithTupleBackingArray:self.tupleBackingArray offset:self.offset + 1]; - sequence.name = self.name; - return sequence; + RACSequence *sequence = [self.class sequenceWithTupleBackingArray:self.tupleBackingArray offset:self.offset + 1]; + sequence.name = self.name; + return sequence; } - (NSArray *)array { - NSRange range = NSMakeRange(self.offset, self.tupleBackingArray.count - self.offset); - NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:range.length]; + NSRange range = NSMakeRange(self.offset, self.tupleBackingArray.count - self.offset); + NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:range.length]; - [self.tupleBackingArray enumerateObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:range] options:0 usingBlock:^(id object, NSUInteger index, BOOL *stop) { - id mappedObject = (object == RACTupleNil.tupleNil ? NSNull.null : object); - [array addObject:mappedObject]; - }]; + [self.tupleBackingArray enumerateObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:range] options:0 usingBlock:^(id object, NSUInteger index, BOOL *stop) { + id mappedObject = (object == RACTupleNil.tupleNil ? NSNull.null : object); + [array addObject:mappedObject]; + }]; - return array; + return array; } #pragma mark NSObject - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p>{ name = %@, tuple = %@ }", self.class, self, self.name, self.tupleBackingArray]; + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, tuple = %@ }", self.class, self, self.name, self.tupleBackingArray]; } @end diff --git a/ReactiveObjC/RACUnarySequence.m b/ReactiveObjC/RACUnarySequence.m index cd22b0275..e4382c1ff 100644 --- a/ReactiveObjC/RACUnarySequence.m +++ b/ReactiveObjC/RACUnarySequence.m @@ -26,56 +26,56 @@ @implementation RACUnarySequence #pragma mark Lifecycle + (RACUnarySequence *)return:(id)value { - RACUnarySequence *sequence = [[self alloc] init]; - sequence.head = value; - return [sequence setNameWithFormat:@"+return: %@", RACDescription(value)]; + RACUnarySequence *sequence = [[self alloc] init]; + sequence.head = value; + return [sequence setNameWithFormat:@"+return: %@", RACDescription(value)]; } #pragma mark RACSequence - (RACSequence *)tail { - return nil; + return nil; } - (RACSequence *)bind:(RACSequenceBindBlock (^)(void))block { - RACStreamBindBlock bindBlock = block(); - BOOL stop = NO; + RACStreamBindBlock bindBlock = block(); + BOOL stop = NO; - RACSequence *result = (id)[bindBlock(self.head, &stop) setNameWithFormat:@"[%@] -bind:", self.name]; - return result ?: self.class.empty; + RACSequence *result = (id)[bindBlock(self.head, &stop) setNameWithFormat:@"[%@] -bind:", self.name]; + return result ?: self.class.empty; } #pragma mark NSCoding - (Class)classForCoder { - // Unary sequences should be encoded as themselves, not array sequences. - return self.class; + // Unary sequences should be encoded as themselves, not array sequences. + return self.class; } - (instancetype)initWithCoder:(NSCoder *)coder { - id value = [coder decodeObjectForKey:@rac_keypath(self.head)]; - return [self.class return:value]; + id value = [coder decodeObjectForKey:@rac_keypath(self.head)]; + return [self.class return:value]; } - (void)encodeWithCoder:(NSCoder *)coder { - if (self.head != nil) [coder encodeObject:self.head forKey:@rac_keypath(self.head)]; + if (self.head != nil) [coder encodeObject:self.head forKey:@rac_keypath(self.head)]; } #pragma mark NSObject - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p>{ name = %@, head = %@ }", self.class, self, self.name, self.head]; + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, head = %@ }", self.class, self, self.name, self.head]; } - (NSUInteger)hash { - return [self.head hash]; + return [self.head hash]; } - (BOOL)isEqual:(RACUnarySequence *)seq { - if (self == seq) return YES; - if (![seq isKindOfClass:RACUnarySequence.class]) return NO; + if (self == seq) return YES; + if (![seq isKindOfClass:RACUnarySequence.class]) return NO; - return self.head == seq.head || [(NSObject *)self.head isEqual:seq.head]; + return self.head == seq.head || [(NSObject *)self.head isEqual:seq.head]; } @end diff --git a/ReactiveObjC/RACUnit.m b/ReactiveObjC/RACUnit.m index 691e5da42..dda018bfc 100644 --- a/ReactiveObjC/RACUnit.m +++ b/ReactiveObjC/RACUnit.m @@ -13,13 +13,13 @@ @implementation RACUnit #pragma mark API + (RACUnit *)defaultUnit { - static dispatch_once_t onceToken; - static RACUnit *defaultUnit = nil; - dispatch_once(&onceToken, ^{ - defaultUnit = [[self alloc] init]; - }); - - return defaultUnit; + static dispatch_once_t onceToken; + static RACUnit *defaultUnit = nil; + dispatch_once(&onceToken, ^{ + defaultUnit = [[self alloc] init]; + }); + + return defaultUnit; } @end diff --git a/ReactiveObjC/RACValueTransformer.m b/ReactiveObjC/RACValueTransformer.m index 6f44e0f2b..045254ba5 100644 --- a/ReactiveObjC/RACValueTransformer.m +++ b/ReactiveObjC/RACValueTransformer.m @@ -19,7 +19,7 @@ @implementation RACValueTransformer #pragma mark NSValueTransformer + (BOOL)allowsReverseTransformation { - return NO; + return NO; } - (id)transformedValue:(id)value { @@ -32,11 +32,11 @@ - (id)transformedValue:(id)value { @synthesize transformBlock; + (instancetype)transformerWithBlock:(id (^)(id value))block { - NSCParameterAssert(block != NULL); - - RACValueTransformer *transformer = [[self alloc] init]; - transformer.transformBlock = block; - return transformer; + NSCParameterAssert(block != NULL); + + RACValueTransformer *transformer = [[self alloc] init]; + transformer.transformBlock = block; + return transformer; } @end diff --git a/ReactiveObjC/ReactiveObjC.h b/ReactiveObjC/ReactiveObjC.h index 6defe8aa8..251515db4 100644 --- a/ReactiveObjC/ReactiveObjC.h +++ b/ReactiveObjC/ReactiveObjC.h @@ -63,33 +63,33 @@ FOUNDATION_EXPORT const unsigned char ReactiveObjCVersionString[]; #if TARGET_OS_WATCH #elif TARGET_OS_IOS || TARGET_OS_TV - #import - #import - #import - #import - #import - #import - #import - #import - #import - #import + #import + #import + #import + #import + #import + #import + #import + #import + #import + #import - #if TARGET_OS_IOS - #import - #import - #import - #import - #import - #import - #import - #import - #import - #import - #endif + #if TARGET_OS_IOS + #import + #import + #import + #import + #import + #import + #import + #import + #import + #import + #endif #elif TARGET_OS_MAC - #import - #import - #import - #import - #import + #import + #import + #import + #import + #import #endif diff --git a/ReactiveObjC/UIActionSheet+RACSignalSupport.m b/ReactiveObjC/UIActionSheet+RACSignalSupport.m index 6a1a4102d..5839699e3 100644 --- a/ReactiveObjC/UIActionSheet+RACSignalSupport.m +++ b/ReactiveObjC/UIActionSheet+RACSignalSupport.m @@ -23,27 +23,27 @@ static void RACUseDelegateProxy(UIActionSheet *self) { } - (RACDelegateProxy *)rac_delegateProxy { - RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); - if (proxy == nil) { - proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIActionSheetDelegate)]; - objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } + RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); + if (proxy == nil) { + proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIActionSheetDelegate)]; + objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } - return proxy; + return proxy; } - (RACSignal *)rac_buttonClickedSignal { - RACSignal *signal = [[[[self.rac_delegateProxy - signalForSelector:@selector(actionSheet:clickedButtonAtIndex:)] - reduceEach:^(UIActionSheet *actionSheet, NSNumber *buttonIndex) { - return buttonIndex; - }] - takeUntil:self.rac_willDeallocSignal] - setNameWithFormat:@"%@ -rac_buttonClickedSignal", RACDescription(self)]; + RACSignal *signal = [[[[self.rac_delegateProxy + signalForSelector:@selector(actionSheet:clickedButtonAtIndex:)] + reduceEach:^(UIActionSheet *actionSheet, NSNumber *buttonIndex) { + return buttonIndex; + }] + takeUntil:self.rac_willDeallocSignal] + setNameWithFormat:@"%@ -rac_buttonClickedSignal", RACDescription(self)]; - RACUseDelegateProxy(self); + RACUseDelegateProxy(self); - return signal; + return signal; } @end diff --git a/ReactiveObjC/UIAlertView+RACSignalSupport.m b/ReactiveObjC/UIAlertView+RACSignalSupport.m index 0afc94a8d..38741fd41 100644 --- a/ReactiveObjC/UIAlertView+RACSignalSupport.m +++ b/ReactiveObjC/UIAlertView+RACSignalSupport.m @@ -16,48 +16,48 @@ @implementation UIAlertView (RACSignalSupport) static void RACUseDelegateProxy(UIAlertView *self) { - if (self.delegate == self.rac_delegateProxy) return; + if (self.delegate == self.rac_delegateProxy) return; - self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; - self.delegate = (id)self.rac_delegateProxy; + self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; + self.delegate = (id)self.rac_delegateProxy; } - (RACDelegateProxy *)rac_delegateProxy { - RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); - if (proxy == nil) { - proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIAlertViewDelegate)]; - objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } + RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); + if (proxy == nil) { + proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIAlertViewDelegate)]; + objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } - return proxy; + return proxy; } - (RACSignal *)rac_buttonClickedSignal { - RACSignal *signal = [[[[self.rac_delegateProxy - signalForSelector:@selector(alertView:clickedButtonAtIndex:)] - reduceEach:^(UIAlertView *alertView, NSNumber *buttonIndex) { - return buttonIndex; - }] - takeUntil:self.rac_willDeallocSignal] - setNameWithFormat:@"%@ -rac_buttonClickedSignal", RACDescription(self)]; + RACSignal *signal = [[[[self.rac_delegateProxy + signalForSelector:@selector(alertView:clickedButtonAtIndex:)] + reduceEach:^(UIAlertView *alertView, NSNumber *buttonIndex) { + return buttonIndex; + }] + takeUntil:self.rac_willDeallocSignal] + setNameWithFormat:@"%@ -rac_buttonClickedSignal", RACDescription(self)]; - RACUseDelegateProxy(self); + RACUseDelegateProxy(self); - return signal; + return signal; } - (RACSignal *)rac_willDismissSignal { - RACSignal *signal = [[[[self.rac_delegateProxy - signalForSelector:@selector(alertView:willDismissWithButtonIndex:)] - reduceEach:^(UIAlertView *alertView, NSNumber *buttonIndex) { - return buttonIndex; - }] - takeUntil:self.rac_willDeallocSignal] - setNameWithFormat:@"%@ -rac_willDismissSignal", RACDescription(self)]; + RACSignal *signal = [[[[self.rac_delegateProxy + signalForSelector:@selector(alertView:willDismissWithButtonIndex:)] + reduceEach:^(UIAlertView *alertView, NSNumber *buttonIndex) { + return buttonIndex; + }] + takeUntil:self.rac_willDeallocSignal] + setNameWithFormat:@"%@ -rac_willDismissSignal", RACDescription(self)]; - RACUseDelegateProxy(self); + RACUseDelegateProxy(self); - return signal; + return signal; } @end diff --git a/ReactiveObjC/UIBarButtonItem+RACCommandSupport.m b/ReactiveObjC/UIBarButtonItem+RACCommandSupport.m index 339473c3d..c6aa0fbf6 100644 --- a/ReactiveObjC/UIBarButtonItem+RACCommandSupport.m +++ b/ReactiveObjC/UIBarButtonItem+RACCommandSupport.m @@ -19,36 +19,36 @@ @implementation UIBarButtonItem (RACCommandSupport) - (RACCommand *)rac_command { - return objc_getAssociatedObject(self, UIControlRACCommandKey); + return objc_getAssociatedObject(self, UIControlRACCommandKey); } - (void)setRac_command:(RACCommand *)command { - objc_setAssociatedObject(self, UIControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - // Check for stored signal in order to remove it and add a new one - RACDisposable *disposable = objc_getAssociatedObject(self, UIControlEnabledDisposableKey); - [disposable dispose]; - - if (command == nil) return; - - disposable = [command.enabled setKeyPath:@rac_keypath(self.enabled) onObject:self]; - objc_setAssociatedObject(self, UIControlEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - [self rac_hijackActionAndTargetIfNeeded]; + objc_setAssociatedObject(self, UIControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // Check for stored signal in order to remove it and add a new one + RACDisposable *disposable = objc_getAssociatedObject(self, UIControlEnabledDisposableKey); + [disposable dispose]; + + if (command == nil) return; + + disposable = [command.enabled setKeyPath:@rac_keypath(self.enabled) onObject:self]; + objc_setAssociatedObject(self, UIControlEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + [self rac_hijackActionAndTargetIfNeeded]; } - (void)rac_hijackActionAndTargetIfNeeded { - SEL hijackSelector = @selector(rac_commandPerformAction:); - if (self.target == self && self.action == hijackSelector) return; - - if (self.target != nil) NSLog(@"WARNING: UIBarButtonItem.rac_command hijacks the control's existing target and action."); - - self.target = self; - self.action = hijackSelector; + SEL hijackSelector = @selector(rac_commandPerformAction:); + if (self.target == self && self.action == hijackSelector) return; + + if (self.target != nil) NSLog(@"WARNING: UIBarButtonItem.rac_command hijacks the control's existing target and action."); + + self.target = self; + self.action = hijackSelector; } - (void)rac_commandPerformAction:(id)sender { - [self.rac_command execute:sender]; + [self.rac_command execute:sender]; } @end diff --git a/ReactiveObjC/UIButton+RACCommandSupport.m b/ReactiveObjC/UIButton+RACCommandSupport.m index 79fdfb61f..ef70a9173 100644 --- a/ReactiveObjC/UIButton+RACCommandSupport.m +++ b/ReactiveObjC/UIButton+RACCommandSupport.m @@ -19,38 +19,38 @@ @implementation UIButton (RACCommandSupport) - (RACCommand *)rac_command { - return objc_getAssociatedObject(self, UIButtonRACCommandKey); + return objc_getAssociatedObject(self, UIButtonRACCommandKey); } - (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:@rac_keypath(self.enabled) onObject:self]; - objc_setAssociatedObject(self, UIButtonEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - [self rac_hijackActionAndTargetIfNeeded]; + 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:@rac_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]; + 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]; } - (void)rac_commandPerformAction:(id)sender { - [self.rac_command execute:sender]; + [self.rac_command execute:sender]; } @end diff --git a/ReactiveObjC/UICollectionReusableView+RACSignalSupport.m b/ReactiveObjC/UICollectionReusableView+RACSignalSupport.m index 9215c162b..4fb549da2 100644 --- a/ReactiveObjC/UICollectionReusableView+RACSignalSupport.m +++ b/ReactiveObjC/UICollectionReusableView+RACSignalSupport.m @@ -16,16 +16,16 @@ @implementation UICollectionReusableView (RACSignalSupport) - (RACSignal *)rac_prepareForReuseSignal { - RACSignal *signal = objc_getAssociatedObject(self, _cmd); - if (signal != nil) return signal; - - signal = [[[self - rac_signalForSelector:@selector(prepareForReuse)] - mapReplace:RACUnit.defaultUnit] - setNameWithFormat:@"%@ -rac_prepareForReuseSignal", RACDescription(self)]; - - objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - return signal; + RACSignal *signal = objc_getAssociatedObject(self, _cmd); + if (signal != nil) return signal; + + signal = [[[self + rac_signalForSelector:@selector(prepareForReuse)] + mapReplace:RACUnit.defaultUnit] + setNameWithFormat:@"%@ -rac_prepareForReuseSignal", RACDescription(self)]; + + objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return signal; } @end diff --git a/ReactiveObjC/UIControl+RACSignalSupport.m b/ReactiveObjC/UIControl+RACSignalSupport.m index 772acfd54..f3120e10d 100644 --- a/ReactiveObjC/UIControl+RACSignalSupport.m +++ b/ReactiveObjC/UIControl+RACSignalSupport.m @@ -18,26 +18,26 @@ @implementation UIControl (RACSignalSupport) - (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents { - @rac_weakify(self); - - return [[RACSignal - createSignal:^(id subscriber) { - @rac_strongify(self); - - [self addTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents]; - - RACDisposable *disposable = [RACDisposable disposableWithBlock:^{ - [subscriber sendCompleted]; - }]; - [self.rac_deallocDisposable addDisposable:disposable]; - - return [RACDisposable disposableWithBlock:^{ - @rac_strongify(self); - [self.rac_deallocDisposable removeDisposable:disposable]; - [self removeTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents]; - }]; - }] - setNameWithFormat:@"%@ -rac_signalForControlEvents: %lx", RACDescription(self), (unsigned long)controlEvents]; + @rac_weakify(self); + + return [[RACSignal + createSignal:^(id subscriber) { + @rac_strongify(self); + + [self addTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents]; + + RACDisposable *disposable = [RACDisposable disposableWithBlock:^{ + [subscriber sendCompleted]; + }]; + [self.rac_deallocDisposable addDisposable:disposable]; + + return [RACDisposable disposableWithBlock:^{ + @rac_strongify(self); + [self.rac_deallocDisposable removeDisposable:disposable]; + [self removeTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents]; + }]; + }] + setNameWithFormat:@"%@ -rac_signalForControlEvents: %lx", RACDescription(self), (unsigned long)controlEvents]; } @end diff --git a/ReactiveObjC/UIControl+RACSignalSupportPrivate.m b/ReactiveObjC/UIControl+RACSignalSupportPrivate.m index 867f57e74..0aa525b17 100644 --- a/ReactiveObjC/UIControl+RACSignalSupportPrivate.m +++ b/ReactiveObjC/UIControl+RACSignalSupportPrivate.m @@ -18,31 +18,31 @@ @implementation UIControl (RACSignalSupportPrivate) - (RACChannelTerminal *)rac_channelForControlEvents:(UIControlEvents)controlEvents key:(NSString *)key nilValue:(id)nilValue { - NSCParameterAssert(key.length > 0); - key = [key copy]; - RACChannel *channel = [[RACChannel alloc] init]; - - [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - [channel.followingTerminal sendCompleted]; - }]]; - - RACSignal *eventSignal = [[[self - rac_signalForControlEvents:controlEvents] - mapReplace:key] - takeUntil:[[channel.followingTerminal - ignoreValues] - catchTo:RACSignal.empty]]; - [[self - rac_liftSelector:@selector(valueForKey:) withSignals:eventSignal, nil] - subscribe:channel.followingTerminal]; - - RACSignal *valuesSignal = [channel.followingTerminal - map:^(id value) { - return value ?: nilValue; - }]; - [self rac_liftSelector:@selector(setValue:forKey:) withSignals:valuesSignal, [RACSignal return:key], nil]; - - return channel.leadingTerminal; + NSCParameterAssert(key.length > 0); + key = [key copy]; + RACChannel *channel = [[RACChannel alloc] init]; + + [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + [channel.followingTerminal sendCompleted]; + }]]; + + RACSignal *eventSignal = [[[self + rac_signalForControlEvents:controlEvents] + mapReplace:key] + takeUntil:[[channel.followingTerminal + ignoreValues] + catchTo:RACSignal.empty]]; + [[self + rac_liftSelector:@selector(valueForKey:) withSignals:eventSignal, nil] + subscribe:channel.followingTerminal]; + + RACSignal *valuesSignal = [channel.followingTerminal + map:^(id value) { + return value ?: nilValue; + }]; + [self rac_liftSelector:@selector(setValue:forKey:) withSignals:valuesSignal, [RACSignal return:key], nil]; + + return channel.leadingTerminal; } @end diff --git a/ReactiveObjC/UIDatePicker+RACSignalSupport.m b/ReactiveObjC/UIDatePicker+RACSignalSupport.m index 44df9f150..d25de9e18 100644 --- a/ReactiveObjC/UIDatePicker+RACSignalSupport.m +++ b/ReactiveObjC/UIDatePicker+RACSignalSupport.m @@ -13,7 +13,7 @@ @implementation UIDatePicker (RACSignalSupport) - (RACChannelTerminal *)rac_newDateChannelWithNilValue:(NSDate *)nilValue { - return [self rac_channelForControlEvents:UIControlEventValueChanged key:@rac_keypath(self.date) nilValue:nilValue]; + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@rac_keypath(self.date) nilValue:nilValue]; } @end diff --git a/ReactiveObjC/UIGestureRecognizer+RACSignalSupport.m b/ReactiveObjC/UIGestureRecognizer+RACSignalSupport.m index 2f8f31e06..90299b0f2 100644 --- a/ReactiveObjC/UIGestureRecognizer+RACSignalSupport.m +++ b/ReactiveObjC/UIGestureRecognizer+RACSignalSupport.m @@ -18,23 +18,23 @@ @implementation UIGestureRecognizer (RACSignalSupport) - (RACSignal *)rac_gestureSignal { - @rac_weakify(self); + @rac_weakify(self); - return [[RACSignal - createSignal:^(id subscriber) { - @rac_strongify(self); + return [[RACSignal + createSignal:^(id subscriber) { + @rac_strongify(self); - [self addTarget:subscriber action:@selector(sendNext:)]; - [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - [subscriber sendCompleted]; - }]]; + [self addTarget:subscriber action:@selector(sendNext:)]; + [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + [subscriber sendCompleted]; + }]]; - return [RACDisposable disposableWithBlock:^{ - @rac_strongify(self); - [self removeTarget:subscriber action:@selector(sendNext:)]; - }]; - }] - setNameWithFormat:@"%@ -rac_gestureSignal", RACDescription(self)]; + return [RACDisposable disposableWithBlock:^{ + @rac_strongify(self); + [self removeTarget:subscriber action:@selector(sendNext:)]; + }]; + }] + setNameWithFormat:@"%@ -rac_gestureSignal", RACDescription(self)]; } @end diff --git a/ReactiveObjC/UIImagePickerController+RACSignalSupport.m b/ReactiveObjC/UIImagePickerController+RACSignalSupport.m index a5df4eec7..3eacb1784 100644 --- a/ReactiveObjC/UIImagePickerController+RACSignalSupport.m +++ b/ReactiveObjC/UIImagePickerController+RACSignalSupport.m @@ -16,38 +16,38 @@ @implementation UIImagePickerController (RACSignalSupport) static void RACUseDelegateProxy(UIImagePickerController *self) { - if (self.delegate == self.rac_delegateProxy) return; + if (self.delegate == self.rac_delegateProxy) return; - self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; - self.delegate = (id)self.rac_delegateProxy; + self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; + self.delegate = (id)self.rac_delegateProxy; } - (RACDelegateProxy *)rac_delegateProxy { - RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); - if (proxy == nil) { - proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIImagePickerControllerDelegate)]; - objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } + RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); + if (proxy == nil) { + proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIImagePickerControllerDelegate)]; + objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } - return proxy; + return proxy; } - (RACSignal *)rac_imageSelectedSignal { - RACSignal *pickerCancelledSignal = [[self.rac_delegateProxy - signalForSelector:@selector(imagePickerControllerDidCancel:)] - merge:self.rac_willDeallocSignal]; - - RACSignal *imagePickerSignal = [[[[self.rac_delegateProxy - signalForSelector:@selector(imagePickerController:didFinishPickingMediaWithInfo:)] - reduceEach:^(UIImagePickerController *pickerController, NSDictionary *userInfo) { - return userInfo; - }] - takeUntil:pickerCancelledSignal] - setNameWithFormat:@"%@ -rac_imageSelectedSignal", RACDescription(self)]; + RACSignal *pickerCancelledSignal = [[self.rac_delegateProxy + signalForSelector:@selector(imagePickerControllerDidCancel:)] + merge:self.rac_willDeallocSignal]; - RACUseDelegateProxy(self); + RACSignal *imagePickerSignal = [[[[self.rac_delegateProxy + signalForSelector:@selector(imagePickerController:didFinishPickingMediaWithInfo:)] + reduceEach:^(UIImagePickerController *pickerController, NSDictionary *userInfo) { + return userInfo; + }] + takeUntil:pickerCancelledSignal] + setNameWithFormat:@"%@ -rac_imageSelectedSignal", RACDescription(self)]; - return imagePickerSignal; + RACUseDelegateProxy(self); + + return imagePickerSignal; } @end diff --git a/ReactiveObjC/UIRefreshControl+RACCommandSupport.m b/ReactiveObjC/UIRefreshControl+RACCommandSupport.m index 9a4e2c687..f0aacbd30 100644 --- a/ReactiveObjC/UIRefreshControl+RACCommandSupport.m +++ b/ReactiveObjC/UIRefreshControl+RACCommandSupport.m @@ -22,38 +22,38 @@ @implementation UIRefreshControl (RACCommandSupport) - (RACCommand *)rac_command { - return objc_getAssociatedObject(self, UIRefreshControlRACCommandKey); + return objc_getAssociatedObject(self, UIRefreshControlRACCommandKey); } - (void)setRac_command:(RACCommand *)command { - objc_setAssociatedObject(self, UIRefreshControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - // 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:@rac_keypath(self.enabled) onObject:self]; - - RACDisposable *executionDisposable = [[[[[self - rac_signalForControlEvents:UIControlEventValueChanged] - map:^(UIRefreshControl *x) { - return [[[command - execute:x] - catchTo:[RACSignal empty]] - then:^{ - return [RACSignal return:x]; - }]; - }] - concat] - deliverOnMainThread] - subscribeNext:^(UIRefreshControl *x) { - [x endRefreshing]; - }]; - - RACDisposable *commandDisposable = [RACCompoundDisposable compoundDisposableWithDisposables:@[ enabledDisposable, executionDisposable ]]; - objc_setAssociatedObject(self, UIRefreshControlDisposableKey, commandDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, UIRefreshControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // 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:@rac_keypath(self.enabled) onObject:self]; + + RACDisposable *executionDisposable = [[[[[self + rac_signalForControlEvents:UIControlEventValueChanged] + map:^(UIRefreshControl *x) { + return [[[command + execute:x] + catchTo:[RACSignal empty]] + then:^{ + return [RACSignal return:x]; + }]; + }] + concat] + deliverOnMainThread] + subscribeNext:^(UIRefreshControl *x) { + [x endRefreshing]; + }]; + + RACDisposable *commandDisposable = [RACCompoundDisposable compoundDisposableWithDisposables:@[ enabledDisposable, executionDisposable ]]; + objc_setAssociatedObject(self, UIRefreshControlDisposableKey, commandDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end diff --git a/ReactiveObjC/UISegmentedControl+RACSignalSupport.m b/ReactiveObjC/UISegmentedControl+RACSignalSupport.m index 94ab136ad..b60f6fe12 100644 --- a/ReactiveObjC/UISegmentedControl+RACSignalSupport.m +++ b/ReactiveObjC/UISegmentedControl+RACSignalSupport.m @@ -13,7 +13,7 @@ @implementation UISegmentedControl (RACSignalSupport) - (RACChannelTerminal *)rac_newSelectedSegmentIndexChannelWithNilValue:(NSNumber *)nilValue { - return [self rac_channelForControlEvents:UIControlEventValueChanged key:@rac_keypath(self.selectedSegmentIndex) nilValue:nilValue]; + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@rac_keypath(self.selectedSegmentIndex) nilValue:nilValue]; } @end diff --git a/ReactiveObjC/UISlider+RACSignalSupport.m b/ReactiveObjC/UISlider+RACSignalSupport.m index 48eb72100..1fb7995da 100644 --- a/ReactiveObjC/UISlider+RACSignalSupport.m +++ b/ReactiveObjC/UISlider+RACSignalSupport.m @@ -13,7 +13,7 @@ @implementation UISlider (RACSignalSupport) - (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue { - return [self rac_channelForControlEvents:UIControlEventValueChanged key:@rac_keypath(self.value) nilValue:nilValue]; + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@rac_keypath(self.value) nilValue:nilValue]; } @end diff --git a/ReactiveObjC/UIStepper+RACSignalSupport.m b/ReactiveObjC/UIStepper+RACSignalSupport.m index 1e35bf15a..2175d6149 100644 --- a/ReactiveObjC/UIStepper+RACSignalSupport.m +++ b/ReactiveObjC/UIStepper+RACSignalSupport.m @@ -13,7 +13,7 @@ @implementation UIStepper (RACSignalSupport) - (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue { - return [self rac_channelForControlEvents:UIControlEventValueChanged key:@rac_keypath(self.value) nilValue:nilValue]; + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@rac_keypath(self.value) nilValue:nilValue]; } @end diff --git a/ReactiveObjC/UISwitch+RACSignalSupport.m b/ReactiveObjC/UISwitch+RACSignalSupport.m index 46b9a9db8..bcd858c0d 100644 --- a/ReactiveObjC/UISwitch+RACSignalSupport.m +++ b/ReactiveObjC/UISwitch+RACSignalSupport.m @@ -13,7 +13,7 @@ @implementation UISwitch (RACSignalSupport) - (RACChannelTerminal *)rac_newOnChannel { - return [self rac_channelForControlEvents:UIControlEventValueChanged key:@rac_keypath(self.on) nilValue:@NO]; + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@rac_keypath(self.on) nilValue:@NO]; } @end diff --git a/ReactiveObjC/UITableViewCell+RACSignalSupport.m b/ReactiveObjC/UITableViewCell+RACSignalSupport.m index 5cf9dfa44..873bc3556 100644 --- a/ReactiveObjC/UITableViewCell+RACSignalSupport.m +++ b/ReactiveObjC/UITableViewCell+RACSignalSupport.m @@ -16,16 +16,16 @@ @implementation UITableViewCell (RACSignalSupport) - (RACSignal *)rac_prepareForReuseSignal { - RACSignal *signal = objc_getAssociatedObject(self, _cmd); - if (signal != nil) return signal; + RACSignal *signal = objc_getAssociatedObject(self, _cmd); + if (signal != nil) return signal; - signal = [[[self - rac_signalForSelector:@selector(prepareForReuse)] - mapReplace:RACUnit.defaultUnit] - setNameWithFormat:@"%@ -rac_prepareForReuseSignal", RACDescription(self)]; - - objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - return signal; + signal = [[[self + rac_signalForSelector:@selector(prepareForReuse)] + mapReplace:RACUnit.defaultUnit] + setNameWithFormat:@"%@ -rac_prepareForReuseSignal", RACDescription(self)]; + + objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return signal; } @end diff --git a/ReactiveObjC/UITableViewHeaderFooterView+RACSignalSupport.m b/ReactiveObjC/UITableViewHeaderFooterView+RACSignalSupport.m index a0ffb436a..17c96cf0f 100644 --- a/ReactiveObjC/UITableViewHeaderFooterView+RACSignalSupport.m +++ b/ReactiveObjC/UITableViewHeaderFooterView+RACSignalSupport.m @@ -16,16 +16,16 @@ @implementation UITableViewHeaderFooterView (RACSignalSupport) - (RACSignal *)rac_prepareForReuseSignal { - RACSignal *signal = objc_getAssociatedObject(self, _cmd); - if (signal != nil) return signal; + RACSignal *signal = objc_getAssociatedObject(self, _cmd); + if (signal != nil) return signal; - signal = [[[self - rac_signalForSelector:@selector(prepareForReuse)] - mapReplace:RACUnit.defaultUnit] - setNameWithFormat:@"%@ -rac_prepareForReuseSignal", RACDescription(self)]; + signal = [[[self + rac_signalForSelector:@selector(prepareForReuse)] + mapReplace:RACUnit.defaultUnit] + setNameWithFormat:@"%@ -rac_prepareForReuseSignal", RACDescription(self)]; - objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - return signal; + objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return signal; } @end diff --git a/ReactiveObjC/UITextField+RACSignalSupport.m b/ReactiveObjC/UITextField+RACSignalSupport.m index 70a14270f..6cd2cc6c9 100644 --- a/ReactiveObjC/UITextField+RACSignalSupport.m +++ b/ReactiveObjC/UITextField+RACSignalSupport.m @@ -18,22 +18,22 @@ @implementation UITextField (RACSignalSupport) - (RACSignal *)rac_textSignal { - @rac_weakify(self); - return [[[[[RACSignal - defer:^{ - @rac_strongify(self); - return [RACSignal return:self]; - }] - concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]] - map:^(UITextField *x) { - return x.text; - }] - takeUntil:self.rac_willDeallocSignal] - setNameWithFormat:@"%@ -rac_textSignal", RACDescription(self)]; + @rac_weakify(self); + return [[[[[RACSignal + defer:^{ + @rac_strongify(self); + return [RACSignal return:self]; + }] + concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]] + map:^(UITextField *x) { + return x.text; + }] + takeUntil:self.rac_willDeallocSignal] + setNameWithFormat:@"%@ -rac_textSignal", RACDescription(self)]; } - (RACChannelTerminal *)rac_newTextChannel { - return [self rac_channelForControlEvents:UIControlEventAllEditingEvents key:@rac_keypath(self.text) nilValue:@""]; + return [self rac_channelForControlEvents:UIControlEventAllEditingEvents key:@rac_keypath(self.text) nilValue:@""]; } @end diff --git a/ReactiveObjC/UITextView+RACSignalSupport.m b/ReactiveObjC/UITextView+RACSignalSupport.m index 3a90fbee7..2dd1f317c 100644 --- a/ReactiveObjC/UITextView+RACSignalSupport.m +++ b/ReactiveObjC/UITextView+RACSignalSupport.m @@ -25,32 +25,32 @@ static void RACUseDelegateProxy(UITextView *self) { } - (RACDelegateProxy *)rac_delegateProxy { - RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); - if (proxy == nil) { - proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UITextViewDelegate)]; - objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } + RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); + if (proxy == nil) { + proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UITextViewDelegate)]; + objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } - return proxy; + return proxy; } - (RACSignal *)rac_textSignal { - @rac_weakify(self); - RACSignal *signal = [[[[[RACSignal - defer:^{ - @rac_strongify(self); - return [RACSignal return:RACTuplePack(self)]; - }] - concat:[self.rac_delegateProxy signalForSelector:@selector(textViewDidChange:)]] - reduceEach:^(UITextView *x) { - return x.text; - }] - takeUntil:self.rac_willDeallocSignal] - setNameWithFormat:@"%@ -rac_textSignal", RACDescription(self)]; - - RACUseDelegateProxy(self); - - return signal; + @rac_weakify(self); + RACSignal *signal = [[[[[RACSignal + defer:^{ + @rac_strongify(self); + return [RACSignal return:RACTuplePack(self)]; + }] + concat:[self.rac_delegateProxy signalForSelector:@selector(textViewDidChange:)]] + reduceEach:^(UITextView *x) { + return x.text; + }] + takeUntil:self.rac_willDeallocSignal] + setNameWithFormat:@"%@ -rac_textSignal", RACDescription(self)]; + + RACUseDelegateProxy(self); + + return signal; } @end diff --git a/ReactiveObjCTests/NSControllerRACSupportSpec.m b/ReactiveObjCTests/NSControllerRACSupportSpec.m index fe34bb89f..cf6127c4d 100644 --- a/ReactiveObjCTests/NSControllerRACSupportSpec.m +++ b/ReactiveObjCTests/NSControllerRACSupportSpec.m @@ -25,23 +25,23 @@ @implementation RACTestController QuickSpecBegin(NSControllerRACSupportSpec) qck_it(@"RACKVOChannel should support NSController", ^{ - RACTestController *a = [[RACTestController alloc] init]; - RACTestController *b = [[RACTestController alloc] init]; - RACChannelTo(a, object) = RACChannelTo(b, object); - expect(a.object).to(beNil()); - expect(b.object).to(beNil()); - - a.object = a; - expect(a.object).to(equal(a)); - expect(b.object).to(equal(a)); - - b.object = b; - expect(a.object).to(equal(b)); - expect(b.object).to(equal(b)); - - a.object = nil; - expect(a.object).to(beNil()); - expect(b.object).to(beNil()); + RACTestController *a = [[RACTestController alloc] init]; + RACTestController *b = [[RACTestController alloc] init]; + RACChannelTo(a, object) = RACChannelTo(b, object); + expect(a.object).to(beNil()); + expect(b.object).to(beNil()); + + a.object = a; + expect(a.object).to(equal(a)); + expect(b.object).to(equal(a)); + + b.object = b; + expect(a.object).to(equal(b)); + expect(b.object).to(equal(b)); + + a.object = nil; + expect(a.object).to(beNil()); + expect(b.object).to(beNil()); }); QuickSpecEnd diff --git a/ReactiveObjCTests/NSEnumeratorRACSequenceAdditionsSpec.m b/ReactiveObjCTests/NSEnumeratorRACSequenceAdditionsSpec.m index 211f88456..3e9235fc5 100644 --- a/ReactiveObjCTests/NSEnumeratorRACSequenceAdditionsSpec.m +++ b/ReactiveObjCTests/NSEnumeratorRACSequenceAdditionsSpec.m @@ -16,13 +16,13 @@ QuickSpecBegin(NSEnumeratorRACSequenceAdditionsSpec) qck_describe(@"-rac_sequence", ^{ - NSArray *values = @[ @0, @1, @2, @3, @4 ]; - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: values.objectEnumerator.rac_sequence, - RACSequenceExampleExpectedValues: values - }; - }); + NSArray *values = @[ @0, @1, @2, @3, @4 ]; + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: values.objectEnumerator.rac_sequence, + RACSequenceExampleExpectedValues: values + }; + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/NSNotificationCenterRACSupportSpec.m b/ReactiveObjCTests/NSNotificationCenterRACSupportSpec.m index af5f71a09..7f7add328 100644 --- a/ReactiveObjCTests/NSNotificationCenterRACSupportSpec.m +++ b/ReactiveObjCTests/NSNotificationCenterRACSupportSpec.m @@ -22,66 +22,66 @@ __block NSNotificationCenter *notificationCenter; qck_beforeEach(^{ - // The compiler gets confused and thinks you might be messaging - // NSDistributedNotificationCenter otherwise. Wtf? - notificationCenter = NSNotificationCenter.defaultCenter; + // The compiler gets confused and thinks you might be messaging + // NSDistributedNotificationCenter otherwise. Wtf? + notificationCenter = NSNotificationCenter.defaultCenter; }); qck_it(@"should send the notification when posted by any object", ^{ - RACSignal *signal = [notificationCenter rac_addObserverForName:TestNotification object:nil]; + RACSignal *signal = [notificationCenter rac_addObserverForName:TestNotification object:nil]; - __block NSUInteger count = 0; - [signal subscribeNext:^(NSNotification *notification) { - ++count; + __block NSUInteger count = 0; + [signal subscribeNext:^(NSNotification *notification) { + ++count; - expect(notification).to(beAKindOf(NSNotification.class)); - expect(notification.name).to(equal(TestNotification)); - }]; + expect(notification).to(beAKindOf(NSNotification.class)); + expect(notification.name).to(equal(TestNotification)); + }]; - expect(@(count)).to(equal(@0)); + expect(@(count)).to(equal(@0)); - [notificationCenter postNotificationName:TestNotification object:nil]; - expect(@(count)).to(equal(@1)); + [notificationCenter postNotificationName:TestNotification object:nil]; + expect(@(count)).to(equal(@1)); - [notificationCenter postNotificationName:TestNotification object:self]; - expect(@(count)).to(equal(@2)); + [notificationCenter postNotificationName:TestNotification object:self]; + expect(@(count)).to(equal(@2)); }); qck_it(@"should send the notification when posted by a specific object", ^{ - RACSignal *signal = [notificationCenter rac_addObserverForName:TestNotification object:self]; + RACSignal *signal = [notificationCenter rac_addObserverForName:TestNotification object:self]; - __block NSUInteger count = 0; - [signal subscribeNext:^(NSNotification *notification) { - ++count; + __block NSUInteger count = 0; + [signal subscribeNext:^(NSNotification *notification) { + ++count; - expect(notification).to(beAKindOf(NSNotification.class)); - expect(notification.name).to(equal(TestNotification)); - expect(notification.object).to(beIdenticalTo(self)); - }]; + expect(notification).to(beAKindOf(NSNotification.class)); + expect(notification.name).to(equal(TestNotification)); + expect(notification.object).to(beIdenticalTo(self)); + }]; - expect(@(count)).to(equal(@0)); + expect(@(count)).to(equal(@0)); - [notificationCenter postNotificationName:TestNotification object:nil]; - expect(@(count)).to(equal(@0)); + [notificationCenter postNotificationName:TestNotification object:nil]; + expect(@(count)).to(equal(@0)); - [notificationCenter postNotificationName:TestNotification object:self]; - expect(@(count)).to(equal(@1)); + [notificationCenter postNotificationName:TestNotification object:self]; + expect(@(count)).to(equal(@1)); }); qck_it(@"shouldn't strongly capture the notification object", ^{ - RACSignal *signal __attribute__((objc_precise_lifetime, unused)); + RACSignal *signal __attribute__((objc_precise_lifetime, unused)); - __block BOOL dealloced = NO; - @autoreleasepool { - NSObject *notificationObject = [[NSObject alloc] init]; - [notificationObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - dealloced = YES; - }]]; + __block BOOL dealloced = NO; + @autoreleasepool { + NSObject *notificationObject = [[NSObject alloc] init]; + [notificationObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + dealloced = YES; + }]]; - signal = [notificationCenter rac_addObserverForName:TestNotification object:notificationObject]; - } + signal = [notificationCenter rac_addObserverForName:TestNotification object:notificationObject]; + } - expect(@(dealloced)).to(beTruthy()); + expect(@(dealloced)).to(beTruthy()); }); QuickSpecEnd diff --git a/ReactiveObjCTests/NSObjectRACAppKitBindingsSpec.m b/ReactiveObjCTests/NSObjectRACAppKitBindingsSpec.m index 5c444849b..18a4c79ba 100644 --- a/ReactiveObjCTests/NSObjectRACAppKitBindingsSpec.m +++ b/ReactiveObjCTests/NSObjectRACAppKitBindingsSpec.m @@ -17,23 +17,23 @@ QuickSpecBegin(NSObjectRACAppKitBindingsSpec) qck_itBehavesLike(RACViewChannelExamples, ^{ - return @{ - RACViewChannelExampleCreateViewBlock: ^{ - return [[NSSlider alloc] initWithFrame:NSZeroRect]; - }, - RACViewChannelExampleCreateTerminalBlock: ^(NSSlider *view) { - return [view rac_channelToBinding:NSValueBinding]; - }, - RACViewChannelExampleKeyPath: @rac_keypath(NSSlider.new, objectValue), - RACViewChannelExampleSetViewValueBlock: ^(NSSlider *view, NSNumber *value) { - view.objectValue = value; + return @{ + RACViewChannelExampleCreateViewBlock: ^{ + return [[NSSlider alloc] initWithFrame:NSZeroRect]; + }, + RACViewChannelExampleCreateTerminalBlock: ^(NSSlider *view) { + return [view rac_channelToBinding:NSValueBinding]; + }, + RACViewChannelExampleKeyPath: @rac_keypath(NSSlider.new, objectValue), + RACViewChannelExampleSetViewValueBlock: ^(NSSlider *view, NSNumber *value) { + view.objectValue = value; - // Bindings don't actually trigger from programmatic modification. Do it - // manually. - NSDictionary *bindingInfo = [view infoForBinding:NSValueBinding]; - [bindingInfo[NSObservedObjectKey] setValue:value forKeyPath:bindingInfo[NSObservedKeyPathKey]]; - } - }; + // Bindings don't actually trigger from programmatic modification. Do it + // manually. + NSDictionary *bindingInfo = [view infoForBinding:NSValueBinding]; + [bindingInfo[NSObservedObjectKey] setValue:value forKeyPath:bindingInfo[NSObservedKeyPathKey]]; + } + }; }); QuickSpecEnd diff --git a/ReactiveObjCTests/NSObjectRACDeallocatingSpec.m b/ReactiveObjCTests/NSObjectRACDeallocatingSpec.m index 1f897df11..5d463e9d0 100644 --- a/ReactiveObjCTests/NSObjectRACDeallocatingSpec.m +++ b/ReactiveObjCTests/NSObjectRACDeallocatingSpec.m @@ -23,7 +23,7 @@ @interface RACDeallocSwizzlingTestClass : NSObject @implementation RACDeallocSwizzlingTestClass - (void)dealloc { - // Provide an empty implementation just so we can swizzle it. + // Provide an empty implementation just so we can swizzle it. } @end @@ -37,162 +37,162 @@ @implementation RACDeallocSwizzlingTestSubclass QuickSpecBegin(NSObjectRACDeallocatingSpec) qck_describe(@"-dealloc swizzling", ^{ - SEL selector = NSSelectorFromString(@"dealloc"); + SEL selector = NSSelectorFromString(@"dealloc"); - qck_it(@"should not invoke superclass -dealloc method twice", ^{ - __block NSUInteger superclassDeallocatedCount = 0; - __block BOOL subclassDeallocated = NO; + qck_it(@"should not invoke superclass -dealloc method twice", ^{ + __block NSUInteger superclassDeallocatedCount = 0; + __block BOOL subclassDeallocated = NO; - @autoreleasepool { - RACDeallocSwizzlingTestSubclass *object __attribute__((objc_precise_lifetime)) = [[RACDeallocSwizzlingTestSubclass alloc] init]; + @autoreleasepool { + RACDeallocSwizzlingTestSubclass *object __attribute__((objc_precise_lifetime)) = [[RACDeallocSwizzlingTestSubclass alloc] init]; - Method oldDeallocMethod = class_getInstanceMethod(RACDeallocSwizzlingTestClass.class, selector); - void (*oldDealloc)(id, SEL) = (__typeof__(oldDealloc))method_getImplementation(oldDeallocMethod); + Method oldDeallocMethod = class_getInstanceMethod(RACDeallocSwizzlingTestClass.class, selector); + void (*oldDealloc)(id, SEL) = (__typeof__(oldDealloc))method_getImplementation(oldDeallocMethod); - id newDealloc = ^(__unsafe_unretained id self) { - superclassDeallocatedCount++; - oldDealloc(self, selector); - }; + id newDealloc = ^(__unsafe_unretained id self) { + superclassDeallocatedCount++; + oldDealloc(self, selector); + }; - class_replaceMethod(RACDeallocSwizzlingTestClass.class, selector, imp_implementationWithBlock(newDealloc), method_getTypeEncoding(oldDeallocMethod)); + class_replaceMethod(RACDeallocSwizzlingTestClass.class, selector, imp_implementationWithBlock(newDealloc), method_getTypeEncoding(oldDeallocMethod)); - [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - subclassDeallocated = YES; - }]]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + subclassDeallocated = YES; + }]]; - expect(@(subclassDeallocated)).to(beFalsy()); - expect(@(superclassDeallocatedCount)).to(equal(@0)); - } + expect(@(subclassDeallocated)).to(beFalsy()); + expect(@(superclassDeallocatedCount)).to(equal(@0)); + } - expect(@(subclassDeallocated)).to(beTruthy()); - expect(@(superclassDeallocatedCount)).to(equal(@1)); - }); + expect(@(subclassDeallocated)).to(beTruthy()); + expect(@(superclassDeallocatedCount)).to(equal(@1)); + }); - qck_it(@"should invoke superclass -dealloc method swizzled in after the subclass", ^{ - __block BOOL superclassDeallocated = NO; - __block BOOL subclassDeallocated = NO; + qck_it(@"should invoke superclass -dealloc method swizzled in after the subclass", ^{ + __block BOOL superclassDeallocated = NO; + __block BOOL subclassDeallocated = NO; - @autoreleasepool { - RACDeallocSwizzlingTestSubclass *object __attribute__((objc_precise_lifetime)) = [[RACDeallocSwizzlingTestSubclass alloc] init]; - [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - subclassDeallocated = YES; - }]]; + @autoreleasepool { + RACDeallocSwizzlingTestSubclass *object __attribute__((objc_precise_lifetime)) = [[RACDeallocSwizzlingTestSubclass alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + subclassDeallocated = YES; + }]]; - Method oldDeallocMethod = class_getInstanceMethod(RACDeallocSwizzlingTestClass.class, selector); - void (*oldDealloc)(id, SEL) = (__typeof__(oldDealloc))method_getImplementation(oldDeallocMethod); + Method oldDeallocMethod = class_getInstanceMethod(RACDeallocSwizzlingTestClass.class, selector); + void (*oldDealloc)(id, SEL) = (__typeof__(oldDealloc))method_getImplementation(oldDeallocMethod); - id newDealloc = ^(__unsafe_unretained id self) { - superclassDeallocated = YES; - oldDealloc(self, selector); - }; + id newDealloc = ^(__unsafe_unretained id self) { + superclassDeallocated = YES; + oldDealloc(self, selector); + }; - class_replaceMethod(RACDeallocSwizzlingTestClass.class, selector, imp_implementationWithBlock(newDealloc), method_getTypeEncoding(oldDeallocMethod)); + class_replaceMethod(RACDeallocSwizzlingTestClass.class, selector, imp_implementationWithBlock(newDealloc), method_getTypeEncoding(oldDeallocMethod)); - expect(@(subclassDeallocated)).to(beFalsy()); - expect(@(superclassDeallocated)).to(beFalsy()); - } + expect(@(subclassDeallocated)).to(beFalsy()); + expect(@(superclassDeallocated)).to(beFalsy()); + } - expect(@(subclassDeallocated)).to(beTruthy()); - expect(@(superclassDeallocated)).to(beTruthy()); - }); + expect(@(subclassDeallocated)).to(beTruthy()); + expect(@(superclassDeallocated)).to(beTruthy()); + }); }); qck_describe(@"-rac_deallocDisposable", ^{ - qck_it(@"should dispose of the disposable when it is dealloc'd", ^{ - __block BOOL wasDisposed = NO; - @autoreleasepool { - NSObject *object __attribute__((objc_precise_lifetime)) = [[NSObject alloc] init]; - [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - wasDisposed = YES; - }]]; - - expect(@(wasDisposed)).to(beFalsy()); - } - - expect(@(wasDisposed)).to(beTruthy()); - }); - - qck_it(@"should be able to use the object during disposal", ^{ - @autoreleasepool { - RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; - - @autoreleasepool { - object.objectValue = [@"foo" mutableCopy]; - } - - __unsafe_unretained RACTestObject *weakObject = object; - - [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - expect(weakObject.objectValue).to(equal(@"foo")); - }]]; - } - }); + qck_it(@"should dispose of the disposable when it is dealloc'd", ^{ + __block BOOL wasDisposed = NO; + @autoreleasepool { + NSObject *object __attribute__((objc_precise_lifetime)) = [[NSObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + wasDisposed = YES; + }]]; + + expect(@(wasDisposed)).to(beFalsy()); + } + + expect(@(wasDisposed)).to(beTruthy()); + }); + + qck_it(@"should be able to use the object during disposal", ^{ + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + + @autoreleasepool { + object.objectValue = [@"foo" mutableCopy]; + } + + __unsafe_unretained RACTestObject *weakObject = object; + + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + expect(weakObject.objectValue).to(equal(@"foo")); + }]]; + } + }); }); qck_describe(@"-rac_willDeallocSignal", ^{ - qck_it(@"should complete on dealloc", ^{ - __block BOOL completed = NO; - @autoreleasepool { - [[[[RACTestObject alloc] init] rac_willDeallocSignal] subscribeCompleted:^{ - completed = YES; - }]; - } - - expect(@(completed)).to(beTruthy()); - }); - - qck_it(@"should not send anything", ^{ - __block BOOL valueReceived = NO; - __block BOOL completed = NO; - @autoreleasepool { - [[[[RACTestObject alloc] init] rac_willDeallocSignal] subscribeNext:^(id x) { - valueReceived = YES; - } completed:^{ - completed = YES; - }]; - } - - expect(@(valueReceived)).to(beFalsy()); - expect(@(completed)).to(beTruthy()); - }); - - qck_it(@"should complete upon subscription if already deallocated", ^{ - __block BOOL deallocated = NO; - - RACSignal *signal; - - @autoreleasepool { - RACTestObject *object = [[RACTestObject alloc] init]; - - signal = [object rac_willDeallocSignal]; - [signal subscribeCompleted:^{ - deallocated = YES; - }]; - } - - expect(@(deallocated)).to(beTruthy()); - expect(@([signal waitUntilCompleted:NULL])).to(beTruthy()); - }); - - qck_it(@"should complete before the object is invalid", ^{ - __block NSString *objectValue; - - @autoreleasepool { - RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; - - @autoreleasepool { - object.objectValue = [@"foo" mutableCopy]; - } - - __unsafe_unretained RACTestObject *weakObject = object; - - [[object rac_willDeallocSignal] subscribeCompleted:^{ - objectValue = [weakObject.objectValue copy]; - }]; - } - - expect(objectValue).to(equal(@"foo")); - }); + qck_it(@"should complete on dealloc", ^{ + __block BOOL completed = NO; + @autoreleasepool { + [[[[RACTestObject alloc] init] rac_willDeallocSignal] subscribeCompleted:^{ + completed = YES; + }]; + } + + expect(@(completed)).to(beTruthy()); + }); + + qck_it(@"should not send anything", ^{ + __block BOOL valueReceived = NO; + __block BOOL completed = NO; + @autoreleasepool { + [[[[RACTestObject alloc] init] rac_willDeallocSignal] subscribeNext:^(id x) { + valueReceived = YES; + } completed:^{ + completed = YES; + }]; + } + + expect(@(valueReceived)).to(beFalsy()); + expect(@(completed)).to(beTruthy()); + }); + + qck_it(@"should complete upon subscription if already deallocated", ^{ + __block BOOL deallocated = NO; + + RACSignal *signal; + + @autoreleasepool { + RACTestObject *object = [[RACTestObject alloc] init]; + + signal = [object rac_willDeallocSignal]; + [signal subscribeCompleted:^{ + deallocated = YES; + }]; + } + + expect(@(deallocated)).to(beTruthy()); + expect(@([signal waitUntilCompleted:NULL])).to(beTruthy()); + }); + + qck_it(@"should complete before the object is invalid", ^{ + __block NSString *objectValue; + + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + + @autoreleasepool { + object.objectValue = [@"foo" mutableCopy]; + } + + __unsafe_unretained RACTestObject *weakObject = object; + + [[object rac_willDeallocSignal] subscribeCompleted:^{ + objectValue = [weakObject.objectValue copy]; + }]; + } + + expect(objectValue).to(equal(@"foo")); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/NSObjectRACLiftingSpec.m b/ReactiveObjCTests/NSObjectRACLiftingSpec.m index 120d6b94a..c52b6a79d 100644 --- a/ReactiveObjCTests/NSObjectRACLiftingSpec.m +++ b/ReactiveObjCTests/NSObjectRACLiftingSpec.m @@ -23,388 +23,388 @@ QuickSpecBegin(NSObjectRACLiftingSpec) qck_describe(@"-rac_liftSelector:withSignals:", ^{ - __block RACTestObject *object; + __block RACTestObject *object; - qck_beforeEach(^{ - object = [[RACTestObject alloc] init]; - }); + qck_beforeEach(^{ + object = [[RACTestObject alloc] init]; + }); - qck_it(@"should call the selector with the value of the signal", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setObjectValue:) withSignals:subject, nil]; + qck_it(@"should call the selector with the value of the signal", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:) withSignals:subject, nil]; - expect(object.objectValue).to(beNil()); + expect(object.objectValue).to(beNil()); - [subject sendNext:@1]; - expect(object.objectValue).to(equal(@1)); + [subject sendNext:@1]; + expect(object.objectValue).to(equal(@1)); - [subject sendNext:@42]; - expect(object.objectValue).to(equal(@42)); - }); + [subject sendNext:@42]; + expect(object.objectValue).to(equal(@42)); + }); }); qck_describe(@"-rac_liftSelector:withSignalsFromArray:", ^{ - __block RACTestObject *object; + __block RACTestObject *object; - qck_beforeEach(^{ - object = [[RACTestObject alloc] init]; - }); + qck_beforeEach(^{ + object = [[RACTestObject alloc] init]; + }); - qck_it(@"should call the selector with the value of the signal", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should call the selector with the value of the signal", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; - expect(object.objectValue).to(beNil()); + expect(object.objectValue).to(beNil()); - [subject sendNext:@1]; - expect(object.objectValue).to(equal(@1)); + [subject sendNext:@1]; + expect(object.objectValue).to(equal(@1)); - [subject sendNext:@42]; - expect(object.objectValue).to(equal(@42)); - }); + [subject sendNext:@42]; + expect(object.objectValue).to(equal(@42)); + }); - qck_it(@"should call the selector with the value of the signal unboxed", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setIntegerValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should call the selector with the value of the signal unboxed", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setIntegerValue:) withSignalsFromArray:@[ subject ]]; - expect(@(object.integerValue)).to(equal(@0)); + expect(@(object.integerValue)).to(equal(@0)); - [subject sendNext:@1]; - expect(@(object.integerValue)).to(equal(@1)); + [subject sendNext:@1]; + expect(@(object.integerValue)).to(equal(@1)); - [subject sendNext:@42]; - expect(@(object.integerValue)).to(equal(@42)); - }); + [subject sendNext:@42]; + expect(@(object.integerValue)).to(equal(@42)); + }); - qck_it(@"should work with multiple arguments", ^{ - RACSubject *objectValueSubject = [RACSubject subject]; - RACSubject *integerValueSubject = [RACSubject subject]; - [object rac_liftSelector:@selector(setObjectValue:andIntegerValue:) withSignalsFromArray:@[ objectValueSubject, integerValueSubject ]]; + qck_it(@"should work with multiple arguments", ^{ + RACSubject *objectValueSubject = [RACSubject subject]; + RACSubject *integerValueSubject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:andIntegerValue:) withSignalsFromArray:@[ objectValueSubject, integerValueSubject ]]; - expect(@(object.hasInvokedSetObjectValueAndIntegerValue)).to(beFalsy()); - expect(object.objectValue).to(beNil()); - expect(@(object.integerValue)).to(equal(@0)); + expect(@(object.hasInvokedSetObjectValueAndIntegerValue)).to(beFalsy()); + expect(object.objectValue).to(beNil()); + expect(@(object.integerValue)).to(equal(@0)); - [objectValueSubject sendNext:@1]; - expect(@(object.hasInvokedSetObjectValueAndIntegerValue)).to(beFalsy()); - expect(object.objectValue).to(beNil()); - expect(@(object.integerValue)).to(equal(@0)); + [objectValueSubject sendNext:@1]; + expect(@(object.hasInvokedSetObjectValueAndIntegerValue)).to(beFalsy()); + expect(object.objectValue).to(beNil()); + expect(@(object.integerValue)).to(equal(@0)); - [integerValueSubject sendNext:@42]; - expect(@(object.hasInvokedSetObjectValueAndIntegerValue)).to(beTruthy()); - expect(object.objectValue).to(equal(@1)); - expect(@(object.integerValue)).to(equal(@42)); - }); + [integerValueSubject sendNext:@42]; + expect(@(object.hasInvokedSetObjectValueAndIntegerValue)).to(beTruthy()); + expect(object.objectValue).to(equal(@1)); + expect(@(object.integerValue)).to(equal(@42)); + }); - qck_it(@"should work with signals that immediately start with a value", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ [subject startWith:@42] ]]; + qck_it(@"should work with signals that immediately start with a value", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ [subject startWith:@42] ]]; - expect(object.objectValue).to(equal(@42)); + expect(object.objectValue).to(equal(@42)); - [subject sendNext:@1]; - expect(object.objectValue).to(equal(@1)); - }); + [subject sendNext:@1]; + expect(object.objectValue).to(equal(@1)); + }); - qck_it(@"should work with signals that send nil", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should work with signals that send nil", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; - [subject sendNext:nil]; - expect(object.objectValue).to(beNil()); + [subject sendNext:nil]; + expect(object.objectValue).to(beNil()); - [subject sendNext:RACTupleNil.tupleNil]; - expect(object.objectValue).to(beNil()); - }); + [subject sendNext:RACTupleNil.tupleNil]; + expect(object.objectValue).to(beNil()); + }); - qck_it(@"should work with integers", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setIntegerValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should work with integers", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setIntegerValue:) withSignalsFromArray:@[ subject ]]; - expect(@(object.integerValue)).to(equal(@0)); + expect(@(object.integerValue)).to(equal(@0)); - [subject sendNext:@1]; - expect(@(object.integerValue)).to(equal(@1)); - }); + [subject sendNext:@1]; + expect(@(object.integerValue)).to(equal(@1)); + }); - qck_it(@"should convert between numeric types", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setIntegerValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should convert between numeric types", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setIntegerValue:) withSignalsFromArray:@[ subject ]]; - expect(@(object.integerValue)).to(equal(@0)); + expect(@(object.integerValue)).to(equal(@0)); - [subject sendNext:@1.0]; - expect(@(object.integerValue)).to(equal(@1)); - }); + [subject sendNext:@1.0]; + expect(@(object.integerValue)).to(equal(@1)); + }); - qck_it(@"should work with class objects", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should work with class objects", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; - expect(object.objectValue).to(beNil()); + expect(object.objectValue).to(beNil()); - [subject sendNext:self.class]; - expect(object.objectValue).to(equal(self.class)); - }); + [subject sendNext:self.class]; + expect(object.objectValue).to(equal(self.class)); + }); - qck_it(@"should work for char pointer", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setCharPointerValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should work for char pointer", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setCharPointerValue:) withSignalsFromArray:@[ subject ]]; - expect(@((size_t)object.charPointerValue)).to(equal(@0)); + expect(@((size_t)object.charPointerValue)).to(equal(@0)); - NSString *string = @"blah blah blah"; - [subject sendNext:string]; - expect(@(object.charPointerValue)).to(equal(string)); - }); + NSString *string = @"blah blah blah"; + [subject sendNext:string]; + expect(@(object.charPointerValue)).to(equal(string)); + }); - qck_it(@"should work for const char pointer", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setConstCharPointerValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should work for const char pointer", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setConstCharPointerValue:) withSignalsFromArray:@[ subject ]]; - expect(@((size_t)object.constCharPointerValue)).to(equal(@0)); + expect(@((size_t)object.constCharPointerValue)).to(equal(@0)); - NSString *string = @"blah blah blah"; - [subject sendNext:string]; - expect(@(object.constCharPointerValue)).to(equal(string)); - }); + NSString *string = @"blah blah blah"; + [subject sendNext:string]; + expect(@(object.constCharPointerValue)).to(equal(string)); + }); - qck_it(@"should work for CGRect", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setRectValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should work for CGRect", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setRectValue:) withSignalsFromArray:@[ subject ]]; - expect(@(CGRectEqualToRect(object.rectValue, CGRectZero))).to(beTruthy()); + expect(@(CGRectEqualToRect(object.rectValue, CGRectZero))).to(beTruthy()); - CGRect value = CGRectMake(10, 20, 30, 40); - [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGRect)]]; - expect(@(CGRectEqualToRect(object.rectValue, value))).to(beTruthy()); - }); + CGRect value = CGRectMake(10, 20, 30, 40); + [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGRect)]]; + expect(@(CGRectEqualToRect(object.rectValue, value))).to(beTruthy()); + }); - qck_it(@"should work for CGSize", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setSizeValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should work for CGSize", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setSizeValue:) withSignalsFromArray:@[ subject ]]; - expect(@(CGSizeEqualToSize(object.sizeValue, CGSizeZero))).to(beTruthy()); + expect(@(CGSizeEqualToSize(object.sizeValue, CGSizeZero))).to(beTruthy()); - CGSize value = CGSizeMake(10, 20); - [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGSize)]]; - expect(@(CGSizeEqualToSize(object.sizeValue, value))).to(beTruthy()); - }); + CGSize value = CGSizeMake(10, 20); + [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGSize)]]; + expect(@(CGSizeEqualToSize(object.sizeValue, value))).to(beTruthy()); + }); - qck_it(@"should work for CGPoint", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setPointValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should work for CGPoint", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setPointValue:) withSignalsFromArray:@[ subject ]]; - expect(@(CGPointEqualToPoint(object.pointValue, CGPointZero))).to(beTruthy()); + expect(@(CGPointEqualToPoint(object.pointValue, CGPointZero))).to(beTruthy()); - CGPoint value = CGPointMake(10, 20); - [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGPoint)]]; - expect(@(CGPointEqualToPoint(object.pointValue, value))).to(beTruthy()); - }); + CGPoint value = CGPointMake(10, 20); + [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGPoint)]]; + expect(@(CGPointEqualToPoint(object.pointValue, value))).to(beTruthy()); + }); - qck_it(@"should work for NSRange", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setRangeValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should work for NSRange", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setRangeValue:) withSignalsFromArray:@[ subject ]]; - expect(@(NSEqualRanges(object.rangeValue, NSMakeRange(0, 0)))).to(beTruthy()); + expect(@(NSEqualRanges(object.rangeValue, NSMakeRange(0, 0)))).to(beTruthy()); - NSRange value = NSMakeRange(10, 20); - [subject sendNext:[NSValue valueWithRange:value]]; - expect(@(NSEqualRanges(object.rangeValue, value))).to(beTruthy()); - }); + NSRange value = NSMakeRange(10, 20); + [subject sendNext:[NSValue valueWithRange:value]]; + expect(@(NSEqualRanges(object.rangeValue, value))).to(beTruthy()); + }); - qck_it(@"should work for _Bool", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setC99BoolValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should work for _Bool", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setC99BoolValue:) withSignalsFromArray:@[ subject ]]; - expect(@(object.c99BoolValue)).to(beFalsy()); + expect(@(object.c99BoolValue)).to(beFalsy()); - _Bool value = true; - [subject sendNext:@(value)]; - expect(@(object.c99BoolValue)).to(beTruthy()); - }); + _Bool value = true; + [subject sendNext:@(value)]; + expect(@(object.c99BoolValue)).to(beTruthy()); + }); - qck_it(@"should work for primitive pointers", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(write5ToIntPointer:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should work for primitive pointers", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(write5ToIntPointer:) withSignalsFromArray:@[ subject ]]; - int value = 0; - int *valuePointer = &value; - expect(@(value)).to(equal(@0)); + int value = 0; + int *valuePointer = &value; + expect(@(value)).to(equal(@0)); - [subject sendNext:[NSValue valueWithPointer:valuePointer]]; - expect(@(value)).to(equal(@5)); - }); + [subject sendNext:[NSValue valueWithPointer:valuePointer]]; + expect(@(value)).to(equal(@5)); + }); - qck_it(@"should work for custom structs", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setStructValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should work for custom structs", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setStructValue:) withSignalsFromArray:@[ subject ]]; - expect(@(object.structValue.integerField)).to(equal(@0)); - expect(@(object.structValue.doubleField)).to(equal(@0.0)); + expect(@(object.structValue.integerField)).to(equal(@0)); + expect(@(object.structValue.doubleField)).to(equal(@0.0)); - RACTestStruct value = (RACTestStruct){7, 1.23}; - [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(typeof(value))]]; - expect(@(object.structValue.integerField)).to(equal(@(value.integerField))); - expect(@(object.structValue.doubleField)).to(equal(@(value.doubleField))); - }); + RACTestStruct value = (RACTestStruct){7, 1.23}; + [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(typeof(value))]]; + expect(@(object.structValue.integerField)).to(equal(@(value.integerField))); + expect(@(object.structValue.doubleField)).to(equal(@(value.doubleField))); + }); - qck_it(@"should send the latest value of the signal as the right argument", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setObjectValue:andIntegerValue:) withSignalsFromArray:@[ [RACSignal return:@"object"], subject ]]; - [subject sendNext:@1]; + qck_it(@"should send the latest value of the signal as the right argument", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:andIntegerValue:) withSignalsFromArray:@[ [RACSignal return:@"object"], subject ]]; + [subject sendNext:@1]; - expect(object.objectValue).to(equal(@"object")); - expect(@(object.integerValue)).to(equal(@1)); - }); + expect(object.objectValue).to(equal(@"object")); + expect(@(object.integerValue)).to(equal(@1)); + }); - qck_describe(@"the returned signal", ^{ - qck_it(@"should send the return value of the method invocation", ^{ - RACSubject *objectSubject = [RACSubject subject]; - RACSubject *integerSubject = [RACSubject subject]; - RACSignal *signal = [object rac_liftSelector:@selector(combineObjectValue:andIntegerValue:) withSignalsFromArray:@[ objectSubject, integerSubject ]]; + qck_describe(@"the returned signal", ^{ + qck_it(@"should send the return value of the method invocation", ^{ + RACSubject *objectSubject = [RACSubject subject]; + RACSubject *integerSubject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(combineObjectValue:andIntegerValue:) withSignalsFromArray:@[ objectSubject, integerSubject ]]; - __block NSString *result; - [signal subscribeNext:^(id x) { - result = x; - }]; + __block NSString *result; + [signal subscribeNext:^(id x) { + result = x; + }]; - [objectSubject sendNext:@"Magic number"]; - expect(result).to(beNil()); + [objectSubject sendNext:@"Magic number"]; + expect(result).to(beNil()); - [integerSubject sendNext:@42]; - expect(result).to(equal(@"Magic number: 42")); - }); + [integerSubject sendNext:@42]; + expect(result).to(equal(@"Magic number: 42")); + }); - qck_it(@"should send RACUnit.defaultUnit for void-returning methods", ^{ - RACSubject *subject = [RACSubject subject]; - RACSignal *signal = [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should send RACUnit.defaultUnit for void-returning methods", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; - __block id result; - [signal subscribeNext:^(id x) { - result = x; - }]; + __block id result; + [signal subscribeNext:^(id x) { + result = x; + }]; - [subject sendNext:@1]; + [subject sendNext:@1]; - expect(result).to(equal(RACUnit.defaultUnit)); - }); + expect(result).to(equal(RACUnit.defaultUnit)); + }); - qck_it(@"should support integer returning methods", ^{ - RACSubject *subject = [RACSubject subject]; - RACSignal *signal = [object rac_liftSelector:@selector(doubleInteger:) withSignalsFromArray:@[ subject ]]; + qck_it(@"should support integer returning methods", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(doubleInteger:) withSignalsFromArray:@[ subject ]]; - __block id result; - [signal subscribeNext:^(id x) { - result = x; - }]; - - [subject sendNext:@1]; - - expect(result).to(equal(@2)); - }); - - qck_it(@"should support char * returning methods", ^{ - RACSubject *subject = [RACSubject subject]; - RACSignal *signal = [object rac_liftSelector:@selector(doubleString:) withSignalsFromArray:@[ subject ]]; - - __block id result; - [signal subscribeNext:^(id x) { - result = x; - }]; - - [subject sendNext:@"test"]; - - expect(result).to(equal(@"testtest")); - }); - - qck_it(@"should support const char * returning methods", ^{ - RACSubject *subject = [RACSubject subject]; - RACSignal *signal = [object rac_liftSelector:@selector(doubleConstString:) withSignalsFromArray:@[ subject ]]; - - __block id result; - [signal subscribeNext:^(id x) { - result = x; - }]; - - [subject sendNext:@"test"]; - - expect(result).to(equal(@"testtest")); - }); - - qck_it(@"should support struct returning methods", ^{ - RACSubject *subject = [RACSubject subject]; - RACSignal *signal = [object rac_liftSelector:@selector(doubleStruct:) withSignalsFromArray:@[ subject ]]; - - __block NSValue *boxedResult; - [signal subscribeNext:^(id x) { - boxedResult = x; - }]; - - RACTestStruct value = {4, 12.3}; - NSValue *boxedValue = [NSValue valueWithBytes:&value objCType:@encode(typeof(value))]; - [subject sendNext:boxedValue]; - - RACTestStruct result = {0, 0.0}; - [boxedResult getValue:&result]; - expect(@(result.integerField)).to(equal(@8)); - expect(@(result.doubleField)).to(equal(@24.6)); - }); - - qck_it(@"should support block arguments and returns", ^{ - RACSubject *subject = [RACSubject subject]; - RACSignal *signal = [object rac_liftSelector:@selector(wrapBlock:) withSignalsFromArray:@[ subject ]]; - - __block BOOL blockInvoked = NO; - dispatch_block_t testBlock = ^{ - blockInvoked = YES; - }; - - __block dispatch_block_t result; - [signal subscribeNext:^(id x) { - result = x; - }]; - - [subject sendNext:testBlock]; - expect((id)result).notTo(beNil()); + __block id result; + [signal subscribeNext:^(id x) { + result = x; + }]; + + [subject sendNext:@1]; + + expect(result).to(equal(@2)); + }); + + qck_it(@"should support char * returning methods", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(doubleString:) withSignalsFromArray:@[ subject ]]; + + __block id result; + [signal subscribeNext:^(id x) { + result = x; + }]; + + [subject sendNext:@"test"]; + + expect(result).to(equal(@"testtest")); + }); + + qck_it(@"should support const char * returning methods", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(doubleConstString:) withSignalsFromArray:@[ subject ]]; + + __block id result; + [signal subscribeNext:^(id x) { + result = x; + }]; + + [subject sendNext:@"test"]; + + expect(result).to(equal(@"testtest")); + }); + + qck_it(@"should support struct returning methods", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(doubleStruct:) withSignalsFromArray:@[ subject ]]; + + __block NSValue *boxedResult; + [signal subscribeNext:^(id x) { + boxedResult = x; + }]; + + RACTestStruct value = {4, 12.3}; + NSValue *boxedValue = [NSValue valueWithBytes:&value objCType:@encode(typeof(value))]; + [subject sendNext:boxedValue]; + + RACTestStruct result = {0, 0.0}; + [boxedResult getValue:&result]; + expect(@(result.integerField)).to(equal(@8)); + expect(@(result.doubleField)).to(equal(@24.6)); + }); + + qck_it(@"should support block arguments and returns", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(wrapBlock:) withSignalsFromArray:@[ subject ]]; + + __block BOOL blockInvoked = NO; + dispatch_block_t testBlock = ^{ + blockInvoked = YES; + }; + + __block dispatch_block_t result; + [signal subscribeNext:^(id x) { + result = x; + }]; + + [subject sendNext:testBlock]; + expect((id)result).notTo(beNil()); - result(); - expect(@(blockInvoked)).to(beTruthy()); - }); + result(); + expect(@(blockInvoked)).to(beTruthy()); + }); - qck_it(@"should replay the last value", ^{ - RACSubject *objectSubject = [RACSubject subject]; - RACSubject *integerSubject = [RACSubject subject]; - RACSignal *signal = [object rac_liftSelector:@selector(combineObjectValue:andIntegerValue:) withSignalsFromArray:@[ objectSubject, integerSubject ]]; - - [objectSubject sendNext:@"Magic number"]; - [integerSubject sendNext:@42]; - [integerSubject sendNext:@43]; - - __block NSString *result; - [signal subscribeNext:^(id x) { - result = x; - }]; - - expect(result).to(equal(@"Magic number: 43")); - }); - }); - - qck_it(@"shouldn't strongly capture the receiver", ^{ - __block BOOL dealloced = NO; - @autoreleasepool { - RACTestObject *testObject __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; - [testObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - dealloced = YES; - }]]; - - RACSubject *subject = [RACSubject subject]; - [testObject rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; - [subject sendNext:@1]; - } - - expect(@(dealloced)).to(beTruthy()); - }); + qck_it(@"should replay the last value", ^{ + RACSubject *objectSubject = [RACSubject subject]; + RACSubject *integerSubject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(combineObjectValue:andIntegerValue:) withSignalsFromArray:@[ objectSubject, integerSubject ]]; + + [objectSubject sendNext:@"Magic number"]; + [integerSubject sendNext:@42]; + [integerSubject sendNext:@43]; + + __block NSString *result; + [signal subscribeNext:^(id x) { + result = x; + }]; + + expect(result).to(equal(@"Magic number: 43")); + }); + }); + + qck_it(@"shouldn't strongly capture the receiver", ^{ + __block BOOL dealloced = NO; + @autoreleasepool { + RACTestObject *testObject __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [testObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + dealloced = YES; + }]]; + + RACSubject *subject = [RACSubject subject]; + [testObject rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; + [subject sendNext:@1]; + } + + expect(@(dealloced)).to(beTruthy()); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/NSObjectRACPropertySubscribingExamples.m b/ReactiveObjCTests/NSObjectRACPropertySubscribingExamples.m index 8bb3b3881..0d9869a68 100644 --- a/ReactiveObjCTests/NSObjectRACPropertySubscribingExamples.m +++ b/ReactiveObjCTests/NSObjectRACPropertySubscribingExamples.m @@ -25,200 +25,200 @@ QuickConfigurationBegin(NSObjectRACPropertySubscribingExamples) + (void)configure:(Configuration *)configuration { - sharedExamples(RACPropertySubscribingExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block RACSignal *(^signalBlock)(RACTestObject *object, NSString *keyPath, id observer); - - qck_beforeEach(^{ - signalBlock = exampleContext()[RACPropertySubscribingExamplesSetupBlock]; - }); - - qck_it(@"should send the current value once on subscription", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; - RACSignal *signal = signalBlock(object, @rac_keypath(object, objectValue), self); - NSMutableArray *values = [NSMutableArray array]; - - object.objectValue = @0; - [signal subscribeNext:^(id x) { - [values addObject:x]; - }]; - - expect(values).to(equal((@[ @0 ]))); - }); - - qck_it(@"should send the new value when it changes", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; - RACSignal *signal = signalBlock(object, @rac_keypath(object, objectValue), self); - NSMutableArray *values = [NSMutableArray array]; - - object.objectValue = @0; - [signal subscribeNext:^(id x) { - [values addObject:x]; - }]; - - expect(values).to(equal((@[ @0 ]))); - - object.objectValue = @1; - expect(values).to(equal((@[ @0, @1 ]))); - - }); - - qck_it(@"should stop observing when disposed", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; - RACSignal *signal = signalBlock(object, @rac_keypath(object, objectValue), self); - NSMutableArray *values = [NSMutableArray array]; - - object.objectValue = @0; - RACDisposable *disposable = [signal subscribeNext:^(id x) { - [values addObject:x]; - }]; - - object.objectValue = @1; - NSArray *expected = @[ @0, @1 ]; - expect(values).to(equal(expected)); - - [disposable dispose]; - object.objectValue = @2; - expect(values).to(equal(expected)); - }); - - qck_it(@"shouldn't send any more values after the observer is gone", ^{ - __block BOOL observerDealloced = NO; - RACTestObject *object = [[RACTestObject alloc] init]; - NSMutableArray *values = [NSMutableArray array]; - @autoreleasepool { - RACTestObject *observer __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; - [observer.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - observerDealloced = YES; - }]]; - - RACSignal *signal = signalBlock(object, @rac_keypath(object, objectValue), observer); - object.objectValue = @1; - [signal subscribeNext:^(id x) { - [values addObject:x]; - }]; - } - - expect(@(observerDealloced)).to(beTruthy()); - - NSArray *expected = @[ @1 ]; - expect(values).to(equal(expected)); - - object.objectValue = @2; - expect(values).to(equal(expected)); - }); - - qck_it(@"shouldn't keep either object alive unnaturally long", ^{ - __block BOOL objectDealloced = NO; - __block BOOL scopeObjectDealloced = NO; - @autoreleasepool { - RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; - [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - objectDealloced = YES; - }]]; - RACTestObject *scopeObject __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; - [scopeObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - scopeObjectDealloced = YES; - }]]; - - RACSignal *signal = signalBlock(object, @rac_keypath(object, objectValue), scopeObject); - - [signal subscribeNext:^(id _) { - - }]; - } - - expect(@(objectDealloced)).to(beTruthy()); - expect(@(scopeObjectDealloced)).to(beTruthy()); - }); - - qck_it(@"shouldn't keep the signal alive past the lifetime of the object", ^{ - __block BOOL objectDealloced = NO; - __block BOOL signalDealloced = NO; - @autoreleasepool { - RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; - [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - objectDealloced = YES; - }]]; - - RACSignal *signal = [signalBlock(object, @rac_keypath(object, objectValue), self) map:^(id value) { - return value; - }]; - - [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - signalDealloced = YES; - }]]; - - [signal subscribeNext:^(id _) { - - }]; - } - - expect(@(signalDealloced)).toEventually(beTruthy()); - expect(@(objectDealloced)).to(beTruthy()); - }); - - qck_it(@"shouldn't crash when the value is changed on a different queue", ^{ - __block id value; - @autoreleasepool { - RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; - - RACSignal *signal = signalBlock(object, @rac_keypath(object, objectValue), self); - - [signal subscribeNext:^(id x) { - value = x; - }]; - - NSOperationQueue *queue = [[NSOperationQueue alloc] init]; - [queue addOperationWithBlock:^{ - object.objectValue = @1; - }]; - - [queue waitUntilAllOperationsAreFinished]; - } - - expect(value).toEventually(equal(@1)); - }); - - qck_describe(@"mutating collections", ^{ - __block RACTestObject *object; - __block NSMutableOrderedSet *lastValue; - __block NSMutableOrderedSet *proxySet; - - qck_beforeEach(^{ - object = [[RACTestObject alloc] init]; - object.objectValue = [NSMutableOrderedSet orderedSetWithObject:@1]; - - NSString *keyPath = @rac_keypath(object, objectValue); + sharedExamples(RACPropertySubscribingExamples, ^(QCKDSLSharedExampleContext exampleContext) { + __block RACSignal *(^signalBlock)(RACTestObject *object, NSString *keyPath, id observer); + + qck_beforeEach(^{ + signalBlock = exampleContext()[RACPropertySubscribingExamplesSetupBlock]; + }); + + qck_it(@"should send the current value once on subscription", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + RACSignal *signal = signalBlock(object, @rac_keypath(object, objectValue), self); + NSMutableArray *values = [NSMutableArray array]; + + object.objectValue = @0; + [signal subscribeNext:^(id x) { + [values addObject:x]; + }]; + + expect(values).to(equal((@[ @0 ]))); + }); + + qck_it(@"should send the new value when it changes", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + RACSignal *signal = signalBlock(object, @rac_keypath(object, objectValue), self); + NSMutableArray *values = [NSMutableArray array]; + + object.objectValue = @0; + [signal subscribeNext:^(id x) { + [values addObject:x]; + }]; + + expect(values).to(equal((@[ @0 ]))); + + object.objectValue = @1; + expect(values).to(equal((@[ @0, @1 ]))); + + }); + + qck_it(@"should stop observing when disposed", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + RACSignal *signal = signalBlock(object, @rac_keypath(object, objectValue), self); + NSMutableArray *values = [NSMutableArray array]; + + object.objectValue = @0; + RACDisposable *disposable = [signal subscribeNext:^(id x) { + [values addObject:x]; + }]; + + object.objectValue = @1; + NSArray *expected = @[ @0, @1 ]; + expect(values).to(equal(expected)); + + [disposable dispose]; + object.objectValue = @2; + expect(values).to(equal(expected)); + }); + + qck_it(@"shouldn't send any more values after the observer is gone", ^{ + __block BOOL observerDealloced = NO; + RACTestObject *object = [[RACTestObject alloc] init]; + NSMutableArray *values = [NSMutableArray array]; + @autoreleasepool { + RACTestObject *observer __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [observer.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + observerDealloced = YES; + }]]; + + RACSignal *signal = signalBlock(object, @rac_keypath(object, objectValue), observer); + object.objectValue = @1; + [signal subscribeNext:^(id x) { + [values addObject:x]; + }]; + } + + expect(@(observerDealloced)).to(beTruthy()); + + NSArray *expected = @[ @1 ]; + expect(values).to(equal(expected)); + + object.objectValue = @2; + expect(values).to(equal(expected)); + }); + + qck_it(@"shouldn't keep either object alive unnaturally long", ^{ + __block BOOL objectDealloced = NO; + __block BOOL scopeObjectDealloced = NO; + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + objectDealloced = YES; + }]]; + RACTestObject *scopeObject __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [scopeObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + scopeObjectDealloced = YES; + }]]; + + RACSignal *signal = signalBlock(object, @rac_keypath(object, objectValue), scopeObject); + + [signal subscribeNext:^(id _) { + + }]; + } + + expect(@(objectDealloced)).to(beTruthy()); + expect(@(scopeObjectDealloced)).to(beTruthy()); + }); + + qck_it(@"shouldn't keep the signal alive past the lifetime of the object", ^{ + __block BOOL objectDealloced = NO; + __block BOOL signalDealloced = NO; + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + objectDealloced = YES; + }]]; + + RACSignal *signal = [signalBlock(object, @rac_keypath(object, objectValue), self) map:^(id value) { + return value; + }]; + + [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + signalDealloced = YES; + }]]; + + [signal subscribeNext:^(id _) { + + }]; + } + + expect(@(signalDealloced)).toEventually(beTruthy()); + expect(@(objectDealloced)).to(beTruthy()); + }); + + qck_it(@"shouldn't crash when the value is changed on a different queue", ^{ + __block id value; + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + + RACSignal *signal = signalBlock(object, @rac_keypath(object, objectValue), self); + + [signal subscribeNext:^(id x) { + value = x; + }]; + + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + [queue addOperationWithBlock:^{ + object.objectValue = @1; + }]; + + [queue waitUntilAllOperationsAreFinished]; + } + + expect(value).toEventually(equal(@1)); + }); + + qck_describe(@"mutating collections", ^{ + __block RACTestObject *object; + __block NSMutableOrderedSet *lastValue; + __block NSMutableOrderedSet *proxySet; + + qck_beforeEach(^{ + object = [[RACTestObject alloc] init]; + object.objectValue = [NSMutableOrderedSet orderedSetWithObject:@1]; + + NSString *keyPath = @rac_keypath(object, objectValue); - [signalBlock(object, keyPath, self) subscribeNext:^(NSMutableOrderedSet *x) { - lastValue = x; - }]; + [signalBlock(object, keyPath, self) subscribeNext:^(NSMutableOrderedSet *x) { + lastValue = x; + }]; - proxySet = [object mutableOrderedSetValueForKey:keyPath]; - }); + proxySet = [object mutableOrderedSetValueForKey:keyPath]; + }); - qck_it(@"sends the newest object when inserting values into an observed object", ^{ - NSMutableOrderedSet *expected = [NSMutableOrderedSet orderedSetWithObjects: @1, @2, nil]; + qck_it(@"sends the newest object when inserting values into an observed object", ^{ + NSMutableOrderedSet *expected = [NSMutableOrderedSet orderedSetWithObjects: @1, @2, nil]; - [proxySet addObject:@2]; - expect(lastValue).to(equal(expected)); - }); - - qck_it(@"sends the newest object when removing values in an observed object", ^{ - NSMutableOrderedSet *expected = [NSMutableOrderedSet orderedSet]; + [proxySet addObject:@2]; + expect(lastValue).to(equal(expected)); + }); + + qck_it(@"sends the newest object when removing values in an observed object", ^{ + NSMutableOrderedSet *expected = [NSMutableOrderedSet orderedSet]; - [proxySet removeAllObjects]; - expect(lastValue).to(equal(expected)); - }); + [proxySet removeAllObjects]; + expect(lastValue).to(equal(expected)); + }); - qck_it(@"sends the newest object when replacing values in an observed object", ^{ - NSMutableOrderedSet *expected = [NSMutableOrderedSet orderedSetWithObjects: @2, nil]; + qck_it(@"sends the newest object when replacing values in an observed object", ^{ + NSMutableOrderedSet *expected = [NSMutableOrderedSet orderedSetWithObjects: @2, nil]; - [proxySet replaceObjectAtIndex:0 withObject:@2]; - expect(lastValue).to(equal(expected)); - }); - }); - }); + [proxySet replaceObjectAtIndex:0 withObject:@2]; + expect(lastValue).to(equal(expected)); + }); + }); + }); } QuickConfigurationEnd diff --git a/ReactiveObjCTests/NSObjectRACPropertySubscribingSpec.m b/ReactiveObjCTests/NSObjectRACPropertySubscribingSpec.m index 25ef4d4f0..6e635d498 100644 --- a/ReactiveObjCTests/NSObjectRACPropertySubscribingSpec.m +++ b/ReactiveObjCTests/NSObjectRACPropertySubscribingSpec.m @@ -19,140 +19,140 @@ QuickSpecBegin(NSObjectRACPropertySubscribingSpec) qck_describe(@"-rac_valuesForKeyPath:observer:", ^{ - id (^setupBlock)(id, id, id) = ^(RACTestObject *object, NSString *keyPath, id observer) { - return [object rac_valuesForKeyPath:keyPath observer:observer]; - }; + id (^setupBlock)(id, id, id) = ^(RACTestObject *object, NSString *keyPath, id observer) { + return [object rac_valuesForKeyPath:keyPath observer:observer]; + }; - qck_itBehavesLike(RACPropertySubscribingExamples, ^{ - return @{ RACPropertySubscribingExamplesSetupBlock: setupBlock }; - }); + qck_itBehavesLike(RACPropertySubscribingExamples, ^{ + return @{ RACPropertySubscribingExamplesSetupBlock: setupBlock }; + }); }); qck_describe(@"+rac_signalWithChangesFor:keyPath:options:observer:", ^{ - qck_describe(@"KVO options argument", ^{ - __block RACTestObject *object; - __block id actual; - __block RACSignal *(^objectValueSignal)(NSKeyValueObservingOptions); + qck_describe(@"KVO options argument", ^{ + __block RACTestObject *object; + __block id actual; + __block RACSignal *(^objectValueSignal)(NSKeyValueObservingOptions); - qck_beforeEach(^{ - object = [[RACTestObject alloc] init]; + qck_beforeEach(^{ + object = [[RACTestObject alloc] init]; - objectValueSignal = ^(NSKeyValueObservingOptions options) { - return [[object rac_valuesAndChangesForKeyPath:@rac_keypath(object, objectValue) options:options observer:self] reduceEach:^(id value, NSDictionary *change) { - return change; - }]; - }; - }); + objectValueSignal = ^(NSKeyValueObservingOptions options) { + return [[object rac_valuesAndChangesForKeyPath:@rac_keypath(object, objectValue) options:options observer:self] reduceEach:^(id value, NSDictionary *change) { + return change; + }]; + }; + }); - qck_it(@"sends a KVO dictionary", ^{ - [objectValueSignal(0) subscribeNext:^(NSDictionary *x) { - actual = x; - }]; + qck_it(@"sends a KVO dictionary", ^{ + [objectValueSignal(0) subscribeNext:^(NSDictionary *x) { + actual = x; + }]; - object.objectValue = @1; + object.objectValue = @1; - expect(actual).to(beAKindOf(NSDictionary.class)); - }); + expect(actual).to(beAKindOf(NSDictionary.class)); + }); - qck_it(@"sends a kind key by default", ^{ - [objectValueSignal(0) subscribeNext:^(NSDictionary *x) { - actual = x[NSKeyValueChangeKindKey]; - }]; + qck_it(@"sends a kind key by default", ^{ + [objectValueSignal(0) subscribeNext:^(NSDictionary *x) { + actual = x[NSKeyValueChangeKindKey]; + }]; - object.objectValue = @1; + object.objectValue = @1; - expect(actual).notTo(beNil()); - }); + expect(actual).notTo(beNil()); + }); - qck_it(@"sends the newest changes with NSKeyValueObservingOptionNew", ^{ - [objectValueSignal(NSKeyValueObservingOptionNew) subscribeNext:^(NSDictionary *x) { - actual = x[NSKeyValueChangeNewKey]; - }]; + qck_it(@"sends the newest changes with NSKeyValueObservingOptionNew", ^{ + [objectValueSignal(NSKeyValueObservingOptionNew) subscribeNext:^(NSDictionary *x) { + actual = x[NSKeyValueChangeNewKey]; + }]; - object.objectValue = @1; - expect(actual).to(equal(@1)); + object.objectValue = @1; + expect(actual).to(equal(@1)); - object.objectValue = @2; - expect(actual).to(equal(@2)); - }); + object.objectValue = @2; + expect(actual).to(equal(@2)); + }); - qck_it(@"sends an additional change value with NSKeyValueObservingOptionPrior", ^{ - NSMutableArray *values = [NSMutableArray new]; - NSArray *expected = @[ @(YES), @(NO) ]; + qck_it(@"sends an additional change value with NSKeyValueObservingOptionPrior", ^{ + NSMutableArray *values = [NSMutableArray new]; + NSArray *expected = @[ @(YES), @(NO) ]; - [objectValueSignal(NSKeyValueObservingOptionPrior) subscribeNext:^(NSDictionary *x) { - BOOL isPrior = [x[NSKeyValueChangeNotificationIsPriorKey] boolValue]; - [values addObject:@(isPrior)]; - }]; + [objectValueSignal(NSKeyValueObservingOptionPrior) subscribeNext:^(NSDictionary *x) { + BOOL isPrior = [x[NSKeyValueChangeNotificationIsPriorKey] boolValue]; + [values addObject:@(isPrior)]; + }]; - object.objectValue = @[ @1 ]; + object.objectValue = @[ @1 ]; - expect(values).to(equal(expected)); - }); + expect(values).to(equal(expected)); + }); - qck_it(@"sends index changes when adding, inserting or removing a value from an observed object", ^{ - __block NSUInteger hasIndexesCount = 0; + qck_it(@"sends index changes when adding, inserting or removing a value from an observed object", ^{ + __block NSUInteger hasIndexesCount = 0; - [objectValueSignal(0) subscribeNext:^(NSDictionary *x) { - if (x[NSKeyValueChangeIndexesKey] != nil) { - hasIndexesCount += 1; - } - }]; + [objectValueSignal(0) subscribeNext:^(NSDictionary *x) { + if (x[NSKeyValueChangeIndexesKey] != nil) { + hasIndexesCount += 1; + } + }]; - object.objectValue = [NSMutableOrderedSet orderedSet]; - expect(@(hasIndexesCount)).to(equal(@0)); + object.objectValue = [NSMutableOrderedSet orderedSet]; + expect(@(hasIndexesCount)).to(equal(@0)); - NSMutableOrderedSet *objectValue = [object mutableOrderedSetValueForKey:@"objectValue"]; + NSMutableOrderedSet *objectValue = [object mutableOrderedSetValueForKey:@"objectValue"]; - [objectValue addObject:@1]; - expect(@(hasIndexesCount)).to(equal(@1)); + [objectValue addObject:@1]; + expect(@(hasIndexesCount)).to(equal(@1)); - [objectValue replaceObjectAtIndex:0 withObject:@2]; - expect(@(hasIndexesCount)).to(equal(@2)); + [objectValue replaceObjectAtIndex:0 withObject:@2]; + expect(@(hasIndexesCount)).to(equal(@2)); - [objectValue removeObject:@2]; - expect(@(hasIndexesCount)).to(equal(@3)); - }); + [objectValue removeObject:@2]; + expect(@(hasIndexesCount)).to(equal(@3)); + }); - qck_it(@"sends the previous value with NSKeyValueObservingOptionOld", ^{ - [objectValueSignal(NSKeyValueObservingOptionOld) subscribeNext:^(NSDictionary *x) { - actual = x[NSKeyValueChangeOldKey]; - }]; + qck_it(@"sends the previous value with NSKeyValueObservingOptionOld", ^{ + [objectValueSignal(NSKeyValueObservingOptionOld) subscribeNext:^(NSDictionary *x) { + actual = x[NSKeyValueChangeOldKey]; + }]; - object.objectValue = @1; - expect(actual).to(equal(NSNull.null)); + object.objectValue = @1; + expect(actual).to(equal(NSNull.null)); - object.objectValue = @2; - expect(actual).to(equal(@1)); - }); + object.objectValue = @2; + expect(actual).to(equal(@1)); + }); - qck_it(@"sends the initial value with NSKeyValueObservingOptionInitial", ^{ - [objectValueSignal(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) subscribeNext:^(NSDictionary *x) { - actual = x[NSKeyValueChangeNewKey]; - }]; - - expect(actual).to(equal(NSNull.null)); - }); - }); + qck_it(@"sends the initial value with NSKeyValueObservingOptionInitial", ^{ + [objectValueSignal(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) subscribeNext:^(NSDictionary *x) { + actual = x[NSKeyValueChangeNewKey]; + }]; + + expect(actual).to(equal(NSNull.null)); + }); + }); }); qck_describe(@"-rac_valuesAndChangesForKeyPath:options:observer:", ^{ - qck_it(@"should complete immediately if the receiver or observer have deallocated", ^{ - RACSignal *signal; - @autoreleasepool { - RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; - RACTestObject *observer __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; - signal = [object rac_valuesAndChangesForKeyPath:@rac_keypath(object, stringValue) options:0 observer:observer]; - } - - __block BOOL completed = NO; - [signal subscribeCompleted:^{ - completed = YES; - }]; - - expect(@(completed)).to(beTruthy()); - }); + qck_it(@"should complete immediately if the receiver or observer have deallocated", ^{ + RACSignal *signal; + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + RACTestObject *observer __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + signal = [object rac_valuesAndChangesForKeyPath:@rac_keypath(object, stringValue) options:0 observer:observer]; + } + + __block BOOL completed = NO; + [signal subscribeCompleted:^{ + completed = YES; + }]; + + expect(@(completed)).to(beTruthy()); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m b/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m index 7c4afbaa0..b270c9575 100644 --- a/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m +++ b/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m @@ -37,465 +37,465 @@ - (id)objectValue; QuickSpecBegin(NSObjectRACSelectorSignalSpec) qck_describe(@"RACTestObject", ^{ - qck_it(@"should send the argument for each invocation", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; - __block id value; - [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { - value = x.first; - }]; - - [object lifeIsGood:@42]; - - expect(value).to(equal(@42)); - }); - - qck_it(@"should send completed on deallocation", ^{ - __block BOOL completed = NO; - __block BOOL deallocated = NO; - - @autoreleasepool { - RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; - - [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - deallocated = YES; - }]]; - - [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeCompleted:^{ - completed = YES; - }]; - - expect(@(deallocated)).to(beFalsy()); - expect(@(completed)).to(beFalsy()); - } - - expect(@(deallocated)).to(beTruthy()); - expect(@(completed)).to(beTruthy()); - }); - - qck_it(@"should send for a zero-argument method", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; - - __block RACTuple *value; - [[object rac_signalForSelector:@selector(objectValue)] subscribeNext:^(RACTuple *x) { - value = x; - }]; - - (void)[object objectValue]; - expect(value).to(equal([RACTuple tupleWithObjectsFromArray:@[]])); - }); - - qck_it(@"should send the argument for each invocation to the instance's own signal", ^{ - RACTestObject *object1 = [[RACTestObject alloc] init]; - __block id value1; - [[object1 rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { - value1 = x.first; - }]; + qck_it(@"should send the argument for each invocation", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + __block id value; + [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; + + [object lifeIsGood:@42]; + + expect(value).to(equal(@42)); + }); + + qck_it(@"should send completed on deallocation", ^{ + __block BOOL completed = NO; + __block BOOL deallocated = NO; + + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeCompleted:^{ + completed = YES; + }]; + + expect(@(deallocated)).to(beFalsy()); + expect(@(completed)).to(beFalsy()); + } + + expect(@(deallocated)).to(beTruthy()); + expect(@(completed)).to(beTruthy()); + }); + + qck_it(@"should send for a zero-argument method", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block RACTuple *value; + [[object rac_signalForSelector:@selector(objectValue)] subscribeNext:^(RACTuple *x) { + value = x; + }]; + + (void)[object objectValue]; + expect(value).to(equal([RACTuple tupleWithObjectsFromArray:@[]])); + }); + + qck_it(@"should send the argument for each invocation to the instance's own signal", ^{ + RACTestObject *object1 = [[RACTestObject alloc] init]; + __block id value1; + [[object1 rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { + value1 = x.first; + }]; - RACTestObject *object2 = [[RACTestObject alloc] init]; - __block id value2; - [[object2 rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { - value2 = x.first; - }]; + RACTestObject *object2 = [[RACTestObject alloc] init]; + __block id value2; + [[object2 rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { + value2 = x.first; + }]; - [object1 lifeIsGood:@42]; - [object2 lifeIsGood:@"Carpe diem"]; + [object1 lifeIsGood:@42]; + [object2 lifeIsGood:@"Carpe diem"]; - expect(value1).to(equal(@42)); - expect(value2).to(equal(@"Carpe diem")); - }); + expect(value1).to(equal(@42)); + expect(value2).to(equal(@"Carpe diem")); + }); - qck_it(@"should send multiple arguments for each invocation", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; + qck_it(@"should send multiple arguments for each invocation", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; - __block id value1; - __block id value2; - [[object rac_signalForSelector:@selector(combineObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) { - value1 = x.first; - value2 = x.second; - }]; + __block id value1; + __block id value2; + [[object rac_signalForSelector:@selector(combineObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) { + value1 = x.first; + value2 = x.second; + }]; - expect([object combineObjectValue:@42 andSecondObjectValue:@"foo"]).to(equal(@"42: foo")); - expect(value1).to(equal(@42)); - expect(value2).to(equal(@"foo")); - }); + expect([object combineObjectValue:@42 andSecondObjectValue:@"foo"]).to(equal(@"42: foo")); + expect(value1).to(equal(@42)); + expect(value2).to(equal(@"foo")); + }); - qck_it(@"should send arguments for invocation of non-existant methods", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; - __block id key; - __block id value; - [[object rac_signalForSelector:@selector(setObject:forKey:)] subscribeNext:^(RACTuple *x) { - value = x.first; - key = x.second; - }]; + qck_it(@"should send arguments for invocation of non-existant methods", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + __block id key; + __block id value; + [[object rac_signalForSelector:@selector(setObject:forKey:)] subscribeNext:^(RACTuple *x) { + value = x.first; + key = x.second; + }]; - [object performSelector:@selector(setObject:forKey:) withObject:@YES withObject:@"Winner"]; + [object performSelector:@selector(setObject:forKey:) withObject:@YES withObject:@"Winner"]; - expect(value).to(equal(@YES)); - expect(key).to(equal(@"Winner")); - }); + expect(value).to(equal(@YES)); + expect(key).to(equal(@"Winner")); + }); - qck_it(@"should send arguments for invocation and invoke the original method on previously KVO'd receiver", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; + qck_it(@"should send arguments for invocation and invoke the original method on previously KVO'd receiver", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; - [[RACObserve(object, objectValue) publish] connect]; + [[RACObserve(object, objectValue) publish] connect]; - __block id key; - __block id value; - [[object rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) { - value = x.first; - key = x.second; - }]; + __block id key; + __block id value; + [[object rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) { + value = x.first; + key = x.second; + }]; - [object setObjectValue:@YES andSecondObjectValue:@"Winner"]; + [object setObjectValue:@YES andSecondObjectValue:@"Winner"]; - expect(@(object.hasInvokedSetObjectValueAndSecondObjectValue)).to(beTruthy()); - expect(object.objectValue).to(equal(@YES)); - expect(object.secondObjectValue).to(equal(@"Winner")); + expect(@(object.hasInvokedSetObjectValueAndSecondObjectValue)).to(beTruthy()); + expect(object.objectValue).to(equal(@YES)); + expect(object.secondObjectValue).to(equal(@"Winner")); - expect(value).to(equal(@YES)); - expect(key).to(equal(@"Winner")); - }); + expect(value).to(equal(@YES)); + expect(key).to(equal(@"Winner")); + }); - qck_it(@"should send arguments for invocation and invoke the original method when receiver is subsequently KVO'd", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; + qck_it(@"should send arguments for invocation and invoke the original method when receiver is subsequently KVO'd", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; - __block id key; - __block id value; - [[object rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) { - value = x.first; - key = x.second; - }]; + __block id key; + __block id value; + [[object rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) { + value = x.first; + key = x.second; + }]; - [[RACObserve(object, objectValue) publish] connect]; + [[RACObserve(object, objectValue) publish] connect]; - [object setObjectValue:@YES andSecondObjectValue:@"Winner"]; + [object setObjectValue:@YES andSecondObjectValue:@"Winner"]; - expect(@(object.hasInvokedSetObjectValueAndSecondObjectValue)).to(beTruthy()); - expect(object.objectValue).to(equal(@YES)); - expect(object.secondObjectValue).to(equal(@"Winner")); + expect(@(object.hasInvokedSetObjectValueAndSecondObjectValue)).to(beTruthy()); + expect(object.objectValue).to(equal(@YES)); + expect(object.secondObjectValue).to(equal(@"Winner")); - expect(value).to(equal(@YES)); - expect(key).to(equal(@"Winner")); - }); + expect(value).to(equal(@YES)); + expect(key).to(equal(@"Winner")); + }); - qck_it(@"should properly implement -respondsToSelector: when called on KVO'd receiver", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; + qck_it(@"should properly implement -respondsToSelector: when called on KVO'd receiver", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; - // First, setup KVO on `object`, which gives us the desired side-effect - // of `object` taking on a KVO-custom subclass. - [[RACObserve(object, objectValue) publish] connect]; + // First, setup KVO on `object`, which gives us the desired side-effect + // of `object` taking on a KVO-custom subclass. + [[RACObserve(object, objectValue) publish] connect]; - SEL selector = NSSelectorFromString(@"anyOldSelector:"); + SEL selector = NSSelectorFromString(@"anyOldSelector:"); - // With the KVO subclass in place, call -rac_signalForSelector: to - // implement -anyOldSelector: directly on the KVO subclass. - [object rac_signalForSelector:selector]; + // With the KVO subclass in place, call -rac_signalForSelector: to + // implement -anyOldSelector: directly on the KVO subclass. + [object rac_signalForSelector:selector]; - expect(@([object respondsToSelector:selector])).to(beTruthy()); - }); + expect(@([object respondsToSelector:selector])).to(beTruthy()); + }); - qck_it(@"should properly implement -respondsToSelector: when called on signalForSelector'd receiver that has subsequently been KVO'd", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; + qck_it(@"should properly implement -respondsToSelector: when called on signalForSelector'd receiver that has subsequently been KVO'd", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; - SEL selector = NSSelectorFromString(@"anyOldSelector:"); + SEL selector = NSSelectorFromString(@"anyOldSelector:"); - // Implement -anyOldSelector: on the object first - [object rac_signalForSelector:selector]; + // Implement -anyOldSelector: on the object first + [object rac_signalForSelector:selector]; - expect(@([object respondsToSelector:selector])).to(beTruthy()); + expect(@([object respondsToSelector:selector])).to(beTruthy()); - // Then KVO the object - [[RACObserve(object, objectValue) publish] connect]; + // Then KVO the object + [[RACObserve(object, objectValue) publish] connect]; - expect(@([object respondsToSelector:selector])).to(beTruthy()); - }); + expect(@([object respondsToSelector:selector])).to(beTruthy()); + }); - qck_it(@"should properly implement -respondsToSelector: when called on signalForSelector'd receiver that has subsequently been KVO'd, then signalForSelector'd again", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; + qck_it(@"should properly implement -respondsToSelector: when called on signalForSelector'd receiver that has subsequently been KVO'd, then signalForSelector'd again", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; - SEL selector = NSSelectorFromString(@"anyOldSelector:"); + SEL selector = NSSelectorFromString(@"anyOldSelector:"); - // Implement -anyOldSelector: on the object first - [object rac_signalForSelector:selector]; + // Implement -anyOldSelector: on the object first + [object rac_signalForSelector:selector]; - expect(@([object respondsToSelector:selector])).to(beTruthy()); + expect(@([object respondsToSelector:selector])).to(beTruthy()); - // Then KVO the object - [[RACObserve(object, objectValue) publish] connect]; + // Then KVO the object + [[RACObserve(object, objectValue) publish] connect]; - expect(@([object respondsToSelector:selector])).to(beTruthy()); - - SEL selector2 = NSSelectorFromString(@"anotherSelector:"); + expect(@([object respondsToSelector:selector])).to(beTruthy()); + + SEL selector2 = NSSelectorFromString(@"anotherSelector:"); - // Then implement -anotherSelector: on the object - [object rac_signalForSelector:selector2]; + // Then implement -anotherSelector: on the object + [object rac_signalForSelector:selector2]; - expect(@([object respondsToSelector:selector2])).to(beTruthy()); - }); - - qck_it(@"should call the right signal for two instances of the same class, adding signals for the same selector", ^{ - RACTestObject *object1 = [[RACTestObject alloc] init]; - RACTestObject *object2 = [[RACTestObject alloc] init]; + expect(@([object respondsToSelector:selector2])).to(beTruthy()); + }); + + qck_it(@"should call the right signal for two instances of the same class, adding signals for the same selector", ^{ + RACTestObject *object1 = [[RACTestObject alloc] init]; + RACTestObject *object2 = [[RACTestObject alloc] init]; - SEL selector = NSSelectorFromString(@"lifeIsGood:"); + SEL selector = NSSelectorFromString(@"lifeIsGood:"); - __block id value1 = nil; - [[object1 rac_signalForSelector:selector] subscribeNext:^(RACTuple *x) { - value1 = x.first; - }]; + __block id value1 = nil; + [[object1 rac_signalForSelector:selector] subscribeNext:^(RACTuple *x) { + value1 = x.first; + }]; - __block id value2 = nil; - [[object2 rac_signalForSelector:selector] subscribeNext:^(RACTuple *x) { - value2 = x.first; - }]; + __block id value2 = nil; + [[object2 rac_signalForSelector:selector] subscribeNext:^(RACTuple *x) { + value2 = x.first; + }]; - [object1 lifeIsGood:@42]; - expect(value1).to(equal(@42)); - expect(value2).to(beNil()); + [object1 lifeIsGood:@42]; + expect(value1).to(equal(@42)); + expect(value2).to(beNil()); - [object2 lifeIsGood:@420]; - expect(value1).to(equal(@42)); - expect(value2).to(equal(@420)); - }); + [object2 lifeIsGood:@420]; + expect(value1).to(equal(@42)); + expect(value2).to(equal(@420)); + }); - qck_it(@"should properly implement -respondsToSelector: for optional method from a protocol", ^{ - // Selector for the targeted optional method from a protocol. - SEL selector = @selector(optionalProtocolMethodWithObjectValue:); + qck_it(@"should properly implement -respondsToSelector: for optional method from a protocol", ^{ + // Selector for the targeted optional method from a protocol. + SEL selector = @selector(optionalProtocolMethodWithObjectValue:); - RACTestObject *object1 = [[RACTestObject alloc] init]; + RACTestObject *object1 = [[RACTestObject alloc] init]; - // Method implementation of the selector is added to its swizzled class. - [object1 rac_signalForSelector:selector fromProtocol:@protocol(RACTestProtocol)]; + // Method implementation of the selector is added to its swizzled class. + [object1 rac_signalForSelector:selector fromProtocol:@protocol(RACTestProtocol)]; - expect(@([object1 respondsToSelector:selector])).to(beTruthy()); + expect(@([object1 respondsToSelector:selector])).to(beTruthy()); - RACTestObject *object2 = [[RACTestObject alloc] init]; + RACTestObject *object2 = [[RACTestObject alloc] init]; - // Call -rac_signalForSelector: to swizzle this instance's class, - // method implementations of -respondsToSelector: and - // -forwardInvocation:. - [object2 rac_signalForSelector:@selector(lifeIsGood:)]; + // Call -rac_signalForSelector: to swizzle this instance's class, + // method implementations of -respondsToSelector: and + // -forwardInvocation:. + [object2 rac_signalForSelector:@selector(lifeIsGood:)]; - // This instance should not respond to the selector because of not - // calling -rac_signalForSelector: with the selector. - expect(@([object2 respondsToSelector:selector])).to(beFalsy()); - }); + // This instance should not respond to the selector because of not + // calling -rac_signalForSelector: with the selector. + expect(@([object2 respondsToSelector:selector])).to(beFalsy()); + }); - qck_it(@"should send non-object arguments", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; + qck_it(@"should send non-object arguments", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; - __block id value; - [[object rac_signalForSelector:@selector(setIntegerValue:)] subscribeNext:^(RACTuple *x) { - value = x.first; - }]; + __block id value; + [[object rac_signalForSelector:@selector(setIntegerValue:)] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; - object.integerValue = 42; - expect(value).to(equal(@42)); - }); + object.integerValue = 42; + expect(value).to(equal(@42)); + }); - qck_it(@"should send on signal after the original method is invoked", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; + qck_it(@"should send on signal after the original method is invoked", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; - __block BOOL invokedMethodBefore = NO; - [[object rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) { - invokedMethodBefore = object.hasInvokedSetObjectValueAndSecondObjectValue; - }]; - - [object setObjectValue:@YES andSecondObjectValue:@"Winner"]; - expect(@(invokedMethodBefore)).to(beTruthy()); - }); + __block BOOL invokedMethodBefore = NO; + [[object rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) { + invokedMethodBefore = object.hasInvokedSetObjectValueAndSecondObjectValue; + }]; + + [object setObjectValue:@YES andSecondObjectValue:@"Winner"]; + expect(@(invokedMethodBefore)).to(beTruthy()); + }); }); qck_it(@"should swizzle an NSObject method", ^{ - NSObject *object = [[NSObject alloc] init]; + NSObject *object = [[NSObject alloc] init]; - __block RACTuple *value; - [[object rac_signalForSelector:@selector(description)] subscribeNext:^(RACTuple *x) { - value = x; - }]; + __block RACTuple *value; + [[object rac_signalForSelector:@selector(description)] subscribeNext:^(RACTuple *x) { + value = x; + }]; - expect([object description]).notTo(beNil()); - expect(value).to(equal([RACTuple tupleWithObjectsFromArray:@[]])); + expect([object description]).notTo(beNil()); + expect(value).to(equal([RACTuple tupleWithObjectsFromArray:@[]])); }); qck_describe(@"a class that already overrides -forwardInvocation:", ^{ - qck_it(@"should invoke the superclass' implementation", ^{ - RACSubclassObject *object = [[RACSubclassObject alloc] init]; + qck_it(@"should invoke the superclass' implementation", ^{ + RACSubclassObject *object = [[RACSubclassObject alloc] init]; - __block id value; - [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { - value = x.first; - }]; + __block id value; + [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; - [object lifeIsGood:@42]; - expect(value).to(equal(@42)); + [object lifeIsGood:@42]; + expect(value).to(equal(@42)); - expect(@((size_t)(void*)object.forwardedSelector)).to(equal(@0)); + expect(@((size_t)(void*)object.forwardedSelector)).to(equal(@0)); - [object performSelector:@selector(allObjects)]; + [object performSelector:@selector(allObjects)]; - expect(value).to(equal(@42)); - expect(NSStringFromSelector(object.forwardedSelector)).to(equal(@"allObjects")); - }); + expect(value).to(equal(@42)); + expect(NSStringFromSelector(object.forwardedSelector)).to(equal(@"allObjects")); + }); - qck_it(@"should not infinite recurse when KVO'd after RAC swizzled", ^{ - RACSubclassObject *object = [[RACSubclassObject alloc] init]; + qck_it(@"should not infinite recurse when KVO'd after RAC swizzled", ^{ + RACSubclassObject *object = [[RACSubclassObject alloc] init]; - __block id value; - [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { - value = x.first; - }]; + __block id value; + [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; - [[RACObserve(object, objectValue) publish] connect]; + [[RACObserve(object, objectValue) publish] connect]; - [object lifeIsGood:@42]; - expect(value).to(equal(@42)); + [object lifeIsGood:@42]; + expect(value).to(equal(@42)); - expect(@((size_t)(void*)object.forwardedSelector)).to(equal(@0)); - [object performSelector:@selector(allObjects)]; - expect(NSStringFromSelector(object.forwardedSelector)).to(equal(@"allObjects")); - }); + expect(@((size_t)(void*)object.forwardedSelector)).to(equal(@0)); + [object performSelector:@selector(allObjects)]; + expect(NSStringFromSelector(object.forwardedSelector)).to(equal(@"allObjects")); + }); }); qck_describe(@"two classes in the same hierarchy", ^{ - __block RACTestObject *superclassObj; - __block RACTuple *superclassTuple; + __block RACTestObject *superclassObj; + __block RACTuple *superclassTuple; - __block RACSubclassObject *subclassObj; - __block RACTuple *subclassTuple; + __block RACSubclassObject *subclassObj; + __block RACTuple *subclassTuple; - qck_beforeEach(^{ - superclassObj = [[RACTestObject alloc] init]; - expect(superclassObj).notTo(beNil()); + qck_beforeEach(^{ + superclassObj = [[RACTestObject alloc] init]; + expect(superclassObj).notTo(beNil()); - subclassObj = [[RACSubclassObject alloc] init]; - expect(subclassObj).notTo(beNil()); - }); + subclassObj = [[RACSubclassObject alloc] init]; + expect(subclassObj).notTo(beNil()); + }); - qck_it(@"should not collide", ^{ - [[superclassObj rac_signalForSelector:@selector(combineObjectValue:andIntegerValue:)] subscribeNext:^(RACTuple *t) { - superclassTuple = t; - }]; + qck_it(@"should not collide", ^{ + [[superclassObj rac_signalForSelector:@selector(combineObjectValue:andIntegerValue:)] subscribeNext:^(RACTuple *t) { + superclassTuple = t; + }]; - [[subclassObj rac_signalForSelector:@selector(combineObjectValue:andIntegerValue:)] subscribeNext:^(RACTuple *t) { - subclassTuple = t; - }]; + [[subclassObj rac_signalForSelector:@selector(combineObjectValue:andIntegerValue:)] subscribeNext:^(RACTuple *t) { + subclassTuple = t; + }]; - expect([superclassObj combineObjectValue:@"foo" andIntegerValue:42]).to(equal(@"foo: 42")); + expect([superclassObj combineObjectValue:@"foo" andIntegerValue:42]).to(equal(@"foo: 42")); - NSArray *expectedValues = @[ @"foo", @42 ]; - expect(superclassTuple.allObjects).to(equal(expectedValues)); + NSArray *expectedValues = @[ @"foo", @42 ]; + expect(superclassTuple.allObjects).to(equal(expectedValues)); - expect([subclassObj combineObjectValue:@"foo" andIntegerValue:42]).to(equal(@"fooSUBCLASS: 42")); + expect([subclassObj combineObjectValue:@"foo" andIntegerValue:42]).to(equal(@"fooSUBCLASS: 42")); - expectedValues = @[ @"foo", @42 ]; - expect(subclassTuple.allObjects).to(equal(expectedValues)); - }); + expectedValues = @[ @"foo", @42 ]; + expect(subclassTuple.allObjects).to(equal(expectedValues)); + }); - qck_it(@"should not collide when the superclass is invoked asynchronously", ^{ - [[superclassObj rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *t) { - superclassTuple = t; - }]; + qck_it(@"should not collide when the superclass is invoked asynchronously", ^{ + [[superclassObj rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *t) { + superclassTuple = t; + }]; - [[subclassObj rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *t) { - subclassTuple = t; - }]; + [[subclassObj rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *t) { + subclassTuple = t; + }]; - [superclassObj setObjectValue:@"foo" andSecondObjectValue:@"42"]; - expect(@(superclassObj.hasInvokedSetObjectValueAndSecondObjectValue)).to(beTruthy()); + [superclassObj setObjectValue:@"foo" andSecondObjectValue:@"42"]; + expect(@(superclassObj.hasInvokedSetObjectValueAndSecondObjectValue)).to(beTruthy()); - NSArray *expectedValues = @[ @"foo", @"42" ]; - expect(superclassTuple.allObjects).to(equal(expectedValues)); + NSArray *expectedValues = @[ @"foo", @"42" ]; + expect(superclassTuple.allObjects).to(equal(expectedValues)); - [subclassObj setObjectValue:@"foo" andSecondObjectValue:@"42"]; - expect(@(subclassObj.hasInvokedSetObjectValueAndSecondObjectValue)).to(beFalsy()); - expect(@(subclassObj.hasInvokedSetObjectValueAndSecondObjectValue)).toEventually(beTruthy()); + [subclassObj setObjectValue:@"foo" andSecondObjectValue:@"42"]; + expect(@(subclassObj.hasInvokedSetObjectValueAndSecondObjectValue)).to(beFalsy()); + expect(@(subclassObj.hasInvokedSetObjectValueAndSecondObjectValue)).toEventually(beTruthy()); - expectedValues = @[ @"foo", @"42" ]; - expect(subclassTuple.allObjects).to(equal(expectedValues)); - }); + expectedValues = @[ @"foo", @"42" ]; + expect(subclassTuple.allObjects).to(equal(expectedValues)); + }); }); qck_describe(@"-rac_signalForSelector:fromProtocol", ^{ - __block RACTestObject *object; - __block Protocol *protocol; - - qck_beforeEach(^{ - object = (id)[[RACTestObject alloc] init]; - expect(object).notTo(beNil()); - - protocol = @protocol(TestProtocol); - expect(protocol).notTo(beNil()); - }); - - qck_it(@"should not clobber a required method already implemented", ^{ - __block id value; - [[object rac_signalForSelector:@selector(lifeIsGood:) fromProtocol:protocol] subscribeNext:^(RACTuple *x) { - value = x.first; - }]; - - [object lifeIsGood:@42]; - expect(value).to(equal(@42)); - }); - - qck_it(@"should not clobber an optional method already implemented", ^{ - object.objectValue = @"foo"; - - __block id value; - [[object rac_signalForSelector:@selector(objectValue) fromProtocol:protocol] subscribeNext:^(RACTuple *x) { - value = x; - }]; - - expect([object objectValue]).to(equal(@"foo")); - expect(value).to(equal([RACTuple tupleWithObjectsFromArray:@[]])); - }); - - qck_it(@"should inject a required method", ^{ - __block id value; - [[object rac_signalForSelector:@selector(requiredMethod:) fromProtocol:protocol] subscribeNext:^(RACTuple *x) { - value = x.first; - }]; - - expect(@([object requiredMethod:42])).to(beFalsy()); - expect(value).to(equal(@42)); - }); - - qck_it(@"should inject an optional method", ^{ - __block id value; - [[object rac_signalForSelector:@selector(optionalMethodWithObject:flag:) fromProtocol:protocol] subscribeNext:^(RACTuple *x) { - value = x; - }]; - - expect(@([object optionalMethodWithObject:@"foo" flag:YES])).to(equal(@0)); - expect(value).to(equal(RACTuplePack(@"foo", @YES))); - }); + __block RACTestObject *object; + __block Protocol *protocol; + + qck_beforeEach(^{ + object = (id)[[RACTestObject alloc] init]; + expect(object).notTo(beNil()); + + protocol = @protocol(TestProtocol); + expect(protocol).notTo(beNil()); + }); + + qck_it(@"should not clobber a required method already implemented", ^{ + __block id value; + [[object rac_signalForSelector:@selector(lifeIsGood:) fromProtocol:protocol] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; + + [object lifeIsGood:@42]; + expect(value).to(equal(@42)); + }); + + qck_it(@"should not clobber an optional method already implemented", ^{ + object.objectValue = @"foo"; + + __block id value; + [[object rac_signalForSelector:@selector(objectValue) fromProtocol:protocol] subscribeNext:^(RACTuple *x) { + value = x; + }]; + + expect([object objectValue]).to(equal(@"foo")); + expect(value).to(equal([RACTuple tupleWithObjectsFromArray:@[]])); + }); + + qck_it(@"should inject a required method", ^{ + __block id value; + [[object rac_signalForSelector:@selector(requiredMethod:) fromProtocol:protocol] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; + + expect(@([object requiredMethod:42])).to(beFalsy()); + expect(value).to(equal(@42)); + }); + + qck_it(@"should inject an optional method", ^{ + __block id value; + [[object rac_signalForSelector:@selector(optionalMethodWithObject:flag:) fromProtocol:protocol] subscribeNext:^(RACTuple *x) { + value = x; + }]; + + expect(@([object optionalMethodWithObject:@"foo" flag:YES])).to(equal(@0)); + expect(value).to(equal(RACTuplePack(@"foo", @YES))); + }); }); qck_describe(@"class reporting", ^{ - __block RACTestObject *object; - __block Class originalClass; - - qck_beforeEach(^{ - object = [[RACTestObject alloc] init]; - originalClass = object.class; - }); - - qck_it(@"should report the original class", ^{ - [object rac_signalForSelector:@selector(lifeIsGood:)]; - expect(object.class).to(beIdenticalTo(originalClass)); - }); - - qck_it(@"should report the original class when it's KVO'd after dynamically subclassing", ^{ - [object rac_signalForSelector:@selector(lifeIsGood:)]; - [[RACObserve(object, objectValue) publish] connect]; - expect(object.class).to(beIdenticalTo(originalClass)); - }); - - qck_it(@"should report the original class when it's KVO'd before dynamically subclassing", ^{ - [[RACObserve(object, objectValue) publish] connect]; - [object rac_signalForSelector:@selector(lifeIsGood:)]; - expect(object.class).to(beIdenticalTo(originalClass)); - }); + __block RACTestObject *object; + __block Class originalClass; + + qck_beforeEach(^{ + object = [[RACTestObject alloc] init]; + originalClass = object.class; + }); + + qck_it(@"should report the original class", ^{ + [object rac_signalForSelector:@selector(lifeIsGood:)]; + expect(object.class).to(beIdenticalTo(originalClass)); + }); + + qck_it(@"should report the original class when it's KVO'd after dynamically subclassing", ^{ + [object rac_signalForSelector:@selector(lifeIsGood:)]; + [[RACObserve(object, objectValue) publish] connect]; + expect(object.class).to(beIdenticalTo(originalClass)); + }); + + qck_it(@"should report the original class when it's KVO'd before dynamically subclassing", ^{ + [[RACObserve(object, objectValue) publish] connect]; + [object rac_signalForSelector:@selector(lifeIsGood:)]; + expect(object.class).to(beIdenticalTo(originalClass)); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/NSStringRACKeyPathUtilitiesSpec.m b/ReactiveObjCTests/NSStringRACKeyPathUtilitiesSpec.m index 94ac15961..870b80a92 100644 --- a/ReactiveObjCTests/NSStringRACKeyPathUtilitiesSpec.m +++ b/ReactiveObjCTests/NSStringRACKeyPathUtilitiesSpec.m @@ -14,41 +14,41 @@ QuickSpecBegin(NSStringRACKeyPathUtilitiesSpec) qck_describe(@"-keyPathComponents", ^{ - qck_it(@"should return components in the key path", ^{ - expect(@"self.test.key.path".rac_keyPathComponents).to(equal((@[@"self", @"test", @"key", @"path"]))); - }); - - qck_it(@"should return nil if given an empty string", ^{ - expect(@"".rac_keyPathComponents).to(beNil()); - }); + qck_it(@"should return components in the key path", ^{ + expect(@"self.test.key.path".rac_keyPathComponents).to(equal((@[@"self", @"test", @"key", @"path"]))); + }); + + qck_it(@"should return nil if given an empty string", ^{ + expect(@"".rac_keyPathComponents).to(beNil()); + }); }); qck_describe(@"-keyPathByDeletingLastKeyPathComponent", ^{ - qck_it(@"should return the parent key path", ^{ - expect(@"grandparent.parent.child".rac_keyPathByDeletingLastKeyPathComponent).to(equal(@"grandparent.parent")); - }); - - qck_it(@"should return nil if given an empty string", ^{ - expect(@"".rac_keyPathByDeletingLastKeyPathComponent).to(beNil()); - }); - - qck_it(@"should return nil if given a key path with only one component", ^{ - expect(@"self".rac_keyPathByDeletingLastKeyPathComponent).to(beNil()); - }); + qck_it(@"should return the parent key path", ^{ + expect(@"grandparent.parent.child".rac_keyPathByDeletingLastKeyPathComponent).to(equal(@"grandparent.parent")); + }); + + qck_it(@"should return nil if given an empty string", ^{ + expect(@"".rac_keyPathByDeletingLastKeyPathComponent).to(beNil()); + }); + + qck_it(@"should return nil if given a key path with only one component", ^{ + expect(@"self".rac_keyPathByDeletingLastKeyPathComponent).to(beNil()); + }); }); qck_describe(@"-keyPathByDeletingFirstKeyPathComponent", ^{ - qck_it(@"should return the remaining key path", ^{ - expect(@"first.second.third".rac_keyPathByDeletingFirstKeyPathComponent).to(equal(@"second.third")); - }); - - qck_it(@"should return nil if given an empty string", ^{ - expect(@"".rac_keyPathByDeletingFirstKeyPathComponent).to(beNil()); - }); - - qck_it(@"should return nil if given a key path with only one component", ^{ - expect(@"self".rac_keyPathByDeletingFirstKeyPathComponent).to(beNil()); - }); + qck_it(@"should return the remaining key path", ^{ + expect(@"first.second.third".rac_keyPathByDeletingFirstKeyPathComponent).to(equal(@"second.third")); + }); + + qck_it(@"should return nil if given an empty string", ^{ + expect(@"".rac_keyPathByDeletingFirstKeyPathComponent).to(beNil()); + }); + + qck_it(@"should return nil if given a key path with only one component", ^{ + expect(@"self".rac_keyPathByDeletingFirstKeyPathComponent).to(beNil()); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/NSURLConnectionRACSupportSpec.m b/ReactiveObjCTests/NSURLConnectionRACSupportSpec.m index 84de95c92..344b5ca88 100644 --- a/ReactiveObjCTests/NSURLConnectionRACSupportSpec.m +++ b/ReactiveObjCTests/NSURLConnectionRACSupportSpec.m @@ -16,24 +16,24 @@ QuickSpecBegin(NSURLConnectionRACSupportSpec) qck_it(@"should fetch a JSON file", ^{ - NSURL *fileURL = [[NSBundle bundleForClass:self.class] URLForResource:@"test-data" withExtension:@"json"]; - expect(fileURL).notTo(beNil()); + NSURL *fileURL = [[NSBundle bundleForClass:self.class] URLForResource:@"test-data" withExtension:@"json"]; + expect(fileURL).notTo(beNil()); - NSURLRequest *request = [NSURLRequest requestWithURL:fileURL]; + NSURLRequest *request = [NSURLRequest requestWithURL:fileURL]; - BOOL success = NO; - NSError *error = nil; - RACTuple *result = [[NSURLConnection rac_sendAsynchronousRequest:request] firstOrDefault:nil success:&success error:&error]; - expect(@(success)).to(beTruthy()); - expect(error).to(beNil()); - expect(result).to(beAKindOf(RACTuple.class)); + BOOL success = NO; + NSError *error = nil; + RACTuple *result = [[NSURLConnection rac_sendAsynchronousRequest:request] firstOrDefault:nil success:&success error:&error]; + expect(@(success)).to(beTruthy()); + expect(error).to(beNil()); + expect(result).to(beAKindOf(RACTuple.class)); - NSURLResponse *response = result.first; - expect(response).to(beAKindOf(NSURLResponse.class)); + NSURLResponse *response = result.first; + expect(response).to(beAKindOf(NSURLResponse.class)); - NSData *data = result.second; - expect(data).to(beAKindOf(NSData.class)); - expect(data).to(equal([NSData dataWithContentsOfURL:fileURL])); + NSData *data = result.second; + expect(data).to(beAKindOf(NSData.class)); + expect(data).to(equal([NSData dataWithContentsOfURL:fileURL])); }); QuickSpecEnd diff --git a/ReactiveObjCTests/NSUserDefaultsRACSupportSpec.m b/ReactiveObjCTests/NSUserDefaultsRACSupportSpec.m index 3f93a3a2f..b536081e1 100644 --- a/ReactiveObjCTests/NSUserDefaultsRACSupportSpec.m +++ b/ReactiveObjCTests/NSUserDefaultsRACSupportSpec.m @@ -39,99 +39,99 @@ @implementation TestObserver __block TestObserver *observer = nil; qck_beforeEach(^{ - defaults = NSUserDefaults.standardUserDefaults; + defaults = NSUserDefaults.standardUserDefaults; - observer = [TestObserver new]; + observer = [TestObserver new]; }); qck_afterEach(^{ - [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecStringDefault]; - [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecBoolDefault]; + [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecStringDefault]; + [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecBoolDefault]; - observer = nil; + observer = nil; }); qck_it(@"should set defaults", ^{ - RACChannelTo(observer, string1) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; - RACChannelTo(observer, bool1, @NO) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecBoolDefault]; - - observer.string1 = @"A string"; - observer.bool1 = YES; - - expect([defaults objectForKey:NSUserDefaultsRACSupportSpecStringDefault]).toEventually(equal(@"A string")); - expect([defaults objectForKey:NSUserDefaultsRACSupportSpecBoolDefault]).toEventually(equal(@YES)); + RACChannelTo(observer, string1) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + RACChannelTo(observer, bool1, @NO) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecBoolDefault]; + + observer.string1 = @"A string"; + observer.bool1 = YES; + + expect([defaults objectForKey:NSUserDefaultsRACSupportSpecStringDefault]).toEventually(equal(@"A string")); + expect([defaults objectForKey:NSUserDefaultsRACSupportSpecBoolDefault]).toEventually(equal(@YES)); }); qck_it(@"should read defaults", ^{ - RACChannelTo(observer, string1) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; - RACChannelTo(observer, bool1, @NO) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecBoolDefault]; - - expect(observer.string1).to(beNil()); - expect(@(observer.bool1)).to(equal(@NO)); - - [defaults setObject:@"Another string" forKey:NSUserDefaultsRACSupportSpecStringDefault]; - [defaults setBool:YES forKey:NSUserDefaultsRACSupportSpecBoolDefault]; - - expect(observer.string1).to(equal(@"Another string")); - expect(@(observer.bool1)).to(equal(@YES)); + RACChannelTo(observer, string1) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + RACChannelTo(observer, bool1, @NO) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecBoolDefault]; + + expect(observer.string1).to(beNil()); + expect(@(observer.bool1)).to(equal(@NO)); + + [defaults setObject:@"Another string" forKey:NSUserDefaultsRACSupportSpecStringDefault]; + [defaults setBool:YES forKey:NSUserDefaultsRACSupportSpecBoolDefault]; + + expect(observer.string1).to(equal(@"Another string")); + expect(@(observer.bool1)).to(equal(@YES)); }); qck_it(@"should be okay to create 2 terminals", ^{ - RACChannelTo(observer, string1) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; - RACChannelTo(observer, string2) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; - - [defaults setObject:@"String 3" forKey:NSUserDefaultsRACSupportSpecStringDefault]; - - expect(observer.string1).to(equal(@"String 3")); - expect(observer.string2).to(equal(@"String 3")); + RACChannelTo(observer, string1) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + RACChannelTo(observer, string2) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + + [defaults setObject:@"String 3" forKey:NSUserDefaultsRACSupportSpecStringDefault]; + + expect(observer.string1).to(equal(@"String 3")); + expect(observer.string2).to(equal(@"String 3")); }); qck_it(@"should handle removed defaults", ^{ - observer.string1 = @"Some string"; - observer.bool1 = YES; - - RACChannelTo(observer, string1) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; - RACChannelTo(observer, bool1, @NO) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecBoolDefault]; - - [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecStringDefault]; - [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecBoolDefault]; - - expect(observer.string1).to(beNil()); - expect(@(observer.bool1)).to(equal(@NO)); + observer.string1 = @"Some string"; + observer.bool1 = YES; + + RACChannelTo(observer, string1) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + RACChannelTo(observer, bool1, @NO) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecBoolDefault]; + + [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecStringDefault]; + [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecBoolDefault]; + + expect(observer.string1).to(beNil()); + expect(@(observer.bool1)).to(equal(@NO)); }); qck_it(@"shouldn't resend values", ^{ - RACChannelTerminal *terminal = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; - - RACChannelTo(observer, string1) = terminal; - - RACSignal *sentValue = [terminal replayLast]; - observer.string1 = @"Test value"; - id value = [sentValue asynchronousFirstOrDefault:nil success:NULL error:NULL]; - expect(value).to(beNil()); + RACChannelTerminal *terminal = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + + RACChannelTo(observer, string1) = terminal; + + RACSignal *sentValue = [terminal replayLast]; + observer.string1 = @"Test value"; + id value = [sentValue asynchronousFirstOrDefault:nil success:NULL error:NULL]; + expect(value).to(beNil()); }); qck_it(@"should complete when the NSUserDefaults deallocates", ^{ - __block RACChannelTerminal *terminal; - __block BOOL deallocated = NO; - - @autoreleasepool { - NSUserDefaults *customDefaults __attribute__((objc_precise_lifetime)) = [NSUserDefaults new]; - [customDefaults.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - deallocated = YES; - }]]; - - terminal = [customDefaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; - } - - expect(@(deallocated)).to(beTruthy()); - expect(@([terminal asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); + __block RACChannelTerminal *terminal; + __block BOOL deallocated = NO; + + @autoreleasepool { + NSUserDefaults *customDefaults __attribute__((objc_precise_lifetime)) = [NSUserDefaults new]; + [customDefaults.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + terminal = [customDefaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + } + + expect(@(deallocated)).to(beTruthy()); + expect(@([terminal asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); }); qck_it(@"should send an initial value", ^{ - [defaults setObject:@"Initial" forKey:NSUserDefaultsRACSupportSpecStringDefault]; - RACChannelTerminal *terminal = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; - expect([terminal asynchronousFirstOrDefault:nil success:NULL error:NULL]).to(equal(@"Initial")); + [defaults setObject:@"Initial" forKey:NSUserDefaultsRACSupportSpecStringDefault]; + RACChannelTerminal *terminal = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + expect([terminal asynchronousFirstOrDefault:nil success:NULL error:NULL]).to(equal(@"Initial")); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACBlockTrampolineSpec.m b/ReactiveObjCTests/RACBlockTrampolineSpec.m index 460703667..923fb6256 100644 --- a/ReactiveObjCTests/RACBlockTrampolineSpec.m +++ b/ReactiveObjCTests/RACBlockTrampolineSpec.m @@ -15,37 +15,37 @@ QuickSpecBegin(RACBlockTrampolineSpec) qck_it(@"should invoke the block with the given arguments", ^{ - __block NSString *stringArg; - __block NSNumber *numberArg; - id (^block)(NSString *, NSNumber *) = ^ id (NSString *string, NSNumber *number) { - stringArg = string; - numberArg = number; - return nil; - }; - - RACInvokeBlock(block, RACTuplePack(@"hi", @1)); - expect(stringArg).to(equal(@"hi")); - expect(numberArg).to(equal(@1)); + __block NSString *stringArg; + __block NSNumber *numberArg; + id (^block)(NSString *, NSNumber *) = ^ id (NSString *string, NSNumber *number) { + stringArg = string; + numberArg = number; + return nil; + }; + + RACInvokeBlock(block, RACTuplePack(@"hi", @1)); + expect(stringArg).to(equal(@"hi")); + expect(numberArg).to(equal(@1)); }); qck_it(@"should return the result of the block invocation", ^{ - NSString * (^block)(NSString *) = ^(NSString *string) { - return string.uppercaseString; - }; + NSString * (^block)(NSString *) = ^(NSString *string) { + return string.uppercaseString; + }; - NSString *result = RACInvokeBlock(block, RACTuplePack(@"hi")); - expect(result).to(equal(@"HI")); + NSString *result = RACInvokeBlock(block, RACTuplePack(@"hi")); + expect(result).to(equal(@"HI")); }); qck_it(@"should pass RACTupleNils as nil", ^{ - __block id arg; - id (^block)(id) = ^ id (id obj) { - arg = obj; - return nil; - }; - - RACInvokeBlock(block, RACTuplePack(nil)); - expect(arg).to(beNil()); + __block id arg; + id (^block)(id) = ^ id (id obj) { + arg = obj; + return nil; + }; + + RACInvokeBlock(block, RACTuplePack(nil)); + expect(arg).to(beNil()); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACChannelExamples.m b/ReactiveObjCTests/RACChannelExamples.m index cc8ec37d0..15898735d 100644 --- a/ReactiveObjCTests/RACChannelExamples.m +++ b/ReactiveObjCTests/RACChannelExamples.m @@ -30,270 +30,270 @@ QuickConfigurationBegin(RACChannelExampleGroups) + (void)configure:(Configuration *)configuration { - sharedExamples(RACChannelExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block RACChannel * (^getChannel)(void); - __block RACChannel *channel; - - id value1 = @"test value 1"; - id value2 = @"test value 2"; - id value3 = @"test value 3"; - NSArray *values = @[ value1, value2, value3 ]; - - qck_beforeEach(^{ - getChannel = exampleContext()[RACChannelExampleCreateBlock]; - channel = getChannel(); - }); - - qck_it(@"should not send any leadingTerminal value on subscription", ^{ - __block id receivedValue = nil; - - [channel.followingTerminal sendNext:value1]; - [channel.leadingTerminal subscribeNext:^(id x) { - receivedValue = x; - }]; - - expect(receivedValue).to(beNil()); - - [channel.followingTerminal sendNext:value2]; - expect(receivedValue).to(equal(value2)); - }); - - qck_it(@"should send the latest followingTerminal value on subscription", ^{ - __block id receivedValue = nil; - - [channel.leadingTerminal sendNext:value1]; - [[channel.followingTerminal take:1] subscribeNext:^(id x) { - receivedValue = x; - }]; - - expect(receivedValue).to(equal(value1)); - - [channel.leadingTerminal sendNext:value2]; - [[channel.followingTerminal take:1] subscribeNext:^(id x) { - receivedValue = x; - }]; - - expect(receivedValue).to(equal(value2)); - }); - - qck_it(@"should send leadingTerminal values as they change", ^{ - NSMutableArray *receivedValues = [NSMutableArray array]; - [channel.leadingTerminal subscribeNext:^(id x) { - [receivedValues addObject:x]; - }]; - - [channel.followingTerminal sendNext:value1]; - [channel.followingTerminal sendNext:value2]; - [channel.followingTerminal sendNext:value3]; - expect(receivedValues).to(equal(values)); - }); - - qck_it(@"should send followingTerminal values as they change", ^{ - [channel.leadingTerminal sendNext:value1]; - - NSMutableArray *receivedValues = [NSMutableArray array]; - [channel.followingTerminal subscribeNext:^(id x) { - [receivedValues addObject:x]; - }]; - - [channel.leadingTerminal sendNext:value2]; - [channel.leadingTerminal sendNext:value3]; - expect(receivedValues).to(equal(values)); - }); - - qck_it(@"should complete both signals when the leadingTerminal is completed", ^{ - __block BOOL completedLeft = NO; - [channel.leadingTerminal subscribeCompleted:^{ - completedLeft = YES; - }]; - - __block BOOL completedRight = NO; - [channel.followingTerminal subscribeCompleted:^{ - completedRight = YES; - }]; - - [channel.leadingTerminal sendCompleted]; - expect(@(completedLeft)).to(beTruthy()); - expect(@(completedRight)).to(beTruthy()); - }); - - qck_it(@"should complete both signals when the followingTerminal is completed", ^{ - __block BOOL completedLeft = NO; - [channel.leadingTerminal subscribeCompleted:^{ - completedLeft = YES; - }]; - - __block BOOL completedRight = NO; - [channel.followingTerminal subscribeCompleted:^{ - completedRight = YES; - }]; - - [channel.followingTerminal sendCompleted]; - expect(@(completedLeft)).to(beTruthy()); - expect(@(completedRight)).to(beTruthy()); - }); - - qck_it(@"should replay completion to new subscribers", ^{ - [channel.leadingTerminal sendCompleted]; - - __block BOOL completedLeft = NO; - [channel.leadingTerminal subscribeCompleted:^{ - completedLeft = YES; - }]; - - __block BOOL completedRight = NO; - [channel.followingTerminal subscribeCompleted:^{ - completedRight = YES; - }]; - - expect(@(completedLeft)).to(beTruthy()); - expect(@(completedRight)).to(beTruthy()); - }); - }); - - sharedExamples(RACViewChannelExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block NSString *keyPath; - __block NSObject * (^getView)(void); - __block RACChannelTerminal * (^getTerminal)(NSObject *); - __block void (^setViewValue)(NSObject *view, NSNumber *value); - - __block NSObject *testView; - __block RACChannelTerminal *endpoint; - - qck_beforeEach(^{ - keyPath = exampleContext()[RACViewChannelExampleKeyPath]; - getTerminal = exampleContext()[RACViewChannelExampleCreateTerminalBlock]; - getView = exampleContext()[RACViewChannelExampleCreateViewBlock]; - setViewValue = exampleContext()[RACViewChannelExampleSetViewValueBlock]; - - testView = getView(); - endpoint = getTerminal(testView); - }); - - qck_it(@"should not send changes made by the channel itself", ^{ - __block BOOL receivedNext = NO; - [endpoint subscribeNext:^(id x) { - receivedNext = YES; - }]; - - expect(@(receivedNext)).to(beFalsy()); - - [endpoint sendNext:@0.1]; - expect(@(receivedNext)).to(beFalsy()); - - [endpoint sendNext:@0.2]; - expect(@(receivedNext)).to(beFalsy()); - - [endpoint sendCompleted]; - expect(@(receivedNext)).to(beFalsy()); - }); - - qck_it(@"should not send progammatic changes made to the view", ^{ - __block BOOL receivedNext = NO; - [endpoint subscribeNext:^(id x) { - receivedNext = YES; - }]; - - expect(@(receivedNext)).to(beFalsy()); - - [testView setValue:@0.1 forKeyPath:keyPath]; - expect(@(receivedNext)).to(beFalsy()); - - [testView setValue:@0.2 forKeyPath:keyPath]; - expect(@(receivedNext)).to(beFalsy()); - }); - - qck_it(@"should not have a starting value", ^{ - __block BOOL receivedNext = NO; - [endpoint subscribeNext:^(id x) { - receivedNext = YES; - }]; - - expect(@(receivedNext)).to(beFalsy()); - }); - - qck_it(@"should send view changes", ^{ - __block NSString *received; - [endpoint subscribeNext:^(id x) { - received = x; - }]; - - setViewValue(testView, @0.1); - expect(received).to(equal(@0.1)); - - setViewValue(testView, @0.2); - expect(received).to(equal(@0.2)); - }); - - qck_it(@"should set values on the view", ^{ - [endpoint sendNext:@0.1]; - expect([testView valueForKeyPath:keyPath]).to(equal(@0.1)); - - [endpoint sendNext:@0.2]; - expect([testView valueForKeyPath:keyPath]).to(equal(@0.2)); - }); - - qck_it(@"should not echo changes back to the channel", ^{ - __block NSUInteger receivedCount = 0; - [endpoint subscribeNext:^(id _) { - receivedCount++; - }]; - - expect(@(receivedCount)).to(equal(@0)); - - [endpoint sendNext:@0.1]; - expect(@(receivedCount)).to(equal(@0)); - - setViewValue(testView, @0.2); - expect(@(receivedCount)).to(equal(@1)); - }); - - qck_it(@"should complete when the view deallocates", ^{ - __block BOOL deallocated = NO; - __block BOOL completed = NO; - - @autoreleasepool { - NSObject *view __attribute__((objc_precise_lifetime)) = getView(); - [view.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - deallocated = YES; - }]]; - - RACChannelTerminal *terminal = getTerminal(view); - [terminal subscribeCompleted:^{ - completed = YES; - }]; - - expect(@(deallocated)).to(beFalsy()); - expect(@(completed)).to(beFalsy()); - } - - expect(@(deallocated)).to(beTruthy()); - expect(@(completed)).to(beTruthy()); - }); - - qck_it(@"should deallocate after the view deallocates", ^{ - __block BOOL viewDeallocated = NO; - __block BOOL terminalDeallocated = NO; - - @autoreleasepool { - NSObject *view __attribute__((objc_precise_lifetime)) = getView(); - [view.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - viewDeallocated = YES; - }]]; - - RACChannelTerminal *terminal = getTerminal(view); - [terminal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - terminalDeallocated = YES; - }]]; - - expect(@(viewDeallocated)).to(beFalsy()); - expect(@(terminalDeallocated)).to(beFalsy()); - } - - expect(@(viewDeallocated)).to(beTruthy()); - expect(@(terminalDeallocated)).toEventually(beTruthy()); - }); - }); + sharedExamples(RACChannelExamples, ^(QCKDSLSharedExampleContext exampleContext) { + __block RACChannel * (^getChannel)(void); + __block RACChannel *channel; + + id value1 = @"test value 1"; + id value2 = @"test value 2"; + id value3 = @"test value 3"; + NSArray *values = @[ value1, value2, value3 ]; + + qck_beforeEach(^{ + getChannel = exampleContext()[RACChannelExampleCreateBlock]; + channel = getChannel(); + }); + + qck_it(@"should not send any leadingTerminal value on subscription", ^{ + __block id receivedValue = nil; + + [channel.followingTerminal sendNext:value1]; + [channel.leadingTerminal subscribeNext:^(id x) { + receivedValue = x; + }]; + + expect(receivedValue).to(beNil()); + + [channel.followingTerminal sendNext:value2]; + expect(receivedValue).to(equal(value2)); + }); + + qck_it(@"should send the latest followingTerminal value on subscription", ^{ + __block id receivedValue = nil; + + [channel.leadingTerminal sendNext:value1]; + [[channel.followingTerminal take:1] subscribeNext:^(id x) { + receivedValue = x; + }]; + + expect(receivedValue).to(equal(value1)); + + [channel.leadingTerminal sendNext:value2]; + [[channel.followingTerminal take:1] subscribeNext:^(id x) { + receivedValue = x; + }]; + + expect(receivedValue).to(equal(value2)); + }); + + qck_it(@"should send leadingTerminal values as they change", ^{ + NSMutableArray *receivedValues = [NSMutableArray array]; + [channel.leadingTerminal subscribeNext:^(id x) { + [receivedValues addObject:x]; + }]; + + [channel.followingTerminal sendNext:value1]; + [channel.followingTerminal sendNext:value2]; + [channel.followingTerminal sendNext:value3]; + expect(receivedValues).to(equal(values)); + }); + + qck_it(@"should send followingTerminal values as they change", ^{ + [channel.leadingTerminal sendNext:value1]; + + NSMutableArray *receivedValues = [NSMutableArray array]; + [channel.followingTerminal subscribeNext:^(id x) { + [receivedValues addObject:x]; + }]; + + [channel.leadingTerminal sendNext:value2]; + [channel.leadingTerminal sendNext:value3]; + expect(receivedValues).to(equal(values)); + }); + + qck_it(@"should complete both signals when the leadingTerminal is completed", ^{ + __block BOOL completedLeft = NO; + [channel.leadingTerminal subscribeCompleted:^{ + completedLeft = YES; + }]; + + __block BOOL completedRight = NO; + [channel.followingTerminal subscribeCompleted:^{ + completedRight = YES; + }]; + + [channel.leadingTerminal sendCompleted]; + expect(@(completedLeft)).to(beTruthy()); + expect(@(completedRight)).to(beTruthy()); + }); + + qck_it(@"should complete both signals when the followingTerminal is completed", ^{ + __block BOOL completedLeft = NO; + [channel.leadingTerminal subscribeCompleted:^{ + completedLeft = YES; + }]; + + __block BOOL completedRight = NO; + [channel.followingTerminal subscribeCompleted:^{ + completedRight = YES; + }]; + + [channel.followingTerminal sendCompleted]; + expect(@(completedLeft)).to(beTruthy()); + expect(@(completedRight)).to(beTruthy()); + }); + + qck_it(@"should replay completion to new subscribers", ^{ + [channel.leadingTerminal sendCompleted]; + + __block BOOL completedLeft = NO; + [channel.leadingTerminal subscribeCompleted:^{ + completedLeft = YES; + }]; + + __block BOOL completedRight = NO; + [channel.followingTerminal subscribeCompleted:^{ + completedRight = YES; + }]; + + expect(@(completedLeft)).to(beTruthy()); + expect(@(completedRight)).to(beTruthy()); + }); + }); + + sharedExamples(RACViewChannelExamples, ^(QCKDSLSharedExampleContext exampleContext) { + __block NSString *keyPath; + __block NSObject * (^getView)(void); + __block RACChannelTerminal * (^getTerminal)(NSObject *); + __block void (^setViewValue)(NSObject *view, NSNumber *value); + + __block NSObject *testView; + __block RACChannelTerminal *endpoint; + + qck_beforeEach(^{ + keyPath = exampleContext()[RACViewChannelExampleKeyPath]; + getTerminal = exampleContext()[RACViewChannelExampleCreateTerminalBlock]; + getView = exampleContext()[RACViewChannelExampleCreateViewBlock]; + setViewValue = exampleContext()[RACViewChannelExampleSetViewValueBlock]; + + testView = getView(); + endpoint = getTerminal(testView); + }); + + qck_it(@"should not send changes made by the channel itself", ^{ + __block BOOL receivedNext = NO; + [endpoint subscribeNext:^(id x) { + receivedNext = YES; + }]; + + expect(@(receivedNext)).to(beFalsy()); + + [endpoint sendNext:@0.1]; + expect(@(receivedNext)).to(beFalsy()); + + [endpoint sendNext:@0.2]; + expect(@(receivedNext)).to(beFalsy()); + + [endpoint sendCompleted]; + expect(@(receivedNext)).to(beFalsy()); + }); + + qck_it(@"should not send progammatic changes made to the view", ^{ + __block BOOL receivedNext = NO; + [endpoint subscribeNext:^(id x) { + receivedNext = YES; + }]; + + expect(@(receivedNext)).to(beFalsy()); + + [testView setValue:@0.1 forKeyPath:keyPath]; + expect(@(receivedNext)).to(beFalsy()); + + [testView setValue:@0.2 forKeyPath:keyPath]; + expect(@(receivedNext)).to(beFalsy()); + }); + + qck_it(@"should not have a starting value", ^{ + __block BOOL receivedNext = NO; + [endpoint subscribeNext:^(id x) { + receivedNext = YES; + }]; + + expect(@(receivedNext)).to(beFalsy()); + }); + + qck_it(@"should send view changes", ^{ + __block NSString *received; + [endpoint subscribeNext:^(id x) { + received = x; + }]; + + setViewValue(testView, @0.1); + expect(received).to(equal(@0.1)); + + setViewValue(testView, @0.2); + expect(received).to(equal(@0.2)); + }); + + qck_it(@"should set values on the view", ^{ + [endpoint sendNext:@0.1]; + expect([testView valueForKeyPath:keyPath]).to(equal(@0.1)); + + [endpoint sendNext:@0.2]; + expect([testView valueForKeyPath:keyPath]).to(equal(@0.2)); + }); + + qck_it(@"should not echo changes back to the channel", ^{ + __block NSUInteger receivedCount = 0; + [endpoint subscribeNext:^(id _) { + receivedCount++; + }]; + + expect(@(receivedCount)).to(equal(@0)); + + [endpoint sendNext:@0.1]; + expect(@(receivedCount)).to(equal(@0)); + + setViewValue(testView, @0.2); + expect(@(receivedCount)).to(equal(@1)); + }); + + qck_it(@"should complete when the view deallocates", ^{ + __block BOOL deallocated = NO; + __block BOOL completed = NO; + + @autoreleasepool { + NSObject *view __attribute__((objc_precise_lifetime)) = getView(); + [view.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + RACChannelTerminal *terminal = getTerminal(view); + [terminal subscribeCompleted:^{ + completed = YES; + }]; + + expect(@(deallocated)).to(beFalsy()); + expect(@(completed)).to(beFalsy()); + } + + expect(@(deallocated)).to(beTruthy()); + expect(@(completed)).to(beTruthy()); + }); + + qck_it(@"should deallocate after the view deallocates", ^{ + __block BOOL viewDeallocated = NO; + __block BOOL terminalDeallocated = NO; + + @autoreleasepool { + NSObject *view __attribute__((objc_precise_lifetime)) = getView(); + [view.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + viewDeallocated = YES; + }]]; + + RACChannelTerminal *terminal = getTerminal(view); + [terminal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + terminalDeallocated = YES; + }]]; + + expect(@(viewDeallocated)).to(beFalsy()); + expect(@(terminalDeallocated)).to(beFalsy()); + } + + expect(@(viewDeallocated)).to(beTruthy()); + expect(@(terminalDeallocated)).toEventually(beTruthy()); + }); + }); } QuickConfigurationEnd diff --git a/ReactiveObjCTests/RACChannelSpec.m b/ReactiveObjCTests/RACChannelSpec.m index b4c17a20e..6ed302abf 100644 --- a/ReactiveObjCTests/RACChannelSpec.m +++ b/ReactiveObjCTests/RACChannelSpec.m @@ -20,57 +20,57 @@ QuickSpecBegin(RACChannelSpec) qck_describe(@"RACChannel", ^{ - qck_itBehavesLike(RACChannelExamples, ^{ - return @{ - RACChannelExampleCreateBlock: [^{ - return [[RACChannel alloc] init]; - } copy] - }; - }); - - qck_describe(@"memory management", ^{ - qck_it(@"should dealloc when its subscribers are disposed", ^{ - RACDisposable *leadingDisposable = nil; - RACDisposable *followingDisposable = nil; + qck_itBehavesLike(RACChannelExamples, ^{ + return @{ + RACChannelExampleCreateBlock: [^{ + return [[RACChannel alloc] init]; + } copy] + }; + }); + + qck_describe(@"memory management", ^{ + qck_it(@"should dealloc when its subscribers are disposed", ^{ + RACDisposable *leadingDisposable = nil; + RACDisposable *followingDisposable = nil; - __block BOOL deallocated = NO; + __block BOOL deallocated = NO; - @autoreleasepool { - RACChannel *channel __attribute__((objc_precise_lifetime)) = [[RACChannel alloc] init]; - [channel.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - deallocated = YES; - }]]; + @autoreleasepool { + RACChannel *channel __attribute__((objc_precise_lifetime)) = [[RACChannel alloc] init]; + [channel.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; - leadingDisposable = [channel.leadingTerminal subscribeCompleted:^{}]; - followingDisposable = [channel.followingTerminal subscribeCompleted:^{}]; - } + leadingDisposable = [channel.leadingTerminal subscribeCompleted:^{}]; + followingDisposable = [channel.followingTerminal subscribeCompleted:^{}]; + } - [leadingDisposable dispose]; - [followingDisposable dispose]; - expect(@(deallocated)).toEventually(beTruthy()); - }); - - qck_it(@"should dealloc when its subscriptions are disposed", ^{ - RACDisposable *leadingDisposable = nil; - RACDisposable *followingDisposable = nil; + [leadingDisposable dispose]; + [followingDisposable dispose]; + expect(@(deallocated)).toEventually(beTruthy()); + }); + + qck_it(@"should dealloc when its subscriptions are disposed", ^{ + RACDisposable *leadingDisposable = nil; + RACDisposable *followingDisposable = nil; - __block BOOL deallocated = NO; + __block BOOL deallocated = NO; - @autoreleasepool { - RACChannel *channel __attribute__((objc_precise_lifetime)) = [[RACChannel alloc] init]; - [channel.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - deallocated = YES; - }]]; + @autoreleasepool { + RACChannel *channel __attribute__((objc_precise_lifetime)) = [[RACChannel alloc] init]; + [channel.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; - leadingDisposable = [[RACSignal never] subscribe:channel.leadingTerminal]; - followingDisposable = [[RACSignal never] subscribe:channel.followingTerminal]; - } + leadingDisposable = [[RACSignal never] subscribe:channel.leadingTerminal]; + followingDisposable = [[RACSignal never] subscribe:channel.followingTerminal]; + } - [leadingDisposable dispose]; - [followingDisposable dispose]; - expect(@(deallocated)).toEventually(beTruthy()); - }); - }); + [leadingDisposable dispose]; + [followingDisposable dispose]; + expect(@(deallocated)).toEventually(beTruthy()); + }); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACCommandSpec.m b/ReactiveObjCTests/RACCommandSpec.m index 75f848531..e0136a91e 100644 --- a/ReactiveObjCTests/RACCommandSpec.m +++ b/ReactiveObjCTests/RACCommandSpec.m @@ -25,521 +25,521 @@ QuickSpecBegin(RACCommandSpec) RACSignal * (^emptySignalBlock)(id) = ^(id _) { - return [RACSignal empty]; + return [RACSignal empty]; }; qck_describe(@"with a simple signal block", ^{ - __block RACCommand *command; - - qck_beforeEach(^{ - command = [[RACCommand alloc] initWithSignalBlock:^(id value) { - return [RACSignal return:value]; - }]; - - expect(command).notTo(beNil()); - expect(@(command.allowsConcurrentExecution)).to(beFalsy()); - }); - - qck_it(@"should be enabled by default", ^{ - expect([command.enabled first]).to(equal(@YES)); - }); - - qck_it(@"should not be executing by default", ^{ - expect([command.executing first]).to(equal(@NO)); - }); - - qck_it(@"should create an execution signal", ^{ - __block NSUInteger signalsReceived = 0; - __block BOOL completed = NO; - - id value = NSNull.null; - [command.executionSignals subscribeNext:^(RACSignal *signal) { - signalsReceived++; - - [signal subscribeNext:^(id x) { - expect(x).to(equal(value)); - } completed:^{ - completed = YES; - }]; - }]; - - expect(@(signalsReceived)).to(equal(@0)); - - [command execute:value]; - expect(@(signalsReceived)).toEventually(equal(@1)); - expect(@(completed)).to(beTruthy()); - }); - - qck_it(@"should return the execution signal from -execute:", ^{ - __block BOOL completed = NO; - - id value = NSNull.null; - [[command - execute:value] - subscribeNext:^(id x) { - expect(x).to(equal(value)); - } completed:^{ - completed = YES; - }]; - - expect(@(completed)).toEventually(beTruthy()); - }); - - qck_it(@"should always send executionSignals on the main thread", ^{ - __block RACScheduler *receivedScheduler = nil; - [command.executionSignals subscribeNext:^(id _) { - receivedScheduler = RACScheduler.currentScheduler; - }]; - - [[RACScheduler scheduler] schedule:^{ - expect(@([[command execute:nil] waitUntilCompleted:NULL])).to(beTruthy()); - }]; - - expect(receivedScheduler).to(beNil()); - expect(receivedScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); - }); - - qck_it(@"should not send anything on 'errors' by default", ^{ - __block BOOL receivedError = NO; - [command.errors subscribeNext:^(id _) { - receivedError = YES; - }]; - - expect(@([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); - expect(@(receivedError)).to(beFalsy()); - }); - - qck_it(@"should be executing while an execution signal is running", ^{ - [command.executionSignals subscribeNext:^(RACSignal *signal) { - [signal subscribeNext:^(id x) { - expect([command.executing first]).to(equal(@YES)); - }]; - }]; - - expect(@([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); - expect([command.executing first]).to(equal(@NO)); - }); - - qck_it(@"should always update executing on the main thread", ^{ - __block RACScheduler *updatedScheduler = nil; - [[command.executing skip:1] subscribeNext:^(NSNumber *executing) { - if (!executing.boolValue) return; - - updatedScheduler = RACScheduler.currentScheduler; - }]; - - [[RACScheduler scheduler] schedule:^{ - expect(@([[command execute:nil] waitUntilCompleted:NULL])).to(beTruthy()); - }]; - - expect([command.executing first]).to(equal(@NO)); - expect(updatedScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); - }); - - qck_it(@"should dealloc without subscribers", ^{ - __block BOOL disposed = NO; - - @autoreleasepool { - RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithSignalBlock:emptySignalBlock]; - [command.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - disposed = YES; - }]]; - } - - expect(@(disposed)).toEventually(beTruthy()); - }); - - qck_it(@"should complete signals on the main thread when deallocated", ^{ - __block RACScheduler *executionSignalsScheduler = nil; - __block RACScheduler *executingScheduler = nil; - __block RACScheduler *enabledScheduler = nil; - __block RACScheduler *errorsScheduler = nil; - - [[RACScheduler scheduler] schedule:^{ - @autoreleasepool { - RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithSignalBlock:emptySignalBlock]; - - [command.executionSignals subscribeCompleted:^{ - executionSignalsScheduler = RACScheduler.currentScheduler; - }]; - - [command.executing subscribeCompleted:^{ - executingScheduler = RACScheduler.currentScheduler; - }]; - - [command.enabled subscribeCompleted:^{ - enabledScheduler = RACScheduler.currentScheduler; - }]; - - [command.errors subscribeCompleted:^{ - errorsScheduler = RACScheduler.currentScheduler; - }]; - } - }]; - - expect(executionSignalsScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); - expect(executingScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); - expect(enabledScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); - expect(errorsScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); - }); + __block RACCommand *command; + + qck_beforeEach(^{ + command = [[RACCommand alloc] initWithSignalBlock:^(id value) { + return [RACSignal return:value]; + }]; + + expect(command).notTo(beNil()); + expect(@(command.allowsConcurrentExecution)).to(beFalsy()); + }); + + qck_it(@"should be enabled by default", ^{ + expect([command.enabled first]).to(equal(@YES)); + }); + + qck_it(@"should not be executing by default", ^{ + expect([command.executing first]).to(equal(@NO)); + }); + + qck_it(@"should create an execution signal", ^{ + __block NSUInteger signalsReceived = 0; + __block BOOL completed = NO; + + id value = NSNull.null; + [command.executionSignals subscribeNext:^(RACSignal *signal) { + signalsReceived++; + + [signal subscribeNext:^(id x) { + expect(x).to(equal(value)); + } completed:^{ + completed = YES; + }]; + }]; + + expect(@(signalsReceived)).to(equal(@0)); + + [command execute:value]; + expect(@(signalsReceived)).toEventually(equal(@1)); + expect(@(completed)).to(beTruthy()); + }); + + qck_it(@"should return the execution signal from -execute:", ^{ + __block BOOL completed = NO; + + id value = NSNull.null; + [[command + execute:value] + subscribeNext:^(id x) { + expect(x).to(equal(value)); + } completed:^{ + completed = YES; + }]; + + expect(@(completed)).toEventually(beTruthy()); + }); + + qck_it(@"should always send executionSignals on the main thread", ^{ + __block RACScheduler *receivedScheduler = nil; + [command.executionSignals subscribeNext:^(id _) { + receivedScheduler = RACScheduler.currentScheduler; + }]; + + [[RACScheduler scheduler] schedule:^{ + expect(@([[command execute:nil] waitUntilCompleted:NULL])).to(beTruthy()); + }]; + + expect(receivedScheduler).to(beNil()); + expect(receivedScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); + }); + + qck_it(@"should not send anything on 'errors' by default", ^{ + __block BOOL receivedError = NO; + [command.errors subscribeNext:^(id _) { + receivedError = YES; + }]; + + expect(@([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); + expect(@(receivedError)).to(beFalsy()); + }); + + qck_it(@"should be executing while an execution signal is running", ^{ + [command.executionSignals subscribeNext:^(RACSignal *signal) { + [signal subscribeNext:^(id x) { + expect([command.executing first]).to(equal(@YES)); + }]; + }]; + + expect(@([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); + expect([command.executing first]).to(equal(@NO)); + }); + + qck_it(@"should always update executing on the main thread", ^{ + __block RACScheduler *updatedScheduler = nil; + [[command.executing skip:1] subscribeNext:^(NSNumber *executing) { + if (!executing.boolValue) return; + + updatedScheduler = RACScheduler.currentScheduler; + }]; + + [[RACScheduler scheduler] schedule:^{ + expect(@([[command execute:nil] waitUntilCompleted:NULL])).to(beTruthy()); + }]; + + expect([command.executing first]).to(equal(@NO)); + expect(updatedScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); + }); + + qck_it(@"should dealloc without subscribers", ^{ + __block BOOL disposed = NO; + + @autoreleasepool { + RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithSignalBlock:emptySignalBlock]; + [command.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + disposed = YES; + }]]; + } + + expect(@(disposed)).toEventually(beTruthy()); + }); + + qck_it(@"should complete signals on the main thread when deallocated", ^{ + __block RACScheduler *executionSignalsScheduler = nil; + __block RACScheduler *executingScheduler = nil; + __block RACScheduler *enabledScheduler = nil; + __block RACScheduler *errorsScheduler = nil; + + [[RACScheduler scheduler] schedule:^{ + @autoreleasepool { + RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithSignalBlock:emptySignalBlock]; + + [command.executionSignals subscribeCompleted:^{ + executionSignalsScheduler = RACScheduler.currentScheduler; + }]; + + [command.executing subscribeCompleted:^{ + executingScheduler = RACScheduler.currentScheduler; + }]; + + [command.enabled subscribeCompleted:^{ + enabledScheduler = RACScheduler.currentScheduler; + }]; + + [command.errors subscribeCompleted:^{ + errorsScheduler = RACScheduler.currentScheduler; + }]; + } + }]; + + expect(executionSignalsScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); + expect(executingScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); + expect(enabledScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); + expect(errorsScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); + }); }); qck_it(@"should invoke the signalBlock once per execution", ^{ - NSMutableArray *valuesReceived = [NSMutableArray array]; - RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id x) { - [valuesReceived addObject:x]; - return [RACSignal empty]; - }]; + NSMutableArray *valuesReceived = [NSMutableArray array]; + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id x) { + [valuesReceived addObject:x]; + return [RACSignal empty]; + }]; - expect(@([[command execute:@"foo"] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); - expect(valuesReceived).to(equal((@[ @"foo" ]))); + expect(@([[command execute:@"foo"] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); + expect(valuesReceived).to(equal((@[ @"foo" ]))); - expect(@([[command execute:@"bar"] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); - expect(valuesReceived).to(equal((@[ @"foo", @"bar" ]))); + expect(@([[command execute:@"bar"] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); + expect(valuesReceived).to(equal((@[ @"foo", @"bar" ]))); }); qck_it(@"should send on executionSignals in order of execution", ^{ - RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSequence *seq) { - return [seq signalWithScheduler:RACScheduler.immediateScheduler]; - }]; + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSequence *seq) { + return [seq signalWithScheduler:RACScheduler.immediateScheduler]; + }]; - NSMutableArray *valuesReceived = [NSMutableArray array]; - [[command.executionSignals - concat] - subscribeNext:^(id x) { - [valuesReceived addObject:x]; - }]; + NSMutableArray *valuesReceived = [NSMutableArray array]; + [[command.executionSignals + concat] + subscribeNext:^(id x) { + [valuesReceived addObject:x]; + }]; - RACSequence *first = @[ @"foo", @"bar" ].rac_sequence; - expect(@([[command execute:first] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); + RACSequence *first = @[ @"foo", @"bar" ].rac_sequence; + expect(@([[command execute:first] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); - RACSequence *second = @[ @"buzz", @"baz" ].rac_sequence; - expect(@([[command execute:second] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); + RACSequence *second = @[ @"buzz", @"baz" ].rac_sequence; + expect(@([[command execute:second] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); - NSArray *expectedValues = @[ @"foo", @"bar", @"buzz", @"baz" ]; - expect(valuesReceived).to(equal(expectedValues)); + NSArray *expectedValues = @[ @"foo", @"bar", @"buzz", @"baz" ]; + expect(valuesReceived).to(equal(expectedValues)); }); qck_it(@"should wait for all signals to complete or error before executing sends NO", ^{ - RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) { - return signal; - }]; + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) { + return signal; + }]; - command.allowsConcurrentExecution = YES; - - RACSubject *firstSubject = [RACSubject subject]; - expect([command execute:firstSubject]).notTo(beNil()); + command.allowsConcurrentExecution = YES; + + RACSubject *firstSubject = [RACSubject subject]; + expect([command execute:firstSubject]).notTo(beNil()); - RACSubject *secondSubject = [RACSubject subject]; - expect([command execute:secondSubject]).notTo(beNil()); + RACSubject *secondSubject = [RACSubject subject]; + expect([command execute:secondSubject]).notTo(beNil()); - expect([command.executing first]).toEventually(equal(@YES)); + expect([command.executing first]).toEventually(equal(@YES)); - [firstSubject sendError:nil]; - expect([command.executing first]).to(equal(@YES)); + [firstSubject sendError:nil]; + expect([command.executing first]).to(equal(@YES)); - [secondSubject sendNext:nil]; - expect([command.executing first]).to(equal(@YES)); + [secondSubject sendNext:nil]; + expect([command.executing first]).to(equal(@YES)); - [secondSubject sendCompleted]; - expect([command.executing first]).toEventually(equal(@NO)); + [secondSubject sendCompleted]; + expect([command.executing first]).toEventually(equal(@NO)); }); qck_it(@"should have allowsConcurrentExecution be observable", ^{ - RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) { - return signal; - }]; - - RACSubject *completion = [RACSubject subject]; - RACSignal *allowsConcurrentExecution = [[RACObserve(command, allowsConcurrentExecution) - takeUntil:completion] - replayLast]; - - command.allowsConcurrentExecution = YES; - - expect([allowsConcurrentExecution first]).to(beTrue()); - [completion sendCompleted]; + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) { + return signal; + }]; + + RACSubject *completion = [RACSubject subject]; + RACSignal *allowsConcurrentExecution = [[RACObserve(command, allowsConcurrentExecution) + takeUntil:completion] + replayLast]; + + command.allowsConcurrentExecution = YES; + + expect([allowsConcurrentExecution first]).to(beTrue()); + [completion sendCompleted]; }); qck_it(@"should not deliver errors from executionSignals", ^{ - RACSubject *subject = [RACSubject subject]; - NSMutableArray *receivedEvents = [NSMutableArray array]; + RACSubject *subject = [RACSubject subject]; + NSMutableArray *receivedEvents = [NSMutableArray array]; - RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) { - return subject; - }]; + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) { + return subject; + }]; - [[[command.executionSignals - flatten] - materialize] - subscribeNext:^(RACEvent *event) { - [receivedEvents addObject:event]; - }]; + [[[command.executionSignals + flatten] + materialize] + subscribeNext:^(RACEvent *event) { + [receivedEvents addObject:event]; + }]; - expect([command execute:nil]).notTo(beNil()); - expect([command.executing first]).toEventually(equal(@YES)); + expect([command execute:nil]).notTo(beNil()); + expect([command.executing first]).toEventually(equal(@YES)); - [subject sendNext:RACUnit.defaultUnit]; + [subject sendNext:RACUnit.defaultUnit]; - NSArray *expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit] ]; - expect(receivedEvents).toEventually(equal(expectedEvents)); - expect([command.executing first]).to(equal(@YES)); + NSArray *expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit] ]; + expect(receivedEvents).toEventually(equal(expectedEvents)); + expect([command.executing first]).to(equal(@YES)); - [subject sendNext:@"foo"]; + [subject sendNext:@"foo"]; - expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"] ]; - expect(receivedEvents).toEventually(equal(expectedEvents)); - expect([command.executing first]).to(equal(@YES)); + expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"] ]; + expect(receivedEvents).toEventually(equal(expectedEvents)); + expect([command.executing first]).to(equal(@YES)); - NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil]; - [subject sendError:error]; + NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil]; + [subject sendError:error]; - expect([command.executing first]).toEventually(equal(@NO)); - expect(receivedEvents).to(equal(expectedEvents)); + expect([command.executing first]).toEventually(equal(@NO)); + expect(receivedEvents).to(equal(expectedEvents)); }); qck_it(@"should deliver errors from -execute:", ^{ - RACSubject *subject = [RACSubject subject]; - NSMutableArray *receivedEvents = [NSMutableArray array]; + RACSubject *subject = [RACSubject subject]; + NSMutableArray *receivedEvents = [NSMutableArray array]; - RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) { - return subject; - }]; + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) { + return subject; + }]; - [[[command - execute:nil] - materialize] - subscribeNext:^(RACEvent *event) { - [receivedEvents addObject:event]; - }]; + [[[command + execute:nil] + materialize] + subscribeNext:^(RACEvent *event) { + [receivedEvents addObject:event]; + }]; - expect([command.executing first]).toEventually(equal(@YES)); + expect([command.executing first]).toEventually(equal(@YES)); - [subject sendNext:RACUnit.defaultUnit]; + [subject sendNext:RACUnit.defaultUnit]; - NSArray *expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit] ]; - expect(receivedEvents).toEventually(equal(expectedEvents)); - expect([command.executing first]).to(equal(@YES)); + NSArray *expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit] ]; + expect(receivedEvents).toEventually(equal(expectedEvents)); + expect([command.executing first]).to(equal(@YES)); - [subject sendNext:@"foo"]; + [subject sendNext:@"foo"]; - expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"] ]; - expect(receivedEvents).toEventually(equal(expectedEvents)); - expect([command.executing first]).to(equal(@YES)); + expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"] ]; + expect(receivedEvents).toEventually(equal(expectedEvents)); + expect([command.executing first]).to(equal(@YES)); - NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil]; - [subject sendError:error]; + NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil]; + [subject sendError:error]; - expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"], [RACEvent eventWithError:error] ]; - expect(receivedEvents).toEventually(equal(expectedEvents)); - expect([command.executing first]).toEventually(equal(@NO)); + expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"], [RACEvent eventWithError:error] ]; + expect(receivedEvents).toEventually(equal(expectedEvents)); + expect([command.executing first]).toEventually(equal(@NO)); }); qck_it(@"should deliver errors onto 'errors'", ^{ - RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) { - return signal; - }]; + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) { + return signal; + }]; - command.allowsConcurrentExecution = YES; - - RACSubject *firstSubject = [RACSubject subject]; - expect([command execute:firstSubject]).notTo(beNil()); + command.allowsConcurrentExecution = YES; + + RACSubject *firstSubject = [RACSubject subject]; + expect([command execute:firstSubject]).notTo(beNil()); - RACSubject *secondSubject = [RACSubject subject]; - expect([command execute:secondSubject]).notTo(beNil()); + RACSubject *secondSubject = [RACSubject subject]; + expect([command execute:secondSubject]).notTo(beNil()); - NSError *firstError = [NSError errorWithDomain:@"" code:1 userInfo:nil]; - NSError *secondError = [NSError errorWithDomain:@"" code:2 userInfo:nil]; - - // We should receive errors from our previously-started executions. - NSMutableArray *receivedErrors = [NSMutableArray array]; - [command.errors subscribeNext:^(NSError *error) { - [receivedErrors addObject:error]; - }]; + NSError *firstError = [NSError errorWithDomain:@"" code:1 userInfo:nil]; + NSError *secondError = [NSError errorWithDomain:@"" code:2 userInfo:nil]; + + // We should receive errors from our previously-started executions. + NSMutableArray *receivedErrors = [NSMutableArray array]; + [command.errors subscribeNext:^(NSError *error) { + [receivedErrors addObject:error]; + }]; - expect([command.executing first]).toEventually(equal(@YES)); + expect([command.executing first]).toEventually(equal(@YES)); - [firstSubject sendError:firstError]; - expect([command.executing first]).toEventually(equal(@YES)); + [firstSubject sendError:firstError]; + expect([command.executing first]).toEventually(equal(@YES)); - NSArray *expected = @[ firstError ]; - expect(receivedErrors).toEventually(equal(expected)); + NSArray *expected = @[ firstError ]; + expect(receivedErrors).toEventually(equal(expected)); - [secondSubject sendError:secondError]; - expect([command.executing first]).toEventually(equal(@NO)); + [secondSubject sendError:secondError]; + expect([command.executing first]).toEventually(equal(@NO)); - expected = @[ firstError, secondError ]; - expect(receivedErrors).toEventually(equal(expected)); + expected = @[ firstError, secondError ]; + expect(receivedErrors).toEventually(equal(expected)); }); qck_it(@"should not deliver non-error events onto 'errors'", ^{ - RACSubject *subject = [RACSubject subject]; - RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) { - return subject; - }]; + RACSubject *subject = [RACSubject subject]; + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) { + return subject; + }]; - __block BOOL receivedEvent = NO; - [command.errors subscribeNext:^(id _) { - receivedEvent = YES; - }]; + __block BOOL receivedEvent = NO; + [command.errors subscribeNext:^(id _) { + receivedEvent = YES; + }]; - expect([command execute:nil]).notTo(beNil()); - expect([command.executing first]).toEventually(equal(@YES)); + expect([command execute:nil]).notTo(beNil()); + expect([command.executing first]).toEventually(equal(@YES)); - [subject sendNext:RACUnit.defaultUnit]; - [subject sendCompleted]; + [subject sendNext:RACUnit.defaultUnit]; + [subject sendCompleted]; - expect([command.executing first]).toEventually(equal(@NO)); - expect(@(receivedEvent)).to(beFalsy()); + expect([command.executing first]).toEventually(equal(@NO)); + expect(@(receivedEvent)).to(beFalsy()); }); qck_it(@"should send errors on the main thread", ^{ - RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) { - return signal; - }]; + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) { + return signal; + }]; - NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil]; + NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil]; - __block RACScheduler *receivedScheduler = nil; - [command.errors subscribeNext:^(NSError *e) { - expect(e).to(equal(error)); - receivedScheduler = RACScheduler.currentScheduler; - }]; + __block RACScheduler *receivedScheduler = nil; + [command.errors subscribeNext:^(NSError *e) { + expect(e).to(equal(error)); + receivedScheduler = RACScheduler.currentScheduler; + }]; - RACSignal *errorSignal = [RACSignal error:error]; + RACSignal *errorSignal = [RACSignal error:error]; - [[RACScheduler scheduler] schedule:^{ - [command execute:errorSignal]; - }]; + [[RACScheduler scheduler] schedule:^{ + [command execute:errorSignal]; + }]; - expect(receivedScheduler).to(beNil()); - expect(receivedScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); + expect(receivedScheduler).to(beNil()); + expect(receivedScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); }); qck_describe(@"enabled signal", ^{ - __block RACSubject *enabledSubject; - __block RACCommand *command; - - qck_beforeEach(^{ - enabledSubject = [RACSubject subject]; - command = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:^(id _) { - return [RACSignal return:RACUnit.defaultUnit]; - }]; - }); - - qck_it(@"should send YES by default", ^{ - expect([command.enabled first]).to(equal(@YES)); - }); - - qck_it(@"should send whatever the enabledSignal has sent most recently", ^{ - [enabledSubject sendNext:@NO]; - expect([command.enabled first]).toEventually(equal(@NO)); - - [enabledSubject sendNext:@YES]; - expect([command.enabled first]).toEventually(equal(@YES)); - - [enabledSubject sendNext:@NO]; - expect([command.enabled first]).toEventually(equal(@NO)); - }); - - qck_it(@"should sample enabledSignal synchronously at initialization time", ^{ - RACCommand *command = [[RACCommand alloc] initWithEnabled:[RACSignal return:@NO] signalBlock:^(id _) { - return [RACSignal empty]; - }]; - expect([command.enabled first]).to(equal(@NO)); - }); - - qck_it(@"should send NO while executing is YES and allowsConcurrentExecution is NO", ^{ - [[command.executionSignals flatten] subscribeNext:^(id _) { - expect([command.executing first]).to(equal(@YES)); - expect([command.enabled first]).to(equal(@NO)); - }]; - - expect([command.enabled first]).to(equal(@YES)); - expect(@([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); - expect([command.enabled first]).to(equal(@YES)); - }); - - qck_it(@"should send YES while executing is YES and allowsConcurrentExecution is YES", ^{ - command.allowsConcurrentExecution = YES; - - __block BOOL outerExecuted = NO; - __block BOOL innerExecuted = NO; - - // Prevent infinite recursion by only responding to the first value. - [[[command.executionSignals - take:1] - flatten] - subscribeNext:^(id _) { - outerExecuted = YES; - - expect([command.executing first]).to(equal(@YES)); - expect([command.enabled first]).to(equal(@YES)); - - [[command execute:nil] subscribeCompleted:^{ - innerExecuted = YES; - }]; - }]; - - expect([command.enabled first]).to(equal(@YES)); - - expect([command execute:nil]).notTo(beNil()); - expect(@(outerExecuted)).toEventually(beTruthy()); - expect(@(innerExecuted)).toEventually(beTruthy()); - - expect([command.enabled first]).to(equal(@YES)); - }); - - qck_it(@"should send an error from -execute: when NO", ^{ - [enabledSubject sendNext:@NO]; - - RACSignal *signal = [command execute:nil]; - expect(signal).notTo(beNil()); - - __block BOOL success = NO; - __block NSError *error = nil; - expect([signal firstOrDefault:nil success:&success error:&error]).to(beNil()); - expect(@(success)).to(beFalsy()); - - expect(error).notTo(beNil()); - expect(error.domain).to(equal(RACCommandErrorDomain)); - expect(@(error.code)).to(equal(@(RACCommandErrorNotEnabled))); - expect(error.userInfo[RACUnderlyingCommandErrorKey]).to(beIdenticalTo(command)); - }); - - qck_it(@"should always update on the main thread", ^{ - __block RACScheduler *updatedScheduler = nil; - [[command.enabled skip:1] subscribeNext:^(id _) { - updatedScheduler = RACScheduler.currentScheduler; - }]; - - [[RACScheduler scheduler] schedule:^{ - [enabledSubject sendNext:@NO]; - }]; - - expect([command.enabled first]).to(equal(@YES)); - expect([command.enabled first]).toEventually(equal(@NO)); - expect(updatedScheduler).to(equal(RACScheduler.mainThreadScheduler)); - }); - - qck_it(@"should complete when the command is deallocated even if the input signal hasn't", ^{ - __block BOOL deallocated = NO; - __block BOOL completed = NO; - - @autoreleasepool { - RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:emptySignalBlock]; - [command.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - deallocated = YES; - }]]; - - [command.enabled subscribeCompleted:^{ - completed = YES; - }]; - } - - expect(@(deallocated)).toEventually(beTruthy()); - expect(@(completed)).toEventually(beTruthy()); - }); + __block RACSubject *enabledSubject; + __block RACCommand *command; + + qck_beforeEach(^{ + enabledSubject = [RACSubject subject]; + command = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:^(id _) { + return [RACSignal return:RACUnit.defaultUnit]; + }]; + }); + + qck_it(@"should send YES by default", ^{ + expect([command.enabled first]).to(equal(@YES)); + }); + + qck_it(@"should send whatever the enabledSignal has sent most recently", ^{ + [enabledSubject sendNext:@NO]; + expect([command.enabled first]).toEventually(equal(@NO)); + + [enabledSubject sendNext:@YES]; + expect([command.enabled first]).toEventually(equal(@YES)); + + [enabledSubject sendNext:@NO]; + expect([command.enabled first]).toEventually(equal(@NO)); + }); + + qck_it(@"should sample enabledSignal synchronously at initialization time", ^{ + RACCommand *command = [[RACCommand alloc] initWithEnabled:[RACSignal return:@NO] signalBlock:^(id _) { + return [RACSignal empty]; + }]; + expect([command.enabled first]).to(equal(@NO)); + }); + + qck_it(@"should send NO while executing is YES and allowsConcurrentExecution is NO", ^{ + [[command.executionSignals flatten] subscribeNext:^(id _) { + expect([command.executing first]).to(equal(@YES)); + expect([command.enabled first]).to(equal(@NO)); + }]; + + expect([command.enabled first]).to(equal(@YES)); + expect(@([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); + expect([command.enabled first]).to(equal(@YES)); + }); + + qck_it(@"should send YES while executing is YES and allowsConcurrentExecution is YES", ^{ + command.allowsConcurrentExecution = YES; + + __block BOOL outerExecuted = NO; + __block BOOL innerExecuted = NO; + + // Prevent infinite recursion by only responding to the first value. + [[[command.executionSignals + take:1] + flatten] + subscribeNext:^(id _) { + outerExecuted = YES; + + expect([command.executing first]).to(equal(@YES)); + expect([command.enabled first]).to(equal(@YES)); + + [[command execute:nil] subscribeCompleted:^{ + innerExecuted = YES; + }]; + }]; + + expect([command.enabled first]).to(equal(@YES)); + + expect([command execute:nil]).notTo(beNil()); + expect(@(outerExecuted)).toEventually(beTruthy()); + expect(@(innerExecuted)).toEventually(beTruthy()); + + expect([command.enabled first]).to(equal(@YES)); + }); + + qck_it(@"should send an error from -execute: when NO", ^{ + [enabledSubject sendNext:@NO]; + + RACSignal *signal = [command execute:nil]; + expect(signal).notTo(beNil()); + + __block BOOL success = NO; + __block NSError *error = nil; + expect([signal firstOrDefault:nil success:&success error:&error]).to(beNil()); + expect(@(success)).to(beFalsy()); + + expect(error).notTo(beNil()); + expect(error.domain).to(equal(RACCommandErrorDomain)); + expect(@(error.code)).to(equal(@(RACCommandErrorNotEnabled))); + expect(error.userInfo[RACUnderlyingCommandErrorKey]).to(beIdenticalTo(command)); + }); + + qck_it(@"should always update on the main thread", ^{ + __block RACScheduler *updatedScheduler = nil; + [[command.enabled skip:1] subscribeNext:^(id _) { + updatedScheduler = RACScheduler.currentScheduler; + }]; + + [[RACScheduler scheduler] schedule:^{ + [enabledSubject sendNext:@NO]; + }]; + + expect([command.enabled first]).to(equal(@YES)); + expect([command.enabled first]).toEventually(equal(@NO)); + expect(updatedScheduler).to(equal(RACScheduler.mainThreadScheduler)); + }); + + qck_it(@"should complete when the command is deallocated even if the input signal hasn't", ^{ + __block BOOL deallocated = NO; + __block BOOL completed = NO; + + @autoreleasepool { + RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:emptySignalBlock]; + [command.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + [command.enabled subscribeCompleted:^{ + completed = YES; + }]; + } + + expect(@(deallocated)).toEventually(beTruthy()); + expect(@(completed)).toEventually(beTruthy()); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACCompoundDisposableSpec.m b/ReactiveObjCTests/RACCompoundDisposableSpec.m index 68e5233ea..b2df8a2ea 100644 --- a/ReactiveObjCTests/RACCompoundDisposableSpec.m +++ b/ReactiveObjCTests/RACCompoundDisposableSpec.m @@ -14,99 +14,99 @@ QuickSpecBegin(RACCompoundDisposableSpec) qck_it(@"should dispose of all its contained disposables", ^{ - __block BOOL d1Disposed = NO; - RACDisposable *d1 = [RACDisposable disposableWithBlock:^{ - d1Disposed = YES; - }]; - - __block BOOL d2Disposed = NO; - RACDisposable *d2 = [RACDisposable disposableWithBlock:^{ - d2Disposed = YES; - }]; - - __block BOOL d3Disposed = NO; - RACDisposable *d3 = [RACDisposable disposableWithBlock:^{ - d3Disposed = YES; - }]; - - __block BOOL d4Disposed = NO; - RACDisposable *d4 = [RACDisposable disposableWithBlock:^{ - d4Disposed = YES; - }]; - - RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposableWithDisposables:@[ d1, d2, d3 ]]; - [disposable addDisposable:d4]; - - expect(@(d1Disposed)).to(beFalsy()); - expect(@(d2Disposed)).to(beFalsy()); - expect(@(d3Disposed)).to(beFalsy()); - expect(@(d4Disposed)).to(beFalsy()); - expect(@(disposable.disposed)).to(beFalsy()); - - [disposable dispose]; - - expect(@(d1Disposed)).to(beTruthy()); - expect(@(d2Disposed)).to(beTruthy()); - expect(@(d3Disposed)).to(beTruthy()); - expect(@(d4Disposed)).to(beTruthy()); - expect(@(disposable.disposed)).to(beTruthy()); + __block BOOL d1Disposed = NO; + RACDisposable *d1 = [RACDisposable disposableWithBlock:^{ + d1Disposed = YES; + }]; + + __block BOOL d2Disposed = NO; + RACDisposable *d2 = [RACDisposable disposableWithBlock:^{ + d2Disposed = YES; + }]; + + __block BOOL d3Disposed = NO; + RACDisposable *d3 = [RACDisposable disposableWithBlock:^{ + d3Disposed = YES; + }]; + + __block BOOL d4Disposed = NO; + RACDisposable *d4 = [RACDisposable disposableWithBlock:^{ + d4Disposed = YES; + }]; + + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposableWithDisposables:@[ d1, d2, d3 ]]; + [disposable addDisposable:d4]; + + expect(@(d1Disposed)).to(beFalsy()); + expect(@(d2Disposed)).to(beFalsy()); + expect(@(d3Disposed)).to(beFalsy()); + expect(@(d4Disposed)).to(beFalsy()); + expect(@(disposable.disposed)).to(beFalsy()); + + [disposable dispose]; + + expect(@(d1Disposed)).to(beTruthy()); + expect(@(d2Disposed)).to(beTruthy()); + expect(@(d3Disposed)).to(beTruthy()); + expect(@(d4Disposed)).to(beTruthy()); + expect(@(disposable.disposed)).to(beTruthy()); }); qck_it(@"should dispose of any added disposables immediately if it's already been disposed", ^{ - RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; - [disposable dispose]; + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + [disposable dispose]; - RACDisposable *d = [[RACDisposable alloc] init]; + RACDisposable *d = [[RACDisposable alloc] init]; - expect(@(d.disposed)).to(beFalsy()); - [disposable addDisposable:d]; - expect(@(d.disposed)).to(beTruthy()); + expect(@(d.disposed)).to(beFalsy()); + [disposable addDisposable:d]; + expect(@(d.disposed)).to(beTruthy()); }); qck_it(@"should work when initialized with -init", ^{ - RACCompoundDisposable *disposable = [[RACCompoundDisposable alloc] init]; + RACCompoundDisposable *disposable = [[RACCompoundDisposable alloc] init]; - __block BOOL disposed = NO; - RACDisposable *d = [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; + __block BOOL disposed = NO; + RACDisposable *d = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; - [disposable addDisposable:d]; - expect(@(disposed)).to(beFalsy()); + [disposable addDisposable:d]; + expect(@(disposed)).to(beFalsy()); - [disposable dispose]; - expect(@(disposed)).to(beTruthy()); + [disposable dispose]; + expect(@(disposed)).to(beTruthy()); }); qck_it(@"should work when initialized with +disposableWithBlock:", ^{ - __block BOOL compoundDisposed = NO; - RACCompoundDisposable *disposable = [RACCompoundDisposable disposableWithBlock:^{ - compoundDisposed = YES; - }]; - - __block BOOL disposed = NO; - RACDisposable *d = [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; - - [disposable addDisposable:d]; - expect(@(disposed)).to(beFalsy()); - expect(@(compoundDisposed)).to(beFalsy()); - - [disposable dispose]; - expect(@(disposed)).to(beTruthy()); - expect(@(compoundDisposed)).to(beTruthy()); + __block BOOL compoundDisposed = NO; + RACCompoundDisposable *disposable = [RACCompoundDisposable disposableWithBlock:^{ + compoundDisposed = YES; + }]; + + __block BOOL disposed = NO; + RACDisposable *d = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + + [disposable addDisposable:d]; + expect(@(disposed)).to(beFalsy()); + expect(@(compoundDisposed)).to(beFalsy()); + + [disposable dispose]; + expect(@(disposed)).to(beTruthy()); + expect(@(compoundDisposed)).to(beTruthy()); }); qck_it(@"should allow disposables to be removed", ^{ - RACCompoundDisposable *disposable = [[RACCompoundDisposable alloc] init]; - RACDisposable *d = [[RACDisposable alloc] init]; + RACCompoundDisposable *disposable = [[RACCompoundDisposable alloc] init]; + RACDisposable *d = [[RACDisposable alloc] init]; - [disposable addDisposable:d]; - [disposable removeDisposable:d]; + [disposable addDisposable:d]; + [disposable removeDisposable:d]; - [disposable dispose]; - expect(@(d.disposed)).to(beFalsy()); + [disposable dispose]; + expect(@(d.disposed)).to(beFalsy()); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACControlCommandExamples.m b/ReactiveObjCTests/RACControlCommandExamples.m index 4476c7565..030f34800 100644 --- a/ReactiveObjCTests/RACControlCommandExamples.m +++ b/ReactiveObjCTests/RACControlCommandExamples.m @@ -32,55 +32,55 @@ - (BOOL)isEnabled; QuickConfigurationBegin(RACControlCommandExampleGroups) + (void)configure:(Configuration *)configuration { - sharedExamples(RACControlCommandExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block id control; - __block void (^activate)(id); - - __block RACSubject *enabledSubject; - __block RACCommand *command; - - qck_beforeEach(^{ - control = exampleContext()[RACControlCommandExampleControl]; - activate = [exampleContext()[RACControlCommandExampleActivateBlock] copy]; - - enabledSubject = [RACSubject subject]; - command = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:^(id sender) { - return [RACSignal return:sender]; - }]; - - [control setRac_command:command]; - }); - - qck_it(@"should bind the control's enabledness to the command", ^{ - expect(@([control isEnabled])).toEventually(beTruthy()); - - [enabledSubject sendNext:@NO]; - expect(@([control isEnabled])).toEventually(beFalsy()); - - [enabledSubject sendNext:@YES]; - expect(@([control isEnabled])).toEventually(beTruthy()); - }); - - qck_it(@"should execute the control's command when activated", ^{ - __block BOOL executed = NO; - [[command.executionSignals flatten] subscribeNext:^(id sender) { - expect(sender).to(equal(control)); - executed = YES; - }]; - - activate(control); - expect(@(executed)).toEventually(beTruthy()); - }); - - qck_it(@"should overwrite an existing command when setting a new one", ^{ - RACCommand *secondCommand = [[RACCommand alloc] initWithSignalBlock:^(id _) { - return [RACSignal return:RACUnit.defaultUnit]; - }]; - - [control setRac_command:secondCommand]; - expect([control rac_command]).to(beIdenticalTo(secondCommand)); - }); - }); + sharedExamples(RACControlCommandExamples, ^(QCKDSLSharedExampleContext exampleContext) { + __block id control; + __block void (^activate)(id); + + __block RACSubject *enabledSubject; + __block RACCommand *command; + + qck_beforeEach(^{ + control = exampleContext()[RACControlCommandExampleControl]; + activate = [exampleContext()[RACControlCommandExampleActivateBlock] copy]; + + enabledSubject = [RACSubject subject]; + command = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:^(id sender) { + return [RACSignal return:sender]; + }]; + + [control setRac_command:command]; + }); + + qck_it(@"should bind the control's enabledness to the command", ^{ + expect(@([control isEnabled])).toEventually(beTruthy()); + + [enabledSubject sendNext:@NO]; + expect(@([control isEnabled])).toEventually(beFalsy()); + + [enabledSubject sendNext:@YES]; + expect(@([control isEnabled])).toEventually(beTruthy()); + }); + + qck_it(@"should execute the control's command when activated", ^{ + __block BOOL executed = NO; + [[command.executionSignals flatten] subscribeNext:^(id sender) { + expect(sender).to(equal(control)); + executed = YES; + }]; + + activate(control); + expect(@(executed)).toEventually(beTruthy()); + }); + + qck_it(@"should overwrite an existing command when setting a new one", ^{ + RACCommand *secondCommand = [[RACCommand alloc] initWithSignalBlock:^(id _) { + return [RACSignal return:RACUnit.defaultUnit]; + }]; + + [control setRac_command:secondCommand]; + expect([control rac_command]).to(beIdenticalTo(secondCommand)); + }); + }); } QuickConfigurationEnd diff --git a/ReactiveObjCTests/RACDelegateProxySpec.m b/ReactiveObjCTests/RACDelegateProxySpec.m index 346f5c49b..40ecf482c 100644 --- a/ReactiveObjCTests/RACDelegateProxySpec.m +++ b/ReactiveObjCTests/RACDelegateProxySpec.m @@ -31,53 +31,53 @@ @interface TestDelegate : NSObject __block Protocol *protocol; qck_beforeEach(^{ - protocol = @protocol(TestDelegateProtocol); - expect(protocol).notTo(beNil()); + protocol = @protocol(TestDelegateProtocol); + expect(protocol).notTo(beNil()); - proxy = [[RACDelegateProxy alloc] initWithProtocol:protocol]; - expect(proxy).notTo(beNil()); - expect([proxy rac_proxiedDelegate]).to(beNil()); + proxy = [[RACDelegateProxy alloc] initWithProtocol:protocol]; + expect(proxy).notTo(beNil()); + expect([proxy rac_proxiedDelegate]).to(beNil()); - delegate = [[TestDelegate alloc] init]; - expect(delegate).notTo(beNil()); + delegate = [[TestDelegate alloc] init]; + expect(delegate).notTo(beNil()); }); qck_it(@"should not respond to selectors at first", ^{ - expect(@([proxy respondsToSelector:@selector(lengthOfString:)])).to(beFalsy()); + expect(@([proxy respondsToSelector:@selector(lengthOfString:)])).to(beFalsy()); }); qck_it(@"should send on a signal for a protocol method", ^{ - __block RACTuple *tuple; - [[proxy signalForSelector:@selector(lengthOfString:)] subscribeNext:^(RACTuple *t) { - tuple = t; - }]; - - expect(@([proxy respondsToSelector:@selector(lengthOfString:)])).to(beTruthy()); - expect(@([proxy lengthOfString:@"foo"])).to(equal(@0)); - expect(tuple).to(equal(RACTuplePack(@"foo"))); + __block RACTuple *tuple; + [[proxy signalForSelector:@selector(lengthOfString:)] subscribeNext:^(RACTuple *t) { + tuple = t; + }]; + + expect(@([proxy respondsToSelector:@selector(lengthOfString:)])).to(beTruthy()); + expect(@([proxy lengthOfString:@"foo"])).to(equal(@0)); + expect(tuple).to(equal(RACTuplePack(@"foo"))); }); qck_it(@"should forward to the proxied delegate", ^{ - [proxy setRac_proxiedDelegate:delegate]; + [proxy setRac_proxiedDelegate:delegate]; - expect(@([proxy respondsToSelector:@selector(lengthOfString:)])).to(beTruthy()); - expect(@([proxy lengthOfString:@"foo"])).to(equal(@3)); - expect(@(delegate.lengthOfStringInvoked)).to(beTruthy()); + expect(@([proxy respondsToSelector:@selector(lengthOfString:)])).to(beTruthy()); + expect(@([proxy lengthOfString:@"foo"])).to(equal(@3)); + expect(@(delegate.lengthOfStringInvoked)).to(beTruthy()); }); qck_it(@"should not send to the delegate when signals are applied", ^{ - [proxy setRac_proxiedDelegate:delegate]; + [proxy setRac_proxiedDelegate:delegate]; - __block RACTuple *tuple; - [[proxy signalForSelector:@selector(lengthOfString:)] subscribeNext:^(RACTuple *t) { - tuple = t; - }]; + __block RACTuple *tuple; + [[proxy signalForSelector:@selector(lengthOfString:)] subscribeNext:^(RACTuple *t) { + tuple = t; + }]; - expect(@([proxy respondsToSelector:@selector(lengthOfString:)])).to(beTruthy()); - expect(@([proxy lengthOfString:@"foo"])).to(equal(@0)); + expect(@([proxy respondsToSelector:@selector(lengthOfString:)])).to(beTruthy()); + expect(@([proxy lengthOfString:@"foo"])).to(equal(@0)); - expect(tuple).to(equal(RACTuplePack(@"foo"))); - expect(@(delegate.lengthOfStringInvoked)).to(beFalsy()); + expect(tuple).to(equal(RACTuplePack(@"foo"))); + expect(@(delegate.lengthOfStringInvoked)).to(beFalsy()); }); QuickSpecEnd @@ -85,8 +85,8 @@ @interface TestDelegate : NSObject @implementation TestDelegate - (NSUInteger)lengthOfString:(NSString *)str { - self.lengthOfStringInvoked = YES; - return str.length; + self.lengthOfStringInvoked = YES; + return str.length; } @end diff --git a/ReactiveObjCTests/RACDisposableSpec.m b/ReactiveObjCTests/RACDisposableSpec.m index b6d7f5969..d2f51c1ac 100644 --- a/ReactiveObjCTests/RACDisposableSpec.m +++ b/ReactiveObjCTests/RACDisposableSpec.m @@ -15,62 +15,62 @@ QuickSpecBegin(RACDisposableSpec) qck_it(@"should initialize without a block", ^{ - RACDisposable *disposable = [[RACDisposable alloc] init]; - expect(disposable).notTo(beNil()); - expect(@(disposable.disposed)).to(beFalsy()); + RACDisposable *disposable = [[RACDisposable alloc] init]; + expect(disposable).notTo(beNil()); + expect(@(disposable.disposed)).to(beFalsy()); - [disposable dispose]; - expect(@(disposable.disposed)).to(beTruthy()); + [disposable dispose]; + expect(@(disposable.disposed)).to(beTruthy()); }); qck_it(@"should execute a block upon disposal", ^{ - __block BOOL disposed = NO; - RACDisposable *disposable = [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; - - expect(disposable).notTo(beNil()); - expect(@(disposed)).to(beFalsy()); - expect(@(disposable.disposed)).to(beFalsy()); - - [disposable dispose]; - expect(@(disposed)).to(beTruthy()); - expect(@(disposable.disposed)).to(beTruthy()); + __block BOOL disposed = NO; + RACDisposable *disposable = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + + expect(disposable).notTo(beNil()); + expect(@(disposed)).to(beFalsy()); + expect(@(disposable.disposed)).to(beFalsy()); + + [disposable dispose]; + expect(@(disposed)).to(beTruthy()); + expect(@(disposable.disposed)).to(beTruthy()); }); qck_it(@"should not dispose upon deallocation", ^{ - __block BOOL disposed = NO; - __weak RACDisposable *weakDisposable = nil; + __block BOOL disposed = NO; + __weak RACDisposable *weakDisposable = nil; - @autoreleasepool { - RACDisposable *disposable = [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; + @autoreleasepool { + RACDisposable *disposable = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; - weakDisposable = disposable; - expect(weakDisposable).notTo(beNil()); - } + weakDisposable = disposable; + expect(weakDisposable).notTo(beNil()); + } - expect(weakDisposable).to(beNil()); - expect(@(disposed)).to(beFalsy()); + expect(weakDisposable).to(beNil()); + expect(@(disposed)).to(beFalsy()); }); qck_it(@"should create a scoped disposable", ^{ - __block BOOL disposed = NO; - __weak RACScopedDisposable *weakDisposable = nil; + __block BOOL disposed = NO; + __weak RACScopedDisposable *weakDisposable = nil; - @autoreleasepool { - RACScopedDisposable *disposable __attribute__((objc_precise_lifetime)) = [RACScopedDisposable disposableWithBlock:^{ - disposed = YES; - }]; + @autoreleasepool { + RACScopedDisposable *disposable __attribute__((objc_precise_lifetime)) = [RACScopedDisposable disposableWithBlock:^{ + disposed = YES; + }]; - weakDisposable = disposable; - expect(weakDisposable).notTo(beNil()); - expect(@(disposed)).to(beFalsy()); - } + weakDisposable = disposable; + expect(weakDisposable).notTo(beNil()); + expect(@(disposed)).to(beFalsy()); + } - expect(weakDisposable).to(beNil()); - expect(@(disposed)).to(beTruthy()); + expect(weakDisposable).to(beNil()); + expect(@(disposed)).to(beTruthy()); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACEventSpec.m b/ReactiveObjCTests/RACEventSpec.m index 03dac4040..9005e3c18 100644 --- a/ReactiveObjCTests/RACEventSpec.m +++ b/ReactiveObjCTests/RACEventSpec.m @@ -14,70 +14,70 @@ QuickSpecBegin(RACEventSpec) qck_it(@"should return the singleton completed event", ^{ - RACEvent *event = RACEvent.completedEvent; - expect(event).notTo(beNil()); + RACEvent *event = RACEvent.completedEvent; + expect(event).notTo(beNil()); - expect(event).to(beIdenticalTo(RACEvent.completedEvent)); - expect([event copy]).to(beIdenticalTo(event)); + expect(event).to(beIdenticalTo(RACEvent.completedEvent)); + expect([event copy]).to(beIdenticalTo(event)); - expect(@(event.eventType)).to(equal(@(RACEventTypeCompleted))); - expect(@(event.finished)).to(beTruthy()); - expect(event.error).to(beNil()); - expect(event.value).to(beNil()); + expect(@(event.eventType)).to(equal(@(RACEventTypeCompleted))); + expect(@(event.finished)).to(beTruthy()); + expect(event.error).to(beNil()); + expect(event.value).to(beNil()); }); qck_it(@"should return an error event", ^{ - NSError *error = [NSError errorWithDomain:@"foo" code:1 userInfo:nil]; - RACEvent *event = [RACEvent eventWithError:error]; - expect(event).notTo(beNil()); + NSError *error = [NSError errorWithDomain:@"foo" code:1 userInfo:nil]; + RACEvent *event = [RACEvent eventWithError:error]; + expect(event).notTo(beNil()); - expect(event).to(equal([RACEvent eventWithError:error])); - expect([event copy]).to(equal(event)); + expect(event).to(equal([RACEvent eventWithError:error])); + expect([event copy]).to(equal(event)); - expect(@(event.eventType)).to(equal(@(RACEventTypeError))); - expect(@(event.finished)).to(beTruthy()); - expect(event.error).to(equal(error)); - expect(event.value).to(beNil()); + expect(@(event.eventType)).to(equal(@(RACEventTypeError))); + expect(@(event.finished)).to(beTruthy()); + expect(event.error).to(equal(error)); + expect(event.value).to(beNil()); }); qck_it(@"should return an error event with a nil error", ^{ - RACEvent *event = [RACEvent eventWithError:nil]; - expect(event).notTo(beNil()); + RACEvent *event = [RACEvent eventWithError:nil]; + expect(event).notTo(beNil()); - expect(event).to(equal([RACEvent eventWithError:nil])); - expect([event copy]).to(equal(event)); + expect(event).to(equal([RACEvent eventWithError:nil])); + expect([event copy]).to(equal(event)); - expect(@(event.eventType)).to(equal(@(RACEventTypeError))); - expect(@(event.finished)).to(beTruthy()); - expect(event.error).to(beNil()); - expect(event.value).to(beNil()); + expect(@(event.eventType)).to(equal(@(RACEventTypeError))); + expect(@(event.finished)).to(beTruthy()); + expect(event.error).to(beNil()); + expect(event.value).to(beNil()); }); qck_it(@"should return a next event", ^{ - NSString *value = @"foo"; - RACEvent *event = [RACEvent eventWithValue:value]; - expect(event).notTo(beNil()); + NSString *value = @"foo"; + RACEvent *event = [RACEvent eventWithValue:value]; + expect(event).notTo(beNil()); - expect(event).to(equal([RACEvent eventWithValue:value])); - expect([event copy]).to(equal(event)); + expect(event).to(equal([RACEvent eventWithValue:value])); + expect([event copy]).to(equal(event)); - expect(@(event.eventType)).to(equal(@(RACEventTypeNext))); - expect(@(event.finished)).to(beFalsy()); - expect(event.error).to(beNil()); - expect(event.value).to(equal(value)); + expect(@(event.eventType)).to(equal(@(RACEventTypeNext))); + expect(@(event.finished)).to(beFalsy()); + expect(event.error).to(beNil()); + expect(event.value).to(equal(value)); }); qck_it(@"should return a next event with a nil value", ^{ - RACEvent *event = [RACEvent eventWithValue:nil]; - expect(event).notTo(beNil()); + RACEvent *event = [RACEvent eventWithValue:nil]; + expect(event).notTo(beNil()); - expect(event).to(equal([RACEvent eventWithValue:nil])); - expect([event copy]).to(equal(event)); + expect(event).to(equal([RACEvent eventWithValue:nil])); + expect([event copy]).to(equal(event)); - expect(@(event.eventType)).to(equal(@(RACEventTypeNext))); - expect(@(event.finished)).to(beFalsy()); - expect(event.error).to(beNil()); - expect(event.value).to(beNil()); + expect(@(event.eventType)).to(equal(@(RACEventTypeNext))); + expect(@(event.finished)).to(beFalsy()); + expect(event.error).to(beNil()); + expect(event.value).to(beNil()); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACKVOChannelSpec.m b/ReactiveObjCTests/RACKVOChannelSpec.m index bf4a6dfc6..c14eeac17 100644 --- a/ReactiveObjCTests/RACKVOChannelSpec.m +++ b/ReactiveObjCTests/RACKVOChannelSpec.m @@ -23,372 +23,372 @@ QuickSpecBegin(RACKVOChannelSpec) qck_describe(@"RACKVOChannel", ^{ - __block RACTestObject *object; - __block RACKVOChannel *channel; - id value1 = @"test value 1"; - id value2 = @"test value 2"; - id value3 = @"test value 3"; - NSArray *values = @[ value1, value2, value3 ]; - - qck_beforeEach(^{ - object = [[RACTestObject alloc] init]; - channel = [[RACKVOChannel alloc] initWithTarget:object keyPath:@rac_keypath(object.stringValue) nilValue:nil]; - }); - - id setupBlock = ^(RACTestObject *testObject, NSString *keyPath, id nilValue, RACSignal *signal) { - RACKVOChannel *channel = [[RACKVOChannel alloc] initWithTarget:testObject keyPath:keyPath nilValue:nilValue]; - [signal subscribe:channel.followingTerminal]; - }; - - qck_itBehavesLike(RACPropertySignalExamples, ^{ - return @{ RACPropertySignalExamplesSetupBlock: setupBlock }; - }); - - qck_itBehavesLike(RACChannelExamples, ^{ - return @{ - RACChannelExampleCreateBlock: [^{ - return [[RACKVOChannel alloc] initWithTarget:object keyPath:@rac_keypath(object.stringValue) nilValue:nil]; - } copy] - }; - }); - - qck_it(@"should send the object's current value when subscribed to followingTerminal", ^{ - __block id receivedValue = @"received value should not be this"; - [[channel.followingTerminal take:1] subscribeNext:^(id x) { - receivedValue = x; - }]; - - expect(receivedValue).to(beNil()); - - object.stringValue = value1; - [[channel.followingTerminal take:1] subscribeNext:^(id x) { - receivedValue = x; - }]; - - expect(receivedValue).to(equal(value1)); - }); - - qck_it(@"should send the object's new value on followingTerminal when it's changed", ^{ - object.stringValue = value1; - - NSMutableArray *receivedValues = [NSMutableArray array]; - [channel.followingTerminal subscribeNext:^(id x) { - [receivedValues addObject:x]; - }]; - - object.stringValue = value2; - object.stringValue = value3; - expect(receivedValues).to(equal(values)); - }); - - qck_it(@"should set the object's value using values sent to the followingTerminal", ^{ - expect(object.stringValue).to(beNil()); - - [channel.followingTerminal sendNext:value1]; - expect(object.stringValue).to(equal(value1)); - - [channel.followingTerminal sendNext:value2]; - expect(object.stringValue).to(equal(value2)); - }); - - qck_it(@"should be able to subscribe to signals", ^{ - NSMutableArray *receivedValues = [NSMutableArray array]; - [object rac_observeKeyPath:@rac_keypath(object.stringValue) options:0 observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - [receivedValues addObject:value]; - }]; - - RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id subscriber) { - [subscriber sendNext:value1]; - [subscriber sendNext:value2]; - [subscriber sendNext:value3]; - return nil; - }]; - - [signal subscribe:channel.followingTerminal]; - expect(receivedValues).to(equal(values)); - }); - - qck_it(@"should complete both terminals when the target deallocates", ^{ - __block BOOL leadingCompleted = NO; - __block BOOL followingCompleted = NO; - __block BOOL deallocated = NO; - - @autoreleasepool { - RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; - [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - deallocated = YES; - }]]; - - RACKVOChannel *channel = [[RACKVOChannel alloc] initWithTarget:object keyPath:@rac_keypath(object.stringValue) nilValue:nil]; - [channel.leadingTerminal subscribeCompleted:^{ - leadingCompleted = YES; - }]; - - [channel.followingTerminal subscribeCompleted:^{ - followingCompleted = YES; - }]; - - expect(@(deallocated)).to(beFalsy()); - expect(@(leadingCompleted)).to(beFalsy()); - expect(@(followingCompleted)).to(beFalsy()); - } - - expect(@(deallocated)).to(beTruthy()); - expect(@(leadingCompleted)).to(beTruthy()); - expect(@(followingCompleted)).to(beTruthy()); - }); - - qck_it(@"should deallocate when the target deallocates", ^{ - __block BOOL targetDeallocated = NO; - __block BOOL channelDeallocated = NO; - - @autoreleasepool { - RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; - [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - targetDeallocated = YES; - }]]; - - RACKVOChannel *channel = [[RACKVOChannel alloc] initWithTarget:object keyPath:@rac_keypath(object.stringValue) nilValue:nil]; - [channel.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - channelDeallocated = YES; - }]]; - - expect(@(targetDeallocated)).to(beFalsy()); - expect(@(channelDeallocated)).to(beFalsy()); - } - - expect(@(targetDeallocated)).to(beTruthy()); - expect(@(channelDeallocated)).to(beTruthy()); - }); + __block RACTestObject *object; + __block RACKVOChannel *channel; + id value1 = @"test value 1"; + id value2 = @"test value 2"; + id value3 = @"test value 3"; + NSArray *values = @[ value1, value2, value3 ]; + + qck_beforeEach(^{ + object = [[RACTestObject alloc] init]; + channel = [[RACKVOChannel alloc] initWithTarget:object keyPath:@rac_keypath(object.stringValue) nilValue:nil]; + }); + + id setupBlock = ^(RACTestObject *testObject, NSString *keyPath, id nilValue, RACSignal *signal) { + RACKVOChannel *channel = [[RACKVOChannel alloc] initWithTarget:testObject keyPath:keyPath nilValue:nilValue]; + [signal subscribe:channel.followingTerminal]; + }; + + qck_itBehavesLike(RACPropertySignalExamples, ^{ + return @{ RACPropertySignalExamplesSetupBlock: setupBlock }; + }); + + qck_itBehavesLike(RACChannelExamples, ^{ + return @{ + RACChannelExampleCreateBlock: [^{ + return [[RACKVOChannel alloc] initWithTarget:object keyPath:@rac_keypath(object.stringValue) nilValue:nil]; + } copy] + }; + }); + + qck_it(@"should send the object's current value when subscribed to followingTerminal", ^{ + __block id receivedValue = @"received value should not be this"; + [[channel.followingTerminal take:1] subscribeNext:^(id x) { + receivedValue = x; + }]; + + expect(receivedValue).to(beNil()); + + object.stringValue = value1; + [[channel.followingTerminal take:1] subscribeNext:^(id x) { + receivedValue = x; + }]; + + expect(receivedValue).to(equal(value1)); + }); + + qck_it(@"should send the object's new value on followingTerminal when it's changed", ^{ + object.stringValue = value1; + + NSMutableArray *receivedValues = [NSMutableArray array]; + [channel.followingTerminal subscribeNext:^(id x) { + [receivedValues addObject:x]; + }]; + + object.stringValue = value2; + object.stringValue = value3; + expect(receivedValues).to(equal(values)); + }); + + qck_it(@"should set the object's value using values sent to the followingTerminal", ^{ + expect(object.stringValue).to(beNil()); + + [channel.followingTerminal sendNext:value1]; + expect(object.stringValue).to(equal(value1)); + + [channel.followingTerminal sendNext:value2]; + expect(object.stringValue).to(equal(value2)); + }); + + qck_it(@"should be able to subscribe to signals", ^{ + NSMutableArray *receivedValues = [NSMutableArray array]; + [object rac_observeKeyPath:@rac_keypath(object.stringValue) options:0 observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + [receivedValues addObject:value]; + }]; + + RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id subscriber) { + [subscriber sendNext:value1]; + [subscriber sendNext:value2]; + [subscriber sendNext:value3]; + return nil; + }]; + + [signal subscribe:channel.followingTerminal]; + expect(receivedValues).to(equal(values)); + }); + + qck_it(@"should complete both terminals when the target deallocates", ^{ + __block BOOL leadingCompleted = NO; + __block BOOL followingCompleted = NO; + __block BOOL deallocated = NO; + + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + RACKVOChannel *channel = [[RACKVOChannel alloc] initWithTarget:object keyPath:@rac_keypath(object.stringValue) nilValue:nil]; + [channel.leadingTerminal subscribeCompleted:^{ + leadingCompleted = YES; + }]; + + [channel.followingTerminal subscribeCompleted:^{ + followingCompleted = YES; + }]; + + expect(@(deallocated)).to(beFalsy()); + expect(@(leadingCompleted)).to(beFalsy()); + expect(@(followingCompleted)).to(beFalsy()); + } + + expect(@(deallocated)).to(beTruthy()); + expect(@(leadingCompleted)).to(beTruthy()); + expect(@(followingCompleted)).to(beTruthy()); + }); + + qck_it(@"should deallocate when the target deallocates", ^{ + __block BOOL targetDeallocated = NO; + __block BOOL channelDeallocated = NO; + + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + targetDeallocated = YES; + }]]; + + RACKVOChannel *channel = [[RACKVOChannel alloc] initWithTarget:object keyPath:@rac_keypath(object.stringValue) nilValue:nil]; + [channel.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + channelDeallocated = YES; + }]]; + + expect(@(targetDeallocated)).to(beFalsy()); + expect(@(channelDeallocated)).to(beFalsy()); + } + + expect(@(targetDeallocated)).to(beTruthy()); + expect(@(channelDeallocated)).to(beTruthy()); + }); }); qck_describe(@"RACChannelTo", ^{ - __block RACTestObject *a; - __block RACTestObject *b; - __block RACTestObject *c; - __block NSString *testName1; - __block NSString *testName2; - __block NSString *testName3; - - qck_beforeEach(^{ - a = [[RACTestObject alloc] init]; - b = [[RACTestObject alloc] init]; - c = [[RACTestObject alloc] init]; - testName1 = @"sync it!"; - testName2 = @"sync it again!"; - testName3 = @"sync it once more!"; - }); - - qck_it(@"should keep objects' properties in sync", ^{ - RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue); - expect(a.stringValue).to(beNil()); - expect(b.stringValue).to(beNil()); - - a.stringValue = testName1; - expect(a.stringValue).to(equal(testName1)); - expect(b.stringValue).to(equal(testName1)); - - b.stringValue = testName2; - expect(a.stringValue).to(equal(testName2)); - expect(b.stringValue).to(equal(testName2)); - - a.stringValue = nil; - expect(a.stringValue).to(beNil()); - expect(b.stringValue).to(beNil()); - }); - - qck_it(@"should keep properties identified by keypaths in sync", ^{ - RACChannelTo(a, strongTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue); - a.strongTestObjectValue = [[RACTestObject alloc] init]; - b.strongTestObjectValue = [[RACTestObject alloc] init]; - - a.strongTestObjectValue.stringValue = testName1; - expect(a.strongTestObjectValue.stringValue).to(equal(testName1)); - expect(b.strongTestObjectValue.stringValue).to(equal(testName1)); - expect(a.strongTestObjectValue).notTo(equal(b.strongTestObjectValue)); - - b.strongTestObjectValue = nil; - expect(a.strongTestObjectValue.stringValue).to(beNil()); - - c.stringValue = testName2; - b.strongTestObjectValue = c; - expect(a.strongTestObjectValue.stringValue).to(equal(testName2)); - expect(b.strongTestObjectValue.stringValue).to(equal(testName2)); - expect(a.strongTestObjectValue).notTo(equal(b.strongTestObjectValue)); - }); - - qck_it(@"should update properties identified by keypaths when the intermediate values change", ^{ - RACChannelTo(a, strongTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue); - a.strongTestObjectValue = [[RACTestObject alloc] init]; - b.strongTestObjectValue = [[RACTestObject alloc] init]; - c.stringValue = testName1; - b.strongTestObjectValue = c; - - expect(a.strongTestObjectValue.stringValue).to(equal(testName1)); - expect(a.strongTestObjectValue).notTo(equal(b.strongTestObjectValue)); - }); - - qck_it(@"should update properties identified by keypaths when the channel was created when one of the two objects had an intermediate nil value", ^{ - RACChannelTo(a, strongTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue); - b.strongTestObjectValue = [[RACTestObject alloc] init]; - c.stringValue = testName1; - a.strongTestObjectValue = c; - - expect(a.strongTestObjectValue.stringValue).to(equal(testName1)); - expect(b.strongTestObjectValue.stringValue).to(equal(testName1)); - expect(a.strongTestObjectValue).notTo(equal(b.strongTestObjectValue)); - }); - - qck_it(@"should take the value of the object being bound to at the start", ^{ - a.stringValue = testName1; - b.stringValue = testName2; - - RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue); - expect(a.stringValue).to(equal(testName2)); - expect(b.stringValue).to(equal(testName2)); - }); - - qck_it(@"should update the value even if it's the same value the object had before it was bound", ^{ - a.stringValue = testName1; - b.stringValue = testName2; - - RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue); - expect(a.stringValue).to(equal(testName2)); - expect(b.stringValue).to(equal(testName2)); - - b.stringValue = testName1; - expect(a.stringValue).to(equal(testName1)); - expect(b.stringValue).to(equal(testName1)); - }); - - qck_it(@"should bind transitively", ^{ - a.stringValue = testName1; - b.stringValue = testName2; - c.stringValue = testName3; - - RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue); - RACChannelTo(b, stringValue) = RACChannelTo(c, stringValue); - expect(a.stringValue).to(equal(testName3)); - expect(b.stringValue).to(equal(testName3)); - expect(c.stringValue).to(equal(testName3)); - - c.stringValue = testName1; - expect(a.stringValue).to(equal(testName1)); - expect(b.stringValue).to(equal(testName1)); - expect(c.stringValue).to(equal(testName1)); - - b.stringValue = testName2; - expect(a.stringValue).to(equal(testName2)); - expect(b.stringValue).to(equal(testName2)); - expect(c.stringValue).to(equal(testName2)); - - a.stringValue = testName3; - expect(a.stringValue).to(equal(testName3)); - expect(b.stringValue).to(equal(testName3)); - expect(c.stringValue).to(equal(testName3)); - }); - - qck_it(@"should bind changes made by KVC on arrays", ^{ - b.arrayValue = @[]; - RACChannelTo(a, arrayValue) = RACChannelTo(b, arrayValue); - - [[b mutableArrayValueForKeyPath:@rac_keypath(b.arrayValue)] addObject:@1]; - expect(a.arrayValue).to(equal(b.arrayValue)); - }); - - qck_it(@"should bind changes made by KVC on sets", ^{ - b.setValue = [NSSet set]; - RACChannelTo(a, setValue) = RACChannelTo(b, setValue); - - [[b mutableSetValueForKeyPath:@rac_keypath(b.setValue)] addObject:@1]; - expect(a.setValue).to(equal(b.setValue)); - }); - - qck_it(@"should bind changes made by KVC on ordered sets", ^{ - b.orderedSetValue = [NSOrderedSet orderedSet]; - RACChannelTo(a, orderedSetValue) = RACChannelTo(b, orderedSetValue); - - [[b mutableOrderedSetValueForKeyPath:@rac_keypath(b.orderedSetValue)] addObject:@1]; - expect(a.orderedSetValue).to(equal(b.orderedSetValue)); - }); - - qck_it(@"should handle deallocation of intermediate objects correctly even without support from KVO", ^{ - __block BOOL wasDisposed = NO; - - RACChannelTo(a, weakTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue); - b.strongTestObjectValue = [[RACTestObject alloc] init]; - - @autoreleasepool { - RACTestObject *object = [[RACTestObject alloc] init]; - [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - wasDisposed = YES; - }]]; - - a.weakTestObjectValue = object; - object.stringValue = testName1; - - expect(@(wasDisposed)).to(beFalsy()); - expect(b.strongTestObjectValue.stringValue).to(equal(testName1)); - } - - expect(@(wasDisposed)).toEventually(beTruthy()); - expect(b.strongTestObjectValue.stringValue).to(beNil()); - }); - - qck_it(@"should stop binding when disposed", ^{ - RACChannelTerminal *aTerminal = RACChannelTo(a, stringValue); - RACChannelTerminal *bTerminal = RACChannelTo(b, stringValue); - - a.stringValue = testName1; - RACDisposable *disposable = [aTerminal subscribe:bTerminal]; - - expect(a.stringValue).to(equal(testName1)); - expect(b.stringValue).to(equal(testName1)); - - a.stringValue = testName2; - expect(a.stringValue).to(equal(testName2)); - expect(b.stringValue).to(equal(testName2)); - - [disposable dispose]; - - a.stringValue = testName3; - expect(a.stringValue).to(equal(testName3)); - expect(b.stringValue).to(equal(testName2)); - }); - - qck_it(@"should use the nilValue when sent nil", ^{ - RACChannelTerminal *terminal = RACChannelTo(a, integerValue, @5); - expect(@(a.integerValue)).to(equal(@0)); - - [terminal sendNext:@2]; - expect(@(a.integerValue)).to(equal(@2)); - - [terminal sendNext:nil]; - expect(@(a.integerValue)).to(equal(@5)); - }); - - qck_it(@"should use the nilValue when an intermediate object is nil", ^{ - __block BOOL wasDisposed = NO; - - RACChannelTo(a, weakTestObjectValue.integerValue, @5) = RACChannelTo(b, strongTestObjectValue.integerValue, @5); - b.strongTestObjectValue = [[RACTestObject alloc] init]; - - @autoreleasepool { - RACTestObject *object = [[RACTestObject alloc] init]; - [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - wasDisposed = YES; - }]]; - - a.weakTestObjectValue = object; - object.integerValue = 2; - - expect(@(wasDisposed)).to(beFalsy()); - expect(@(b.strongTestObjectValue.integerValue)).to(equal(@2)); - } - - expect(@(wasDisposed)).toEventually(beTruthy()); - expect(@(b.strongTestObjectValue.integerValue)).to(equal(@5)); - }); + __block RACTestObject *a; + __block RACTestObject *b; + __block RACTestObject *c; + __block NSString *testName1; + __block NSString *testName2; + __block NSString *testName3; + + qck_beforeEach(^{ + a = [[RACTestObject alloc] init]; + b = [[RACTestObject alloc] init]; + c = [[RACTestObject alloc] init]; + testName1 = @"sync it!"; + testName2 = @"sync it again!"; + testName3 = @"sync it once more!"; + }); + + qck_it(@"should keep objects' properties in sync", ^{ + RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue); + expect(a.stringValue).to(beNil()); + expect(b.stringValue).to(beNil()); + + a.stringValue = testName1; + expect(a.stringValue).to(equal(testName1)); + expect(b.stringValue).to(equal(testName1)); + + b.stringValue = testName2; + expect(a.stringValue).to(equal(testName2)); + expect(b.stringValue).to(equal(testName2)); + + a.stringValue = nil; + expect(a.stringValue).to(beNil()); + expect(b.stringValue).to(beNil()); + }); + + qck_it(@"should keep properties identified by keypaths in sync", ^{ + RACChannelTo(a, strongTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue); + a.strongTestObjectValue = [[RACTestObject alloc] init]; + b.strongTestObjectValue = [[RACTestObject alloc] init]; + + a.strongTestObjectValue.stringValue = testName1; + expect(a.strongTestObjectValue.stringValue).to(equal(testName1)); + expect(b.strongTestObjectValue.stringValue).to(equal(testName1)); + expect(a.strongTestObjectValue).notTo(equal(b.strongTestObjectValue)); + + b.strongTestObjectValue = nil; + expect(a.strongTestObjectValue.stringValue).to(beNil()); + + c.stringValue = testName2; + b.strongTestObjectValue = c; + expect(a.strongTestObjectValue.stringValue).to(equal(testName2)); + expect(b.strongTestObjectValue.stringValue).to(equal(testName2)); + expect(a.strongTestObjectValue).notTo(equal(b.strongTestObjectValue)); + }); + + qck_it(@"should update properties identified by keypaths when the intermediate values change", ^{ + RACChannelTo(a, strongTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue); + a.strongTestObjectValue = [[RACTestObject alloc] init]; + b.strongTestObjectValue = [[RACTestObject alloc] init]; + c.stringValue = testName1; + b.strongTestObjectValue = c; + + expect(a.strongTestObjectValue.stringValue).to(equal(testName1)); + expect(a.strongTestObjectValue).notTo(equal(b.strongTestObjectValue)); + }); + + qck_it(@"should update properties identified by keypaths when the channel was created when one of the two objects had an intermediate nil value", ^{ + RACChannelTo(a, strongTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue); + b.strongTestObjectValue = [[RACTestObject alloc] init]; + c.stringValue = testName1; + a.strongTestObjectValue = c; + + expect(a.strongTestObjectValue.stringValue).to(equal(testName1)); + expect(b.strongTestObjectValue.stringValue).to(equal(testName1)); + expect(a.strongTestObjectValue).notTo(equal(b.strongTestObjectValue)); + }); + + qck_it(@"should take the value of the object being bound to at the start", ^{ + a.stringValue = testName1; + b.stringValue = testName2; + + RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue); + expect(a.stringValue).to(equal(testName2)); + expect(b.stringValue).to(equal(testName2)); + }); + + qck_it(@"should update the value even if it's the same value the object had before it was bound", ^{ + a.stringValue = testName1; + b.stringValue = testName2; + + RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue); + expect(a.stringValue).to(equal(testName2)); + expect(b.stringValue).to(equal(testName2)); + + b.stringValue = testName1; + expect(a.stringValue).to(equal(testName1)); + expect(b.stringValue).to(equal(testName1)); + }); + + qck_it(@"should bind transitively", ^{ + a.stringValue = testName1; + b.stringValue = testName2; + c.stringValue = testName3; + + RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue); + RACChannelTo(b, stringValue) = RACChannelTo(c, stringValue); + expect(a.stringValue).to(equal(testName3)); + expect(b.stringValue).to(equal(testName3)); + expect(c.stringValue).to(equal(testName3)); + + c.stringValue = testName1; + expect(a.stringValue).to(equal(testName1)); + expect(b.stringValue).to(equal(testName1)); + expect(c.stringValue).to(equal(testName1)); + + b.stringValue = testName2; + expect(a.stringValue).to(equal(testName2)); + expect(b.stringValue).to(equal(testName2)); + expect(c.stringValue).to(equal(testName2)); + + a.stringValue = testName3; + expect(a.stringValue).to(equal(testName3)); + expect(b.stringValue).to(equal(testName3)); + expect(c.stringValue).to(equal(testName3)); + }); + + qck_it(@"should bind changes made by KVC on arrays", ^{ + b.arrayValue = @[]; + RACChannelTo(a, arrayValue) = RACChannelTo(b, arrayValue); + + [[b mutableArrayValueForKeyPath:@rac_keypath(b.arrayValue)] addObject:@1]; + expect(a.arrayValue).to(equal(b.arrayValue)); + }); + + qck_it(@"should bind changes made by KVC on sets", ^{ + b.setValue = [NSSet set]; + RACChannelTo(a, setValue) = RACChannelTo(b, setValue); + + [[b mutableSetValueForKeyPath:@rac_keypath(b.setValue)] addObject:@1]; + expect(a.setValue).to(equal(b.setValue)); + }); + + qck_it(@"should bind changes made by KVC on ordered sets", ^{ + b.orderedSetValue = [NSOrderedSet orderedSet]; + RACChannelTo(a, orderedSetValue) = RACChannelTo(b, orderedSetValue); + + [[b mutableOrderedSetValueForKeyPath:@rac_keypath(b.orderedSetValue)] addObject:@1]; + expect(a.orderedSetValue).to(equal(b.orderedSetValue)); + }); + + qck_it(@"should handle deallocation of intermediate objects correctly even without support from KVO", ^{ + __block BOOL wasDisposed = NO; + + RACChannelTo(a, weakTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue); + b.strongTestObjectValue = [[RACTestObject alloc] init]; + + @autoreleasepool { + RACTestObject *object = [[RACTestObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + wasDisposed = YES; + }]]; + + a.weakTestObjectValue = object; + object.stringValue = testName1; + + expect(@(wasDisposed)).to(beFalsy()); + expect(b.strongTestObjectValue.stringValue).to(equal(testName1)); + } + + expect(@(wasDisposed)).toEventually(beTruthy()); + expect(b.strongTestObjectValue.stringValue).to(beNil()); + }); + + qck_it(@"should stop binding when disposed", ^{ + RACChannelTerminal *aTerminal = RACChannelTo(a, stringValue); + RACChannelTerminal *bTerminal = RACChannelTo(b, stringValue); + + a.stringValue = testName1; + RACDisposable *disposable = [aTerminal subscribe:bTerminal]; + + expect(a.stringValue).to(equal(testName1)); + expect(b.stringValue).to(equal(testName1)); + + a.stringValue = testName2; + expect(a.stringValue).to(equal(testName2)); + expect(b.stringValue).to(equal(testName2)); + + [disposable dispose]; + + a.stringValue = testName3; + expect(a.stringValue).to(equal(testName3)); + expect(b.stringValue).to(equal(testName2)); + }); + + qck_it(@"should use the nilValue when sent nil", ^{ + RACChannelTerminal *terminal = RACChannelTo(a, integerValue, @5); + expect(@(a.integerValue)).to(equal(@0)); + + [terminal sendNext:@2]; + expect(@(a.integerValue)).to(equal(@2)); + + [terminal sendNext:nil]; + expect(@(a.integerValue)).to(equal(@5)); + }); + + qck_it(@"should use the nilValue when an intermediate object is nil", ^{ + __block BOOL wasDisposed = NO; + + RACChannelTo(a, weakTestObjectValue.integerValue, @5) = RACChannelTo(b, strongTestObjectValue.integerValue, @5); + b.strongTestObjectValue = [[RACTestObject alloc] init]; + + @autoreleasepool { + RACTestObject *object = [[RACTestObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + wasDisposed = YES; + }]]; + + a.weakTestObjectValue = object; + object.integerValue = 2; + + expect(@(wasDisposed)).to(beFalsy()); + expect(@(b.strongTestObjectValue.integerValue)).to(equal(@2)); + } + + expect(@(wasDisposed)).toEventually(beTruthy()); + expect(@(b.strongTestObjectValue.integerValue)).to(equal(@5)); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACKVOProxySpec.m b/ReactiveObjCTests/RACKVOProxySpec.m index d2d3b5265..f11ce5f45 100644 --- a/ReactiveObjCTests/RACKVOProxySpec.m +++ b/ReactiveObjCTests/RACKVOProxySpec.m @@ -20,8 +20,8 @@ #import "RACSubject.h" @interface TestObject : NSObject { - volatile int _testInt; - pthread_mutex_t _mutex; + volatile int _testInt; + pthread_mutex_t _mutex; } @property (assign, atomic) int testInt; @@ -31,33 +31,33 @@ @interface TestObject : NSObject { @implementation TestObject - (instancetype)init { - if ((self = [super init])) { - pthread_mutex_init(&_mutex, nil); - } + if ((self = [super init])) { + pthread_mutex_init(&_mutex, nil); + } - return self; + return self; } - (int)testInt { - int test = 0; - pthread_mutex_lock(&_mutex); - test = _testInt; - pthread_mutex_unlock(&_mutex); - return test; + int test = 0; + pthread_mutex_lock(&_mutex); + test = _testInt; + pthread_mutex_unlock(&_mutex); + return test; } // Use manual KVO notifications to avoid any possible race conditions within the // automatic KVO implementation. - (void)setTestInt:(int)value { - [self willChangeValueForKey:@rac_keypath(self.testInt)]; - pthread_mutex_lock(&_mutex); - _testInt = value; - pthread_mutex_unlock(&_mutex); - [self didChangeValueForKey:@rac_keypath(self.testInt)]; + [self willChangeValueForKey:@rac_keypath(self.testInt)]; + pthread_mutex_lock(&_mutex); + _testInt = value; + pthread_mutex_unlock(&_mutex); + [self didChangeValueForKey:@rac_keypath(self.testInt)]; } + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { - return NO; + return NO; } @end @@ -65,173 +65,173 @@ + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { QuickSpecBegin(RACKVOProxySpec) qck_describe(@"RACKVOProxy", ^{ - __block TestObject *testObject; - __block dispatch_queue_t concurrentQueue; - - qck_beforeEach(^{ - testObject = [[TestObject alloc] init]; - concurrentQueue = dispatch_queue_create("org.reactivecocoa.ReactiveObjC.RACKVOProxySpec.concurrentQueue", DISPATCH_QUEUE_CONCURRENT); - }); - - qck_afterEach(^{ - dispatch_barrier_sync(concurrentQueue, ^{ - testObject = nil; - }); - }); - - qck_describe(@"basic", ^{ - qck_it(@"should handle multiple observations on the same value", ^{ - __block int observedValue1 = 0; - __block int observedValue2 = 0; - - [[[RACObserve(testObject, testInt) - skip:1] - take:1] - subscribeNext:^(NSNumber *wrappedInt) { - observedValue1 = wrappedInt.intValue; - }]; - - [[[RACObserve(testObject, testInt) - skip:1] - take:1] - subscribeNext:^(NSNumber *wrappedInt) { - observedValue2 = wrappedInt.intValue; - }]; - - testObject.testInt = 2; - - expect(@(observedValue1)).toEventually(equal(@2)); - expect(@(observedValue2)).toEventually(equal(@2)); - }); - - qck_it(@"can remove individual observation", ^{ - __block int observedValue1 = 0; - __block int observedValue2 = 0; - - RACDisposable *disposable1 = [RACObserve(testObject, testInt) subscribeNext:^(NSNumber *wrappedInt) { - observedValue1 = wrappedInt.intValue; - }]; - - [RACObserve(testObject, testInt) subscribeNext:^(NSNumber *wrappedInt) { - observedValue2 = wrappedInt.intValue; - }]; - - testObject.testInt = 2; - - expect(@(observedValue1)).toEventually(equal(@2)); - expect(@(observedValue2)).toEventually(equal(@2)); - - [disposable1 dispose]; - testObject.testInt = 3; - - expect(@(observedValue2)).toEventually(equal(@3)); - expect(@(observedValue1)).to(equal(@2)); - }); - }); - - qck_describe(@"async", ^{ - qck_it(@"should handle changes being made on another queue", ^{ - __block int observedValue = 0; - [[[RACObserve(testObject, testInt) - skip:1] - take:1] - subscribeNext:^(NSNumber *wrappedInt) { - observedValue = wrappedInt.intValue; - }]; - - dispatch_async(concurrentQueue, ^{ - testObject.testInt = 2; - }); - - dispatch_barrier_sync(concurrentQueue, ^{}); - expect(@(observedValue)).toEventually(equal(@2)); - }); - - qck_it(@"should handle changes being made on another queue using deliverOn", ^{ - __block int observedValue = 0; - [[[[RACObserve(testObject, testInt) - skip:1] - take:1] - deliverOn:[RACScheduler mainThreadScheduler]] - subscribeNext:^(NSNumber *wrappedInt) { - observedValue = wrappedInt.intValue; - }]; - - dispatch_async(concurrentQueue, ^{ - testObject.testInt = 2; - }); - - dispatch_barrier_sync(concurrentQueue, ^{}); - expect(@(observedValue)).toEventually(equal(@2)); - }); - - qck_it(@"async disposal of target", ^{ - __block int observedValue; - [[RACObserve(testObject, testInt) - deliverOn:RACScheduler.mainThreadScheduler] - subscribeNext:^(NSNumber *wrappedInt) { - observedValue = wrappedInt.intValue; - }]; - - dispatch_async(concurrentQueue, ^{ - testObject.testInt = 2; - testObject = nil; - }); - - dispatch_barrier_sync(concurrentQueue, ^{}); - expect(@(observedValue)).toEventually(equal(@2)); - }); - }); - - qck_describe(@"stress", ^{ - static const size_t numIterations = 5000; - - __block dispatch_queue_t iterationQueue; - - beforeEach(^{ - iterationQueue = dispatch_queue_create("org.reactivecocoa.ReactiveObjC.RACKVOProxySpec.iterationQueue", DISPATCH_QUEUE_CONCURRENT); - }); - - // ReactiveCocoa/ReactiveCocoa#1122 - qck_it(@"async disposal of observer", ^{ - RACSerialDisposable *disposable = [[RACSerialDisposable alloc] init]; - - dispatch_apply(numIterations, iterationQueue, ^(size_t index) { - RACDisposable *newDisposable = [RACObserve(testObject, testInt) subscribeCompleted:^{}]; - [[disposable swapInDisposable:newDisposable] dispose]; - - dispatch_async(concurrentQueue, ^{ - testObject.testInt = (int)index; - }); - }); - - dispatch_barrier_sync(iterationQueue, ^{ - [disposable dispose]; - }); - }); - - qck_it(@"async disposal of signal with in-flight changes", ^{ - RACSubject *teardown = [RACSubject subject]; - - RACSignal *isEvenSignal = [[[[RACObserve(testObject, testInt) - map:^(NSNumber *wrappedInt) { - return @((wrappedInt.intValue % 2) == 0); - }] - deliverOn:RACScheduler.mainThreadScheduler] - takeUntil:teardown] - replayLast]; - - dispatch_apply(numIterations, iterationQueue, ^(size_t index) { - testObject.testInt = (int)index; - }); - - dispatch_barrier_async(iterationQueue, ^{ - [teardown sendNext:nil]; - }); - - expect(@([isEvenSignal asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); - }); - }); + __block TestObject *testObject; + __block dispatch_queue_t concurrentQueue; + + qck_beforeEach(^{ + testObject = [[TestObject alloc] init]; + concurrentQueue = dispatch_queue_create("org.reactivecocoa.ReactiveObjC.RACKVOProxySpec.concurrentQueue", DISPATCH_QUEUE_CONCURRENT); + }); + + qck_afterEach(^{ + dispatch_barrier_sync(concurrentQueue, ^{ + testObject = nil; + }); + }); + + qck_describe(@"basic", ^{ + qck_it(@"should handle multiple observations on the same value", ^{ + __block int observedValue1 = 0; + __block int observedValue2 = 0; + + [[[RACObserve(testObject, testInt) + skip:1] + take:1] + subscribeNext:^(NSNumber *wrappedInt) { + observedValue1 = wrappedInt.intValue; + }]; + + [[[RACObserve(testObject, testInt) + skip:1] + take:1] + subscribeNext:^(NSNumber *wrappedInt) { + observedValue2 = wrappedInt.intValue; + }]; + + testObject.testInt = 2; + + expect(@(observedValue1)).toEventually(equal(@2)); + expect(@(observedValue2)).toEventually(equal(@2)); + }); + + qck_it(@"can remove individual observation", ^{ + __block int observedValue1 = 0; + __block int observedValue2 = 0; + + RACDisposable *disposable1 = [RACObserve(testObject, testInt) subscribeNext:^(NSNumber *wrappedInt) { + observedValue1 = wrappedInt.intValue; + }]; + + [RACObserve(testObject, testInt) subscribeNext:^(NSNumber *wrappedInt) { + observedValue2 = wrappedInt.intValue; + }]; + + testObject.testInt = 2; + + expect(@(observedValue1)).toEventually(equal(@2)); + expect(@(observedValue2)).toEventually(equal(@2)); + + [disposable1 dispose]; + testObject.testInt = 3; + + expect(@(observedValue2)).toEventually(equal(@3)); + expect(@(observedValue1)).to(equal(@2)); + }); + }); + + qck_describe(@"async", ^{ + qck_it(@"should handle changes being made on another queue", ^{ + __block int observedValue = 0; + [[[RACObserve(testObject, testInt) + skip:1] + take:1] + subscribeNext:^(NSNumber *wrappedInt) { + observedValue = wrappedInt.intValue; + }]; + + dispatch_async(concurrentQueue, ^{ + testObject.testInt = 2; + }); + + dispatch_barrier_sync(concurrentQueue, ^{}); + expect(@(observedValue)).toEventually(equal(@2)); + }); + + qck_it(@"should handle changes being made on another queue using deliverOn", ^{ + __block int observedValue = 0; + [[[[RACObserve(testObject, testInt) + skip:1] + take:1] + deliverOn:[RACScheduler mainThreadScheduler]] + subscribeNext:^(NSNumber *wrappedInt) { + observedValue = wrappedInt.intValue; + }]; + + dispatch_async(concurrentQueue, ^{ + testObject.testInt = 2; + }); + + dispatch_barrier_sync(concurrentQueue, ^{}); + expect(@(observedValue)).toEventually(equal(@2)); + }); + + qck_it(@"async disposal of target", ^{ + __block int observedValue; + [[RACObserve(testObject, testInt) + deliverOn:RACScheduler.mainThreadScheduler] + subscribeNext:^(NSNumber *wrappedInt) { + observedValue = wrappedInt.intValue; + }]; + + dispatch_async(concurrentQueue, ^{ + testObject.testInt = 2; + testObject = nil; + }); + + dispatch_barrier_sync(concurrentQueue, ^{}); + expect(@(observedValue)).toEventually(equal(@2)); + }); + }); + + qck_describe(@"stress", ^{ + static const size_t numIterations = 5000; + + __block dispatch_queue_t iterationQueue; + + beforeEach(^{ + iterationQueue = dispatch_queue_create("org.reactivecocoa.ReactiveObjC.RACKVOProxySpec.iterationQueue", DISPATCH_QUEUE_CONCURRENT); + }); + + // ReactiveCocoa/ReactiveCocoa#1122 + qck_it(@"async disposal of observer", ^{ + RACSerialDisposable *disposable = [[RACSerialDisposable alloc] init]; + + dispatch_apply(numIterations, iterationQueue, ^(size_t index) { + RACDisposable *newDisposable = [RACObserve(testObject, testInt) subscribeCompleted:^{}]; + [[disposable swapInDisposable:newDisposable] dispose]; + + dispatch_async(concurrentQueue, ^{ + testObject.testInt = (int)index; + }); + }); + + dispatch_barrier_sync(iterationQueue, ^{ + [disposable dispose]; + }); + }); + + qck_it(@"async disposal of signal with in-flight changes", ^{ + RACSubject *teardown = [RACSubject subject]; + + RACSignal *isEvenSignal = [[[[RACObserve(testObject, testInt) + map:^(NSNumber *wrappedInt) { + return @((wrappedInt.intValue % 2) == 0); + }] + deliverOn:RACScheduler.mainThreadScheduler] + takeUntil:teardown] + replayLast]; + + dispatch_apply(numIterations, iterationQueue, ^(size_t index) { + testObject.testInt = (int)index; + }); + + dispatch_barrier_async(iterationQueue, ^{ + [teardown sendNext:nil]; + }); + + expect(@([isEvenSignal asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); + }); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACKVOWrapperSpec.m b/ReactiveObjCTests/RACKVOWrapperSpec.m index f069e157c..eb17d548d 100644 --- a/ReactiveObjCTests/RACKVOWrapperSpec.m +++ b/ReactiveObjCTests/RACKVOWrapperSpec.m @@ -60,259 +60,259 @@ @interface RACTestOperation : NSOperation QuickConfigurationBegin(RACKVOWrapperExampleGroups) + (void)configure:(Configuration *)configuration { - sharedExamples(RACKVOWrapperExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block NSObject *target = nil; - __block NSString *keyPath = nil; - __block void (^changeBlock)(NSObject *, id) = nil; - __block id (^valueBlock)(void) = nil; - __block BOOL changesValueDirectly = NO; - - __block NSUInteger priorCallCount = 0; - __block NSUInteger posteriorCallCount = 0; - __block BOOL priorTriggeredByLastKeyPathComponent = NO; - __block BOOL posteriorTriggeredByLastKeyPathComponent = NO; - __block BOOL posteriorTriggeredByDeallocation = NO; - __block void (^callbackBlock)(id, NSDictionary *, BOOL, BOOL) = nil; - - qck_beforeEach(^{ - NSObject * (^targetBlock)(void) = exampleContext()[RACKVOWrapperExamplesTargetBlock]; - target = targetBlock(); - keyPath = exampleContext()[RACKVOWrapperExamplesKeyPath]; - changeBlock = exampleContext()[RACKVOWrapperExamplesChangeBlock]; - valueBlock = exampleContext()[RACKVOWrapperExamplesValueBlock]; - changesValueDirectly = [exampleContext()[RACKVOWrapperExamplesChangesValueDirectly] boolValue]; - - priorCallCount = 0; - posteriorCallCount = 0; - - callbackBlock = [^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { - priorTriggeredByLastKeyPathComponent = affectedOnlyLastComponent; - ++priorCallCount; - return; - } - posteriorTriggeredByLastKeyPathComponent = affectedOnlyLastComponent; - posteriorTriggeredByDeallocation = causedByDealloc; - ++posteriorCallCount; - } copy]; - }); - - qck_afterEach(^{ - target = nil; - keyPath = nil; - changeBlock = nil; - valueBlock = nil; - changesValueDirectly = NO; - - callbackBlock = nil; - }); - - qck_it(@"should not call the callback block on add if called without NSKeyValueObservingOptionInitial", ^{ - [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; - expect(@(priorCallCount)).to(equal(@0)); - expect(@(posteriorCallCount)).to(equal(@0)); - }); - - qck_it(@"should call the callback block on add if called with NSKeyValueObservingOptionInitial", ^{ - [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior | NSKeyValueObservingOptionInitial observer:nil block:callbackBlock]; - expect(@(priorCallCount)).to(equal(@0)); - expect(@(posteriorCallCount)).to(equal(@1)); - }); - - qck_it(@"should call the callback block twice per change, once prior and once posterior", ^{ - [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; - priorCallCount = 0; - posteriorCallCount = 0; - - id value1 = valueBlock(); - changeBlock(target, value1); - expect(@(priorCallCount)).to(equal(@1)); - expect(@(posteriorCallCount)).to(equal(@1)); - expect(@(priorTriggeredByLastKeyPathComponent)).to(equal(@(changesValueDirectly))); - expect(@(posteriorTriggeredByLastKeyPathComponent)).to(equal(@(changesValueDirectly))); - expect(@(posteriorTriggeredByDeallocation)).to(beFalsy()); - - id value2 = valueBlock(); - changeBlock(target, value2); - expect(@(priorCallCount)).to(equal(@2)); - expect(@(posteriorCallCount)).to(equal(@2)); - expect(@(priorTriggeredByLastKeyPathComponent)).to(equal(@(changesValueDirectly))); - expect(@(posteriorTriggeredByLastKeyPathComponent)).to(equal(@(changesValueDirectly))); - expect(@(posteriorTriggeredByDeallocation)).to(beFalsy()); - }); - - qck_it(@"should call the callback block with NSKeyValueChangeNotificationIsPriorKey set before the value is changed, and not set after the value is changed", ^{ - __block BOOL priorCalled = NO; - __block BOOL posteriorCalled = NO; - __block id priorValue = nil; - __block id posteriorValue = nil; - - id value1 = valueBlock(); - changeBlock(target, value1); - id oldValue = [target valueForKeyPath:keyPath]; - - [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { - priorCalled = YES; - priorValue = value; - expect(@(posteriorCalled)).to(beFalsy()); - return; - } - posteriorCalled = YES; - posteriorValue = value; - expect(@(priorCalled)).to(beTruthy()); - }]; - - id value2 = valueBlock(); - changeBlock(target, value2); - id newValue = [target valueForKeyPath:keyPath]; - expect(@(priorCalled)).to(beTruthy()); - expect(priorValue).to(equal(oldValue)); - expect(@(posteriorCalled)).to(beTruthy()); - expect(posteriorValue).to(equal(newValue)); - }); - - qck_it(@"should not call the callback block after it's been disposed", ^{ - RACDisposable *disposable = [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; - priorCallCount = 0; - posteriorCallCount = 0; - - [disposable dispose]; - expect(@(priorCallCount)).to(equal(@0)); - expect(@(posteriorCallCount)).to(equal(@0)); - - id value = valueBlock(); - changeBlock(target, value); - expect(@(priorCallCount)).to(equal(@0)); - expect(@(posteriorCallCount)).to(equal(@0)); - }); - - qck_it(@"should call the callback block only once with NSKeyValueChangeNotificationIsPriorKey not set when the value is deallocated", ^{ - __block BOOL valueDidDealloc = NO; - - [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; - - @autoreleasepool { - NSObject *value __attribute__((objc_precise_lifetime)) = valueBlock(); - [value.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - valueDidDealloc = YES; - }]]; - - changeBlock(target, value); - priorCallCount = 0; - posteriorCallCount = 0; - } - - expect(@(valueDidDealloc)).to(beTruthy()); - expect(@(priorCallCount)).to(equal(@0)); - expect(@(posteriorCallCount)).to(equal(@1)); - expect(@(posteriorTriggeredByDeallocation)).to(beTruthy()); - }); - }); - - qck_sharedExamples(RACKVOWrapperCollectionExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block NSObject *target = nil; - __block NSString *keyPath = nil; - __block NSMutableOrderedSet *mutableKeyPathProxy = nil; - __block void (^callbackBlock)(id, NSDictionary *, BOOL, BOOL) = nil; - - __block id priorValue = nil; - __block id posteriorValue = nil; - __block NSDictionary *priorChange = nil; - __block NSDictionary *posteriorChange = nil; - - qck_beforeEach(^{ - NSObject * (^targetBlock)(void) = exampleContext()[RACKVOWrapperCollectionExamplesTargetBlock]; - target = targetBlock(); - keyPath = exampleContext()[RACKVOWrapperCollectionExamplesKeyPath]; - - callbackBlock = [^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { - priorValue = value; - priorChange = change; - return; - } - posteriorValue = value; - posteriorChange = change; - } copy]; - - [target setValue:[NSOrderedSet orderedSetWithObject:@0] forKeyPath:keyPath]; - [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; - mutableKeyPathProxy = [target mutableOrderedSetValueForKeyPath:keyPath]; - }); - - qck_afterEach(^{ - target = nil; - keyPath = nil; - callbackBlock = nil; - - priorValue = nil; - priorChange = nil; - posteriorValue = nil; - posteriorChange = nil; - }); - - qck_it(@"should support inserting elements into ordered collections", ^{ - [mutableKeyPathProxy insertObject:@1 atIndex:0]; - - expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]])); - expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:(@[ @1, @0 ])])); - expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeInsertion))); - expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeInsertion))); - expect(priorChange[NSKeyValueChangeOldKey]).to(beNil()); - expect(posteriorChange[NSKeyValueChangeNewKey]).to(equal(@[ @1 ])); - expect(priorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0])); - expect(posteriorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0])); - }); - - qck_it(@"should support removing elements from ordered collections", ^{ - [mutableKeyPathProxy removeObjectAtIndex:0]; - - expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]])); - expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:@[]])); - expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeRemoval))); - expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeRemoval))); - expect(priorChange[NSKeyValueChangeOldKey]).to(equal(@[ @0 ])); - expect(posteriorChange[NSKeyValueChangeNewKey]).to(beNil()); - expect(priorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0])); - expect(posteriorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0])); - }); - - qck_it(@"should support replacing elements in ordered collections", ^{ - [mutableKeyPathProxy replaceObjectAtIndex:0 withObject:@1]; - - expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]])); - expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @1 ]])); - expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeReplacement))); - expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeReplacement))); - expect(priorChange[NSKeyValueChangeOldKey]).to(equal(@[ @0 ])); - expect(posteriorChange[NSKeyValueChangeNewKey]).to(equal(@[ @1 ])); - expect(priorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0])); - expect(posteriorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0])); - }); - - qck_it(@"should support adding elements to unordered collections", ^{ - [mutableKeyPathProxy unionOrderedSet:[NSOrderedSet orderedSetWithObject:@1]]; - - expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]])); - expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:(@[ @0, @1 ])])); - expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeInsertion))); - expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeInsertion))); - expect(priorChange[NSKeyValueChangeOldKey]).to(beNil()); - expect(posteriorChange[NSKeyValueChangeNewKey]).to(equal(@[ @1 ])); - }); - - qck_it(@"should support removing elements from unordered collections", ^{ - [mutableKeyPathProxy minusOrderedSet:[NSOrderedSet orderedSetWithObject:@0]]; - - expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]])); - expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:@[]])); - expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeRemoval))); - expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeRemoval))); - expect(priorChange[NSKeyValueChangeOldKey]).to(equal(@[ @0 ])); - expect(posteriorChange[NSKeyValueChangeNewKey]).to(beNil()); - }); - }); + sharedExamples(RACKVOWrapperExamples, ^(QCKDSLSharedExampleContext exampleContext) { + __block NSObject *target = nil; + __block NSString *keyPath = nil; + __block void (^changeBlock)(NSObject *, id) = nil; + __block id (^valueBlock)(void) = nil; + __block BOOL changesValueDirectly = NO; + + __block NSUInteger priorCallCount = 0; + __block NSUInteger posteriorCallCount = 0; + __block BOOL priorTriggeredByLastKeyPathComponent = NO; + __block BOOL posteriorTriggeredByLastKeyPathComponent = NO; + __block BOOL posteriorTriggeredByDeallocation = NO; + __block void (^callbackBlock)(id, NSDictionary *, BOOL, BOOL) = nil; + + qck_beforeEach(^{ + NSObject * (^targetBlock)(void) = exampleContext()[RACKVOWrapperExamplesTargetBlock]; + target = targetBlock(); + keyPath = exampleContext()[RACKVOWrapperExamplesKeyPath]; + changeBlock = exampleContext()[RACKVOWrapperExamplesChangeBlock]; + valueBlock = exampleContext()[RACKVOWrapperExamplesValueBlock]; + changesValueDirectly = [exampleContext()[RACKVOWrapperExamplesChangesValueDirectly] boolValue]; + + priorCallCount = 0; + posteriorCallCount = 0; + + callbackBlock = [^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { + priorTriggeredByLastKeyPathComponent = affectedOnlyLastComponent; + ++priorCallCount; + return; + } + posteriorTriggeredByLastKeyPathComponent = affectedOnlyLastComponent; + posteriorTriggeredByDeallocation = causedByDealloc; + ++posteriorCallCount; + } copy]; + }); + + qck_afterEach(^{ + target = nil; + keyPath = nil; + changeBlock = nil; + valueBlock = nil; + changesValueDirectly = NO; + + callbackBlock = nil; + }); + + qck_it(@"should not call the callback block on add if called without NSKeyValueObservingOptionInitial", ^{ + [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; + expect(@(priorCallCount)).to(equal(@0)); + expect(@(posteriorCallCount)).to(equal(@0)); + }); + + qck_it(@"should call the callback block on add if called with NSKeyValueObservingOptionInitial", ^{ + [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior | NSKeyValueObservingOptionInitial observer:nil block:callbackBlock]; + expect(@(priorCallCount)).to(equal(@0)); + expect(@(posteriorCallCount)).to(equal(@1)); + }); + + qck_it(@"should call the callback block twice per change, once prior and once posterior", ^{ + [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; + priorCallCount = 0; + posteriorCallCount = 0; + + id value1 = valueBlock(); + changeBlock(target, value1); + expect(@(priorCallCount)).to(equal(@1)); + expect(@(posteriorCallCount)).to(equal(@1)); + expect(@(priorTriggeredByLastKeyPathComponent)).to(equal(@(changesValueDirectly))); + expect(@(posteriorTriggeredByLastKeyPathComponent)).to(equal(@(changesValueDirectly))); + expect(@(posteriorTriggeredByDeallocation)).to(beFalsy()); + + id value2 = valueBlock(); + changeBlock(target, value2); + expect(@(priorCallCount)).to(equal(@2)); + expect(@(posteriorCallCount)).to(equal(@2)); + expect(@(priorTriggeredByLastKeyPathComponent)).to(equal(@(changesValueDirectly))); + expect(@(posteriorTriggeredByLastKeyPathComponent)).to(equal(@(changesValueDirectly))); + expect(@(posteriorTriggeredByDeallocation)).to(beFalsy()); + }); + + qck_it(@"should call the callback block with NSKeyValueChangeNotificationIsPriorKey set before the value is changed, and not set after the value is changed", ^{ + __block BOOL priorCalled = NO; + __block BOOL posteriorCalled = NO; + __block id priorValue = nil; + __block id posteriorValue = nil; + + id value1 = valueBlock(); + changeBlock(target, value1); + id oldValue = [target valueForKeyPath:keyPath]; + + [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { + priorCalled = YES; + priorValue = value; + expect(@(posteriorCalled)).to(beFalsy()); + return; + } + posteriorCalled = YES; + posteriorValue = value; + expect(@(priorCalled)).to(beTruthy()); + }]; + + id value2 = valueBlock(); + changeBlock(target, value2); + id newValue = [target valueForKeyPath:keyPath]; + expect(@(priorCalled)).to(beTruthy()); + expect(priorValue).to(equal(oldValue)); + expect(@(posteriorCalled)).to(beTruthy()); + expect(posteriorValue).to(equal(newValue)); + }); + + qck_it(@"should not call the callback block after it's been disposed", ^{ + RACDisposable *disposable = [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; + priorCallCount = 0; + posteriorCallCount = 0; + + [disposable dispose]; + expect(@(priorCallCount)).to(equal(@0)); + expect(@(posteriorCallCount)).to(equal(@0)); + + id value = valueBlock(); + changeBlock(target, value); + expect(@(priorCallCount)).to(equal(@0)); + expect(@(posteriorCallCount)).to(equal(@0)); + }); + + qck_it(@"should call the callback block only once with NSKeyValueChangeNotificationIsPriorKey not set when the value is deallocated", ^{ + __block BOOL valueDidDealloc = NO; + + [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; + + @autoreleasepool { + NSObject *value __attribute__((objc_precise_lifetime)) = valueBlock(); + [value.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + valueDidDealloc = YES; + }]]; + + changeBlock(target, value); + priorCallCount = 0; + posteriorCallCount = 0; + } + + expect(@(valueDidDealloc)).to(beTruthy()); + expect(@(priorCallCount)).to(equal(@0)); + expect(@(posteriorCallCount)).to(equal(@1)); + expect(@(posteriorTriggeredByDeallocation)).to(beTruthy()); + }); + }); + + qck_sharedExamples(RACKVOWrapperCollectionExamples, ^(QCKDSLSharedExampleContext exampleContext) { + __block NSObject *target = nil; + __block NSString *keyPath = nil; + __block NSMutableOrderedSet *mutableKeyPathProxy = nil; + __block void (^callbackBlock)(id, NSDictionary *, BOOL, BOOL) = nil; + + __block id priorValue = nil; + __block id posteriorValue = nil; + __block NSDictionary *priorChange = nil; + __block NSDictionary *posteriorChange = nil; + + qck_beforeEach(^{ + NSObject * (^targetBlock)(void) = exampleContext()[RACKVOWrapperCollectionExamplesTargetBlock]; + target = targetBlock(); + keyPath = exampleContext()[RACKVOWrapperCollectionExamplesKeyPath]; + + callbackBlock = [^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { + priorValue = value; + priorChange = change; + return; + } + posteriorValue = value; + posteriorChange = change; + } copy]; + + [target setValue:[NSOrderedSet orderedSetWithObject:@0] forKeyPath:keyPath]; + [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; + mutableKeyPathProxy = [target mutableOrderedSetValueForKeyPath:keyPath]; + }); + + qck_afterEach(^{ + target = nil; + keyPath = nil; + callbackBlock = nil; + + priorValue = nil; + priorChange = nil; + posteriorValue = nil; + posteriorChange = nil; + }); + + qck_it(@"should support inserting elements into ordered collections", ^{ + [mutableKeyPathProxy insertObject:@1 atIndex:0]; + + expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]])); + expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:(@[ @1, @0 ])])); + expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeInsertion))); + expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeInsertion))); + expect(priorChange[NSKeyValueChangeOldKey]).to(beNil()); + expect(posteriorChange[NSKeyValueChangeNewKey]).to(equal(@[ @1 ])); + expect(priorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0])); + expect(posteriorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0])); + }); + + qck_it(@"should support removing elements from ordered collections", ^{ + [mutableKeyPathProxy removeObjectAtIndex:0]; + + expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]])); + expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:@[]])); + expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeRemoval))); + expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeRemoval))); + expect(priorChange[NSKeyValueChangeOldKey]).to(equal(@[ @0 ])); + expect(posteriorChange[NSKeyValueChangeNewKey]).to(beNil()); + expect(priorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0])); + expect(posteriorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0])); + }); + + qck_it(@"should support replacing elements in ordered collections", ^{ + [mutableKeyPathProxy replaceObjectAtIndex:0 withObject:@1]; + + expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]])); + expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @1 ]])); + expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeReplacement))); + expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeReplacement))); + expect(priorChange[NSKeyValueChangeOldKey]).to(equal(@[ @0 ])); + expect(posteriorChange[NSKeyValueChangeNewKey]).to(equal(@[ @1 ])); + expect(priorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0])); + expect(posteriorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0])); + }); + + qck_it(@"should support adding elements to unordered collections", ^{ + [mutableKeyPathProxy unionOrderedSet:[NSOrderedSet orderedSetWithObject:@1]]; + + expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]])); + expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:(@[ @0, @1 ])])); + expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeInsertion))); + expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeInsertion))); + expect(priorChange[NSKeyValueChangeOldKey]).to(beNil()); + expect(posteriorChange[NSKeyValueChangeNewKey]).to(equal(@[ @1 ])); + }); + + qck_it(@"should support removing elements from unordered collections", ^{ + [mutableKeyPathProxy minusOrderedSet:[NSOrderedSet orderedSetWithObject:@0]]; + + expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]])); + expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:@[]])); + expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeRemoval))); + expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeRemoval))); + expect(priorChange[NSKeyValueChangeOldKey]).to(equal(@[ @0 ])); + expect(posteriorChange[NSKeyValueChangeNewKey]).to(beNil()); + }); + }); } QuickConfigurationEnd @@ -320,353 +320,353 @@ + (void)configure:(Configuration *)configuration { QuickSpecBegin(RACKVOWrapperSpec) qck_describe(@"-rac_observeKeyPath:options:observer:block:", ^{ - qck_describe(@"on simple keys", ^{ - NSObject * (^targetBlock)(void) = ^{ - return [[RACTestObject alloc] init]; - }; - - void (^changeBlock)(RACTestObject *, id) = ^(RACTestObject *target, id value) { - target.weakTestObjectValue = value; - }; - - id (^valueBlock)(void) = ^{ - return [[RACTestObject alloc] init]; - }; - - qck_itBehavesLike(RACKVOWrapperExamples, ^{ - return @{ - RACKVOWrapperExamplesTargetBlock: targetBlock, - RACKVOWrapperExamplesKeyPath: @rac_keypath(RACTestObject.new, weakTestObjectValue), - RACKVOWrapperExamplesChangeBlock: changeBlock, - RACKVOWrapperExamplesValueBlock: valueBlock, - RACKVOWrapperExamplesChangesValueDirectly: @YES - }; - }); - - qck_itBehavesLike(RACKVOWrapperCollectionExamples, ^{ - return @{ - RACKVOWrapperCollectionExamplesTargetBlock: targetBlock, - RACKVOWrapperCollectionExamplesKeyPath: @rac_keypath(RACTestObject.new, orderedSetValue) - }; - }); - }); - - qck_describe(@"on composite key paths'", ^{ - qck_describe(@"last key path components", ^{ - NSObject *(^targetBlock)(void) = ^{ - RACTestObject *object = [[RACTestObject alloc] init]; - object.strongTestObjectValue = [[RACTestObject alloc] init]; - return object; - }; - - void (^changeBlock)(RACTestObject *, id) = ^(RACTestObject *target, id value) { - target.strongTestObjectValue.weakTestObjectValue = value; - }; - - id (^valueBlock)(void) = ^{ - return [[RACTestObject alloc] init]; - }; - - qck_itBehavesLike(RACKVOWrapperExamples, ^{ - return @{ - RACKVOWrapperExamplesTargetBlock: targetBlock, - RACKVOWrapperExamplesKeyPath: @rac_keypath(RACTestObject.new, strongTestObjectValue.weakTestObjectValue), - RACKVOWrapperExamplesChangeBlock: changeBlock, - RACKVOWrapperExamplesValueBlock: valueBlock, - RACKVOWrapperExamplesChangesValueDirectly: @YES - }; - }); - - qck_itBehavesLike(RACKVOWrapperCollectionExamples, ^{ - return @{ - RACKVOWrapperCollectionExamplesTargetBlock: targetBlock, - RACKVOWrapperCollectionExamplesKeyPath: @rac_keypath(RACTestObject.new, strongTestObjectValue.orderedSetValue) - }; - }); - }); - - qck_describe(@"intermediate key path components", ^{ - NSObject *(^targetBlock)(void) = ^{ - return [[RACTestObject alloc] init]; - }; - - void (^changeBlock)(RACTestObject *, id) = ^(RACTestObject *target, id value) { - target.weakTestObjectValue = value; - }; - - id (^valueBlock)(void) = ^{ - RACTestObject *object = [[RACTestObject alloc] init]; - object.strongTestObjectValue = [[RACTestObject alloc] init]; - return object; - }; - - qck_itBehavesLike(RACKVOWrapperExamples, ^{ - return @{ - RACKVOWrapperExamplesTargetBlock: targetBlock, - RACKVOWrapperExamplesKeyPath: @rac_keypath([[RACTestObject alloc] init], weakTestObjectValue.strongTestObjectValue), - RACKVOWrapperExamplesChangeBlock: changeBlock, - RACKVOWrapperExamplesValueBlock: valueBlock, - RACKVOWrapperExamplesChangesValueDirectly: @NO - }; - }); - }); - - qck_it(@"should not notice deallocation of the object returned by a dynamic final property", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; - - __block id lastValue = nil; - @autoreleasepool { - [object rac_observeKeyPath:@rac_keypath(object.dynamicObjectProperty) options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - lastValue = value; - }]; - - expect(lastValue).to(beAKindOf(RACTestObject.class)); - } - - expect(lastValue).to(beAKindOf(RACTestObject.class)); - }); - - qck_it(@"should not notice deallocation of the object returned by a dynamic intermediate property", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; - - __block id lastValue = nil; - @autoreleasepool { - [object rac_observeKeyPath:@rac_keypath(object.dynamicObjectProperty.integerValue) options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - lastValue = value; - }]; - - expect(lastValue).to(equal(@42)); - } - - expect(lastValue).to(equal(@42)); - }); - - qck_it(@"should not notice deallocation of the object returned by a dynamic method", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; - - __block id lastValue = nil; - @autoreleasepool { - [object rac_observeKeyPath:@rac_keypath(object.dynamicObjectMethod) options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - lastValue = value; - }]; - - expect(lastValue).to(beAKindOf(RACTestObject.class)); - } - - expect(lastValue).to(beAKindOf(RACTestObject.class)); - }); - }); - - qck_it(@"should not call the callback block when the value is the observer", ^{ - __block BOOL observerDisposed = NO; - __block BOOL observerDeallocationTriggeredChange = NO; - __block BOOL targetDisposed = NO; - __block BOOL targetDeallocationTriggeredChange = NO; - - @autoreleasepool { - RACTestObject *observer __attribute__((objc_precise_lifetime)) = [RACTestObject new]; - [observer.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - observerDisposed = YES; - }]]; - - RACTestObject *target __attribute__((objc_precise_lifetime)) = [RACTestObject new]; - [target.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - targetDisposed = YES; - }]]; - - observer.weakTestObjectValue = observer; - target.weakTestObjectValue = target; - - // These observations can only result in dealloc triggered callbacks. - [observer rac_observeKeyPath:@rac_keypath(target.weakTestObjectValue) options:0 observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - observerDeallocationTriggeredChange = YES; - }]; - - [target rac_observeKeyPath:@rac_keypath(target.weakTestObjectValue) options:0 observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - targetDeallocationTriggeredChange = YES; - }]; - } - - expect(@(observerDisposed)).to(beTruthy()); - expect(@(observerDeallocationTriggeredChange)).to(beFalsy()); - - expect(@(targetDisposed)).to(beTruthy()); - expect(@(targetDeallocationTriggeredChange)).to(beTruthy()); - }); - - qck_it(@"should call the callback block for deallocation of the initial value of a single-key key path", ^{ - RACTestObject *target = [RACTestObject new]; - __block BOOL objectDisposed = NO; - __block BOOL objectDeallocationTriggeredChange = NO; - - @autoreleasepool { - RACTestObject *object __attribute__((objc_precise_lifetime)) = [RACTestObject new]; - target.weakTestObjectValue = object; - [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - objectDisposed = YES; - }]]; - - [target rac_observeKeyPath:@rac_keypath(target.weakTestObjectValue) options:0 observer:target block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - objectDeallocationTriggeredChange = YES; - }]; - } - - expect(@(objectDisposed)).to(beTruthy()); - expect(@(objectDeallocationTriggeredChange)).to(beTruthy()); - }); - - qck_it(@"should call the callback block for deallocation of an object conforming to protocol property", ^{ - RACTestObject *target = [RACTestObject new]; - __block BOOL objectDisposed = NO; - __block BOOL objectDeallocationTriggeredChange = NO; - - @autoreleasepool { - RACTestObject *object __attribute__((objc_precise_lifetime)) = [RACTestObject new]; - target.weakObjectWithProtocol = object; - [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - objectDisposed = YES; - }]]; - - [target rac_observeKeyPath:@rac_keypath(target.weakObjectWithProtocol) options:0 observer:target block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - objectDeallocationTriggeredChange = YES; - }]; - } - - expect(@(objectDisposed)).to(beTruthy()); - expect(@(objectDeallocationTriggeredChange)).to(beTruthy()); + qck_describe(@"on simple keys", ^{ + NSObject * (^targetBlock)(void) = ^{ + return [[RACTestObject alloc] init]; + }; + + void (^changeBlock)(RACTestObject *, id) = ^(RACTestObject *target, id value) { + target.weakTestObjectValue = value; + }; + + id (^valueBlock)(void) = ^{ + return [[RACTestObject alloc] init]; + }; + + qck_itBehavesLike(RACKVOWrapperExamples, ^{ + return @{ + RACKVOWrapperExamplesTargetBlock: targetBlock, + RACKVOWrapperExamplesKeyPath: @rac_keypath(RACTestObject.new, weakTestObjectValue), + RACKVOWrapperExamplesChangeBlock: changeBlock, + RACKVOWrapperExamplesValueBlock: valueBlock, + RACKVOWrapperExamplesChangesValueDirectly: @YES + }; + }); + + qck_itBehavesLike(RACKVOWrapperCollectionExamples, ^{ + return @{ + RACKVOWrapperCollectionExamplesTargetBlock: targetBlock, + RACKVOWrapperCollectionExamplesKeyPath: @rac_keypath(RACTestObject.new, orderedSetValue) + }; + }); + }); + + qck_describe(@"on composite key paths'", ^{ + qck_describe(@"last key path components", ^{ + NSObject *(^targetBlock)(void) = ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + object.strongTestObjectValue = [[RACTestObject alloc] init]; + return object; + }; + + void (^changeBlock)(RACTestObject *, id) = ^(RACTestObject *target, id value) { + target.strongTestObjectValue.weakTestObjectValue = value; + }; + + id (^valueBlock)(void) = ^{ + return [[RACTestObject alloc] init]; + }; + + qck_itBehavesLike(RACKVOWrapperExamples, ^{ + return @{ + RACKVOWrapperExamplesTargetBlock: targetBlock, + RACKVOWrapperExamplesKeyPath: @rac_keypath(RACTestObject.new, strongTestObjectValue.weakTestObjectValue), + RACKVOWrapperExamplesChangeBlock: changeBlock, + RACKVOWrapperExamplesValueBlock: valueBlock, + RACKVOWrapperExamplesChangesValueDirectly: @YES + }; + }); + + qck_itBehavesLike(RACKVOWrapperCollectionExamples, ^{ + return @{ + RACKVOWrapperCollectionExamplesTargetBlock: targetBlock, + RACKVOWrapperCollectionExamplesKeyPath: @rac_keypath(RACTestObject.new, strongTestObjectValue.orderedSetValue) + }; + }); + }); + + qck_describe(@"intermediate key path components", ^{ + NSObject *(^targetBlock)(void) = ^{ + return [[RACTestObject alloc] init]; + }; + + void (^changeBlock)(RACTestObject *, id) = ^(RACTestObject *target, id value) { + target.weakTestObjectValue = value; + }; + + id (^valueBlock)(void) = ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + object.strongTestObjectValue = [[RACTestObject alloc] init]; + return object; + }; + + qck_itBehavesLike(RACKVOWrapperExamples, ^{ + return @{ + RACKVOWrapperExamplesTargetBlock: targetBlock, + RACKVOWrapperExamplesKeyPath: @rac_keypath([[RACTestObject alloc] init], weakTestObjectValue.strongTestObjectValue), + RACKVOWrapperExamplesChangeBlock: changeBlock, + RACKVOWrapperExamplesValueBlock: valueBlock, + RACKVOWrapperExamplesChangesValueDirectly: @NO + }; + }); + }); + + qck_it(@"should not notice deallocation of the object returned by a dynamic final property", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block id lastValue = nil; + @autoreleasepool { + [object rac_observeKeyPath:@rac_keypath(object.dynamicObjectProperty) options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + lastValue = value; + }]; + + expect(lastValue).to(beAKindOf(RACTestObject.class)); + } + + expect(lastValue).to(beAKindOf(RACTestObject.class)); + }); + + qck_it(@"should not notice deallocation of the object returned by a dynamic intermediate property", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block id lastValue = nil; + @autoreleasepool { + [object rac_observeKeyPath:@rac_keypath(object.dynamicObjectProperty.integerValue) options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + lastValue = value; + }]; + + expect(lastValue).to(equal(@42)); + } + + expect(lastValue).to(equal(@42)); + }); + + qck_it(@"should not notice deallocation of the object returned by a dynamic method", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block id lastValue = nil; + @autoreleasepool { + [object rac_observeKeyPath:@rac_keypath(object.dynamicObjectMethod) options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + lastValue = value; + }]; + + expect(lastValue).to(beAKindOf(RACTestObject.class)); + } + + expect(lastValue).to(beAKindOf(RACTestObject.class)); + }); + }); + + qck_it(@"should not call the callback block when the value is the observer", ^{ + __block BOOL observerDisposed = NO; + __block BOOL observerDeallocationTriggeredChange = NO; + __block BOOL targetDisposed = NO; + __block BOOL targetDeallocationTriggeredChange = NO; + + @autoreleasepool { + RACTestObject *observer __attribute__((objc_precise_lifetime)) = [RACTestObject new]; + [observer.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + observerDisposed = YES; + }]]; + + RACTestObject *target __attribute__((objc_precise_lifetime)) = [RACTestObject new]; + [target.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + targetDisposed = YES; + }]]; + + observer.weakTestObjectValue = observer; + target.weakTestObjectValue = target; + + // These observations can only result in dealloc triggered callbacks. + [observer rac_observeKeyPath:@rac_keypath(target.weakTestObjectValue) options:0 observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + observerDeallocationTriggeredChange = YES; + }]; + + [target rac_observeKeyPath:@rac_keypath(target.weakTestObjectValue) options:0 observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + targetDeallocationTriggeredChange = YES; + }]; + } + + expect(@(observerDisposed)).to(beTruthy()); + expect(@(observerDeallocationTriggeredChange)).to(beFalsy()); + + expect(@(targetDisposed)).to(beTruthy()); + expect(@(targetDeallocationTriggeredChange)).to(beTruthy()); + }); + + qck_it(@"should call the callback block for deallocation of the initial value of a single-key key path", ^{ + RACTestObject *target = [RACTestObject new]; + __block BOOL objectDisposed = NO; + __block BOOL objectDeallocationTriggeredChange = NO; + + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [RACTestObject new]; + target.weakTestObjectValue = object; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + objectDisposed = YES; + }]]; + + [target rac_observeKeyPath:@rac_keypath(target.weakTestObjectValue) options:0 observer:target block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + objectDeallocationTriggeredChange = YES; + }]; + } + + expect(@(objectDisposed)).to(beTruthy()); + expect(@(objectDeallocationTriggeredChange)).to(beTruthy()); + }); + + qck_it(@"should call the callback block for deallocation of an object conforming to protocol property", ^{ + RACTestObject *target = [RACTestObject new]; + __block BOOL objectDisposed = NO; + __block BOOL objectDeallocationTriggeredChange = NO; + + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [RACTestObject new]; + target.weakObjectWithProtocol = object; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + objectDisposed = YES; + }]]; + + [target rac_observeKeyPath:@rac_keypath(target.weakObjectWithProtocol) options:0 observer:target block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + objectDeallocationTriggeredChange = YES; + }]; + } + + expect(@(objectDisposed)).to(beTruthy()); + expect(@(objectDeallocationTriggeredChange)).to(beTruthy()); }); }); qck_describe(@"rac_addObserver:forKeyPath:options:block:", ^{ - qck_it(@"should add and remove an observer", ^{ - NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{}]; - expect(operation).notTo(beNil()); + qck_it(@"should add and remove an observer", ^{ + NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{}]; + expect(operation).notTo(beNil()); + + __block BOOL notified = NO; + RACDisposable *disposable = [operation rac_observeKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + expect([change objectForKey:NSKeyValueChangeNewKey]).to(equal(@YES)); + + expect(@(notified)).to(beFalsy()); + notified = YES; + }]; + + expect(disposable).notTo(beNil()); + + [operation start]; + [operation waitUntilFinished]; - __block BOOL notified = NO; - RACDisposable *disposable = [operation rac_observeKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - expect([change objectForKey:NSKeyValueChangeNewKey]).to(equal(@YES)); + expect(@(notified)).toEventually(beTruthy()); + }); - expect(@(notified)).to(beFalsy()); - notified = YES; - }]; + qck_it(@"should accept a nil observer", ^{ + NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{}]; + RACDisposable *disposable = [operation rac_observeKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {}]; - expect(disposable).notTo(beNil()); + expect(disposable).notTo(beNil()); + }); - [operation start]; - [operation waitUntilFinished]; + qck_context(@"automatically stops KVO on subclasses when the target deallocates", ^{ + void (^testKVOOnSubclass)(Class targetClass, id observer) = ^(Class targetClass, id observer) { + __weak id weakTarget = nil; + __weak id identifier = nil; - expect(@(notified)).toEventually(beTruthy()); - }); + @autoreleasepool { + // Create an observable target that we control the memory management of. + CFTypeRef target = CFBridgingRetain([[targetClass alloc] init]); + expect((__bridge id)target).notTo(beNil()); - qck_it(@"should accept a nil observer", ^{ - NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{}]; - RACDisposable *disposable = [operation rac_observeKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {}]; + weakTarget = (__bridge id)target; + expect(weakTarget).notTo(beNil()); - expect(disposable).notTo(beNil()); - }); + identifier = [(__bridge id)target rac_observeKeyPath:@"isFinished" options:0 observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {}]; + expect(identifier).notTo(beNil()); - qck_context(@"automatically stops KVO on subclasses when the target deallocates", ^{ - void (^testKVOOnSubclass)(Class targetClass, id observer) = ^(Class targetClass, id observer) { - __weak id weakTarget = nil; - __weak id identifier = nil; - - @autoreleasepool { - // Create an observable target that we control the memory management of. - CFTypeRef target = CFBridgingRetain([[targetClass alloc] init]); - expect((__bridge id)target).notTo(beNil()); - - weakTarget = (__bridge id)target; - expect(weakTarget).notTo(beNil()); - - identifier = [(__bridge id)target rac_observeKeyPath:@"isFinished" options:0 observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {}]; - expect(identifier).notTo(beNil()); - - CFRelease(target); - } - - expect(weakTarget).to(beNil()); - expect(identifier).to(beNil()); - }; - - qck_it(@"stops KVO on NSObject subclasses", ^{ - testKVOOnSubclass(NSOperation.class, self); - }); - - qck_it(@"stops KVO on subclasses of already-swizzled classes", ^{ - testKVOOnSubclass(RACTestOperation.class, self); - }); - - qck_it(@"stops KVO on NSObject subclasses even with a nil observer", ^{ - testKVOOnSubclass(NSOperation.class, nil); - }); - - qck_it(@"stops KVO on subclasses of already-swizzled classes even with a nil observer", ^{ - testKVOOnSubclass(RACTestOperation.class, nil); - }); - }); - - qck_it(@"should automatically stop KVO when the observer deallocates", ^{ - __weak id weakObserver = nil; - __weak id weakIdentifier = nil; - - NSOperation *operation = [[NSOperation alloc] init]; - - @autoreleasepool { - // Create an observer that we control the memory management of. - CFTypeRef observer = CFBridgingRetain([[NSOperation alloc] init]); - expect((__bridge id)observer).notTo(beNil()); - - weakObserver = (__bridge id)observer; - expect(weakObserver).notTo(beNil()); - - id identifier = [operation rac_observeKeyPath:@"isFinished" options:0 observer:(__bridge id)observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {}]; - expect(identifier).notTo(beNil()); - - weakIdentifier = identifier; - expect(weakIdentifier).notTo(beNil()); - - CFRelease(observer); - } - - expect(weakObserver).to(beNil()); - expect(weakIdentifier).to(beNil()); - }); - - qck_it(@"should stop KVO when the observer is disposed", ^{ - NSOperationQueue *queue = [[NSOperationQueue alloc] init]; - __block NSString *name = nil; - - RACDisposable *disposable = [queue rac_observeKeyPath:@"name" options:0 observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - name = queue.name; - }]; - - queue.name = @"1"; - expect(name).to(equal(@"1")); - [disposable dispose]; - queue.name = @"2"; - expect(name).to(equal(@"1")); - }); - - qck_it(@"should distinguish between observers being disposed", ^{ - NSOperationQueue *queue = [[NSOperationQueue alloc] init]; - __block NSString *name1 = nil; - __block NSString *name2 = nil; - - RACDisposable *disposable = [queue rac_observeKeyPath:@"name" options:0 observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - name1 = queue.name; - }]; - [queue rac_observeKeyPath:@"name" options:0 observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - name2 = queue.name; - }]; - - queue.name = @"1"; - expect(name1).to(equal(@"1")); - expect(name2).to(equal(@"1")); - [disposable dispose]; - queue.name = @"2"; - expect(name1).to(equal(@"1")); - expect(name2).to(equal(@"2")); - }); + CFRelease(target); + } + + expect(weakTarget).to(beNil()); + expect(identifier).to(beNil()); + }; + + qck_it(@"stops KVO on NSObject subclasses", ^{ + testKVOOnSubclass(NSOperation.class, self); + }); + + qck_it(@"stops KVO on subclasses of already-swizzled classes", ^{ + testKVOOnSubclass(RACTestOperation.class, self); + }); + + qck_it(@"stops KVO on NSObject subclasses even with a nil observer", ^{ + testKVOOnSubclass(NSOperation.class, nil); + }); + + qck_it(@"stops KVO on subclasses of already-swizzled classes even with a nil observer", ^{ + testKVOOnSubclass(RACTestOperation.class, nil); + }); + }); + + qck_it(@"should automatically stop KVO when the observer deallocates", ^{ + __weak id weakObserver = nil; + __weak id weakIdentifier = nil; + + NSOperation *operation = [[NSOperation alloc] init]; + + @autoreleasepool { + // Create an observer that we control the memory management of. + CFTypeRef observer = CFBridgingRetain([[NSOperation alloc] init]); + expect((__bridge id)observer).notTo(beNil()); + + weakObserver = (__bridge id)observer; + expect(weakObserver).notTo(beNil()); + + id identifier = [operation rac_observeKeyPath:@"isFinished" options:0 observer:(__bridge id)observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {}]; + expect(identifier).notTo(beNil()); + + weakIdentifier = identifier; + expect(weakIdentifier).notTo(beNil()); + + CFRelease(observer); + } + + expect(weakObserver).to(beNil()); + expect(weakIdentifier).to(beNil()); + }); + + qck_it(@"should stop KVO when the observer is disposed", ^{ + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + __block NSString *name = nil; + + RACDisposable *disposable = [queue rac_observeKeyPath:@"name" options:0 observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + name = queue.name; + }]; + + queue.name = @"1"; + expect(name).to(equal(@"1")); + [disposable dispose]; + queue.name = @"2"; + expect(name).to(equal(@"1")); + }); + + qck_it(@"should distinguish between observers being disposed", ^{ + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + __block NSString *name1 = nil; + __block NSString *name2 = nil; + + RACDisposable *disposable = [queue rac_observeKeyPath:@"name" options:0 observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + name1 = queue.name; + }]; + [queue rac_observeKeyPath:@"name" options:0 observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { + name2 = queue.name; + }]; + + queue.name = @"1"; + expect(name1).to(equal(@"1")); + expect(name2).to(equal(@"1")); + [disposable dispose]; + queue.name = @"2"; + expect(name1).to(equal(@"1")); + expect(name2).to(equal(@"2")); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACMulticastConnectionSpec.m b/ReactiveObjCTests/RACMulticastConnectionSpec.m index 5ce04ba5f..78769157f 100644 --- a/ReactiveObjCTests/RACMulticastConnectionSpec.m +++ b/ReactiveObjCTests/RACMulticastConnectionSpec.m @@ -23,128 +23,128 @@ __block RACMulticastConnection *connection; qck_beforeEach(^{ - subscriptionCount = 0; - connection = [[RACSignal createSignal:^(id subscriber) { - subscriptionCount++; - return (RACDisposable *)nil; - }] publish]; + subscriptionCount = 0; + connection = [[RACSignal createSignal:^(id subscriber) { + subscriptionCount++; + return (RACDisposable *)nil; + }] publish]; - expect(@(subscriptionCount)).to(equal(@0)); + expect(@(subscriptionCount)).to(equal(@0)); }); qck_describe(@"-connect", ^{ - qck_it(@"should subscribe to the underlying signal", ^{ - [connection connect]; - expect(@(subscriptionCount)).to(equal(@1)); - }); - - qck_it(@"should return the same disposable for each invocation", ^{ - RACDisposable *d1 = [connection connect]; - RACDisposable *d2 = [connection connect]; - expect(d1).to(equal(d2)); - expect(@(subscriptionCount)).to(equal(@1)); - }); - - qck_it(@"shouldn't reconnect after disposal", ^{ - RACDisposable *disposable1 = [connection connect]; - expect(@(subscriptionCount)).to(equal(@1)); - - [disposable1 dispose]; - - RACDisposable *disposable2 = [connection connect]; - expect(@(subscriptionCount)).to(equal(@1)); - expect(disposable1).to(equal(disposable2)); - }); - - qck_it(@"shouldn't race when connecting", ^{ - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - RACMulticastConnection *connection = [[RACSignal - defer:^ id { - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); - return nil; - }] - publish]; - - __block RACDisposable *disposable; - [RACScheduler.scheduler schedule:^{ - disposable = [connection connect]; - dispatch_semaphore_signal(semaphore); - }]; - - expect([connection connect]).notTo(beNil()); - dispatch_semaphore_signal(semaphore); - - expect(disposable).toEventuallyNot(beNil()); - }); + qck_it(@"should subscribe to the underlying signal", ^{ + [connection connect]; + expect(@(subscriptionCount)).to(equal(@1)); + }); + + qck_it(@"should return the same disposable for each invocation", ^{ + RACDisposable *d1 = [connection connect]; + RACDisposable *d2 = [connection connect]; + expect(d1).to(equal(d2)); + expect(@(subscriptionCount)).to(equal(@1)); + }); + + qck_it(@"shouldn't reconnect after disposal", ^{ + RACDisposable *disposable1 = [connection connect]; + expect(@(subscriptionCount)).to(equal(@1)); + + [disposable1 dispose]; + + RACDisposable *disposable2 = [connection connect]; + expect(@(subscriptionCount)).to(equal(@1)); + expect(disposable1).to(equal(disposable2)); + }); + + qck_it(@"shouldn't race when connecting", ^{ + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + + RACMulticastConnection *connection = [[RACSignal + defer:^ id { + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + return nil; + }] + publish]; + + __block RACDisposable *disposable; + [RACScheduler.scheduler schedule:^{ + disposable = [connection connect]; + dispatch_semaphore_signal(semaphore); + }]; + + expect([connection connect]).notTo(beNil()); + dispatch_semaphore_signal(semaphore); + + expect(disposable).toEventuallyNot(beNil()); + }); }); qck_describe(@"-autoconnect", ^{ - __block RACSignal *autoconnectedSignal; - - qck_beforeEach(^{ - autoconnectedSignal = [connection autoconnect]; - }); - - qck_it(@"should subscribe to the multicasted signal on the first subscription", ^{ - expect(@(subscriptionCount)).to(equal(@0)); - - [autoconnectedSignal subscribeNext:^(id x) {}]; - expect(@(subscriptionCount)).to(equal(@1)); - - [autoconnectedSignal subscribeNext:^(id x) {}]; - expect(@(subscriptionCount)).to(equal(@1)); - }); - - qck_it(@"should dispose of the multicasted subscription when the signal has no subscribers", ^{ - __block BOOL disposed = NO; - __block id connectionSubscriber = nil; - RACSignal *signal = [[[RACSignal createSignal:^(id subscriber) { - // Keep the subscriber alive so it doesn't trigger disposal on dealloc - connectionSubscriber = subscriber; - subscriptionCount++; - return [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; - }] publish] autoconnect]; - RACDisposable *disposable = [signal subscribeNext:^(id x) {}]; - - expect(@(disposed)).to(beFalsy()); - [disposable dispose]; - expect(@(disposed)).to(beTruthy()); - }); - - qck_it(@"shouldn't reconnect after disposal", ^{ - RACDisposable *disposable = [autoconnectedSignal subscribeNext:^(id x) {}]; - expect(@(subscriptionCount)).to(equal(@1)); - [disposable dispose]; - - disposable = [autoconnectedSignal subscribeNext:^(id x) {}]; - expect(@(subscriptionCount)).to(equal(@1)); - [disposable dispose]; - }); - - qck_it(@"should replay values after disposal when multicasted to a replay subject", ^{ - RACSubject *subject = [RACSubject subject]; - RACSignal *signal = [[subject multicast:[RACReplaySubject subject]] autoconnect]; - - NSMutableArray *results1 = [NSMutableArray array]; - RACDisposable *disposable = [signal subscribeNext:^(id x) { - [results1 addObject:x]; - }]; - - [subject sendNext:@1]; - [subject sendNext:@2]; - - expect(results1).to(equal((@[ @1, @2 ]))); - [disposable dispose]; - - NSMutableArray *results2 = [NSMutableArray array]; - [signal subscribeNext:^(id x) { - [results2 addObject:x]; - }]; - expect(results2).toEventually(equal((@[ @1, @2 ]))); - }); + __block RACSignal *autoconnectedSignal; + + qck_beforeEach(^{ + autoconnectedSignal = [connection autoconnect]; + }); + + qck_it(@"should subscribe to the multicasted signal on the first subscription", ^{ + expect(@(subscriptionCount)).to(equal(@0)); + + [autoconnectedSignal subscribeNext:^(id x) {}]; + expect(@(subscriptionCount)).to(equal(@1)); + + [autoconnectedSignal subscribeNext:^(id x) {}]; + expect(@(subscriptionCount)).to(equal(@1)); + }); + + qck_it(@"should dispose of the multicasted subscription when the signal has no subscribers", ^{ + __block BOOL disposed = NO; + __block id connectionSubscriber = nil; + RACSignal *signal = [[[RACSignal createSignal:^(id subscriber) { + // Keep the subscriber alive so it doesn't trigger disposal on dealloc + connectionSubscriber = subscriber; + subscriptionCount++; + return [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + }] publish] autoconnect]; + RACDisposable *disposable = [signal subscribeNext:^(id x) {}]; + + expect(@(disposed)).to(beFalsy()); + [disposable dispose]; + expect(@(disposed)).to(beTruthy()); + }); + + qck_it(@"shouldn't reconnect after disposal", ^{ + RACDisposable *disposable = [autoconnectedSignal subscribeNext:^(id x) {}]; + expect(@(subscriptionCount)).to(equal(@1)); + [disposable dispose]; + + disposable = [autoconnectedSignal subscribeNext:^(id x) {}]; + expect(@(subscriptionCount)).to(equal(@1)); + [disposable dispose]; + }); + + qck_it(@"should replay values after disposal when multicasted to a replay subject", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [[subject multicast:[RACReplaySubject subject]] autoconnect]; + + NSMutableArray *results1 = [NSMutableArray array]; + RACDisposable *disposable = [signal subscribeNext:^(id x) { + [results1 addObject:x]; + }]; + + [subject sendNext:@1]; + [subject sendNext:@2]; + + expect(results1).to(equal((@[ @1, @2 ]))); + [disposable dispose]; + + NSMutableArray *results2 = [NSMutableArray array]; + [signal subscribeNext:^(id x) { + [results2 addObject:x]; + }]; + expect(results2).toEventually(equal((@[ @1, @2 ]))); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACPropertySignalExamples.m b/ReactiveObjCTests/RACPropertySignalExamples.m index b76ce8ba3..6ce7fb80d 100644 --- a/ReactiveObjCTests/RACPropertySignalExamples.m +++ b/ReactiveObjCTests/RACPropertySignalExamples.m @@ -25,120 +25,120 @@ QuickConfigurationBegin(RACPropertySignalExampleGroups) + (void)configure:(Configuration *)configuration { - sharedExamples(RACPropertySignalExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block RACTestObject *testObject = nil; - __block void (^setupBlock)(RACTestObject *, NSString *keyPath, id nilValue, RACSignal *); + sharedExamples(RACPropertySignalExamples, ^(QCKDSLSharedExampleContext exampleContext) { + __block RACTestObject *testObject = nil; + __block void (^setupBlock)(RACTestObject *, NSString *keyPath, id nilValue, RACSignal *); - qck_beforeEach(^{ - setupBlock = exampleContext()[RACPropertySignalExamplesSetupBlock]; - testObject = [[RACTestObject alloc] init]; - }); + qck_beforeEach(^{ + setupBlock = exampleContext()[RACPropertySignalExamplesSetupBlock]; + testObject = [[RACTestObject alloc] init]; + }); - qck_it(@"should set the value of the property with the latest value from the signal", ^{ - RACSubject *subject = [RACSubject subject]; - setupBlock(testObject, @rac_keypath(testObject.objectValue), nil, subject); - expect(testObject.objectValue).to(beNil()); + qck_it(@"should set the value of the property with the latest value from the signal", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @rac_keypath(testObject.objectValue), nil, subject); + expect(testObject.objectValue).to(beNil()); - [subject sendNext:@1]; - expect(testObject.objectValue).to(equal(@1)); + [subject sendNext:@1]; + expect(testObject.objectValue).to(equal(@1)); - [subject sendNext:@2]; - expect(testObject.objectValue).to(equal(@2)); + [subject sendNext:@2]; + expect(testObject.objectValue).to(equal(@2)); - [subject sendNext:nil]; - expect(testObject.objectValue).to(beNil()); - }); + [subject sendNext:nil]; + expect(testObject.objectValue).to(beNil()); + }); - qck_it(@"should set the given nilValue for an object property", ^{ - RACSubject *subject = [RACSubject subject]; - setupBlock(testObject, @rac_keypath(testObject.objectValue), @"foo", subject); - expect(testObject.objectValue).to(beNil()); + qck_it(@"should set the given nilValue for an object property", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @rac_keypath(testObject.objectValue), @"foo", subject); + expect(testObject.objectValue).to(beNil()); - [subject sendNext:@1]; - expect(testObject.objectValue).to(equal(@1)); + [subject sendNext:@1]; + expect(testObject.objectValue).to(equal(@1)); - [subject sendNext:@2]; - expect(testObject.objectValue).to(equal(@2)); + [subject sendNext:@2]; + expect(testObject.objectValue).to(equal(@2)); - [subject sendNext:nil]; - expect(testObject.objectValue).to(equal(@"foo")); - }); + [subject sendNext:nil]; + expect(testObject.objectValue).to(equal(@"foo")); + }); - qck_it(@"should leave the value of the property alone after the signal completes", ^{ - RACSubject *subject = [RACSubject subject]; - setupBlock(testObject, @rac_keypath(testObject.objectValue), nil, subject); - expect(testObject.objectValue).to(beNil()); + qck_it(@"should leave the value of the property alone after the signal completes", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @rac_keypath(testObject.objectValue), nil, subject); + expect(testObject.objectValue).to(beNil()); - [subject sendNext:@1]; - expect(testObject.objectValue).to(equal(@1)); + [subject sendNext:@1]; + expect(testObject.objectValue).to(equal(@1)); - [subject sendCompleted]; - expect(testObject.objectValue).to(equal(@1)); - }); + [subject sendCompleted]; + expect(testObject.objectValue).to(equal(@1)); + }); - qck_it(@"should set the value of a non-object property with the latest value from the signal", ^{ - RACSubject *subject = [RACSubject subject]; - setupBlock(testObject, @rac_keypath(testObject.integerValue), nil, subject); - expect(@(testObject.integerValue)).to(equal(@0)); + qck_it(@"should set the value of a non-object property with the latest value from the signal", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @rac_keypath(testObject.integerValue), nil, subject); + expect(@(testObject.integerValue)).to(equal(@0)); - [subject sendNext:@1]; - expect(@(testObject.integerValue)).to(equal(@1)); + [subject sendNext:@1]; + expect(@(testObject.integerValue)).to(equal(@1)); - [subject sendNext:@2]; - expect(@(testObject.integerValue)).to(equal(@2)); + [subject sendNext:@2]; + expect(@(testObject.integerValue)).to(equal(@2)); - [subject sendNext:@0]; - expect(@(testObject.integerValue)).to(equal(@0)); - }); + [subject sendNext:@0]; + expect(@(testObject.integerValue)).to(equal(@0)); + }); - qck_it(@"should set the given nilValue for a non-object property", ^{ - RACSubject *subject = [RACSubject subject]; - setupBlock(testObject, @rac_keypath(testObject.integerValue), @42, subject); - expect(@(testObject.integerValue)).to(equal(@0)); + qck_it(@"should set the given nilValue for a non-object property", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @rac_keypath(testObject.integerValue), @42, subject); + expect(@(testObject.integerValue)).to(equal(@0)); - [subject sendNext:@1]; - expect(@(testObject.integerValue)).to(equal(@1)); + [subject sendNext:@1]; + expect(@(testObject.integerValue)).to(equal(@1)); - [subject sendNext:@2]; - expect(@(testObject.integerValue)).to(equal(@2)); + [subject sendNext:@2]; + expect(@(testObject.integerValue)).to(equal(@2)); - [subject sendNext:nil]; - expect(@(testObject.integerValue)).to(equal(@42)); - }); + [subject sendNext:nil]; + expect(@(testObject.integerValue)).to(equal(@42)); + }); - qck_it(@"should not invoke -setNilValueForKey: with a nilValue", ^{ - RACSubject *subject = [RACSubject subject]; - setupBlock(testObject, @rac_keypath(testObject.integerValue), @42, subject); + qck_it(@"should not invoke -setNilValueForKey: with a nilValue", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @rac_keypath(testObject.integerValue), @42, subject); - __block BOOL setNilValueForKeyInvoked = NO; - [[testObject rac_signalForSelector:@selector(setNilValueForKey:)] subscribeNext:^(RACTuple *arguments) { - setNilValueForKeyInvoked = YES; - }]; + __block BOOL setNilValueForKeyInvoked = NO; + [[testObject rac_signalForSelector:@selector(setNilValueForKey:)] subscribeNext:^(RACTuple *arguments) { + setNilValueForKeyInvoked = YES; + }]; - [subject sendNext:nil]; - expect(@(testObject.integerValue)).to(equal(@42)); - expect(@(setNilValueForKeyInvoked)).to(beFalsy()); - }); + [subject sendNext:nil]; + expect(@(testObject.integerValue)).to(equal(@42)); + expect(@(setNilValueForKeyInvoked)).to(beFalsy()); + }); - qck_it(@"should invoke -setNilValueForKey: without a nilValue", ^{ - RACSubject *subject = [RACSubject subject]; - setupBlock(testObject, @rac_keypath(testObject.integerValue), nil, subject); + qck_it(@"should invoke -setNilValueForKey: without a nilValue", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @rac_keypath(testObject.integerValue), nil, subject); - [subject sendNext:@1]; - expect(@(testObject.integerValue)).to(equal(@1)); + [subject sendNext:@1]; + expect(@(testObject.integerValue)).to(equal(@1)); - testObject.catchSetNilValueForKey = YES; + testObject.catchSetNilValueForKey = YES; - __block BOOL setNilValueForKeyInvoked = NO; - [[testObject rac_signalForSelector:@selector(setNilValueForKey:)] subscribeNext:^(RACTuple *arguments) { - setNilValueForKeyInvoked = YES; - }]; + __block BOOL setNilValueForKeyInvoked = NO; + [[testObject rac_signalForSelector:@selector(setNilValueForKey:)] subscribeNext:^(RACTuple *arguments) { + setNilValueForKeyInvoked = YES; + }]; - [subject sendNext:nil]; - expect(@(testObject.integerValue)).to(equal(@1)); - expect(@(setNilValueForKeyInvoked)).to(beTruthy()); - }); - }); + [subject sendNext:nil]; + expect(@(testObject.integerValue)).to(equal(@1)); + expect(@(setNilValueForKeyInvoked)).to(beTruthy()); + }); + }); } QuickConfigurationEnd diff --git a/ReactiveObjCTests/RACSchedulerSpec.m b/ReactiveObjCTests/RACSchedulerSpec.m index 8f5a2ec4a..d37a6734e 100644 --- a/ReactiveObjCTests/RACSchedulerSpec.m +++ b/ReactiveObjCTests/RACSchedulerSpec.m @@ -22,420 +22,420 @@ // This shouldn't be used directly. Use the `expectCurrentSchedulers` block // below instead. static void expectCurrentSchedulersInner(NSArray *schedulers, NSMutableArray *currentSchedulerArray) { - if (schedulers.count > 0) { - RACScheduler *topScheduler = schedulers[0]; - [topScheduler schedule:^{ - RACScheduler *currentScheduler = RACScheduler.currentScheduler; - if (currentScheduler != nil) [currentSchedulerArray addObject:currentScheduler]; - expectCurrentSchedulersInner([schedulers subarrayWithRange:NSMakeRange(1, schedulers.count - 1)], currentSchedulerArray); - }]; - } + if (schedulers.count > 0) { + RACScheduler *topScheduler = schedulers[0]; + [topScheduler schedule:^{ + RACScheduler *currentScheduler = RACScheduler.currentScheduler; + if (currentScheduler != nil) [currentSchedulerArray addObject:currentScheduler]; + expectCurrentSchedulersInner([schedulers subarrayWithRange:NSMakeRange(1, schedulers.count - 1)], currentSchedulerArray); + }]; + } } QuickSpecBegin(RACSchedulerSpec) qck_it(@"should know its current scheduler", ^{ - // Recursively schedules a block in each of the given schedulers and records - // the +currentScheduler at each step. It then expects the array of - // +currentSchedulers and the expected array to be equal. - // - // schedulers - The array of schedulers to recursively schedule. - // expectedCurrentSchedulers - The array of +currentSchedulers to expect. - void (^expectCurrentSchedulers)(NSArray *, NSArray *) = ^(NSArray *schedulers, NSArray *expectedCurrentSchedulers) { - NSMutableArray *currentSchedulerArray = [NSMutableArray array]; - expectCurrentSchedulersInner(schedulers, currentSchedulerArray); - expect(currentSchedulerArray).toEventually(equal(expectedCurrentSchedulers)); - }; - - RACScheduler *backgroundScheduler = [RACScheduler scheduler]; - - expectCurrentSchedulers(@[ backgroundScheduler, RACScheduler.immediateScheduler ], @[ backgroundScheduler, backgroundScheduler ]); - expectCurrentSchedulers(@[ backgroundScheduler, RACScheduler.subscriptionScheduler ], @[ backgroundScheduler, backgroundScheduler ]); - - NSArray *mainThreadJumper = @[ RACScheduler.mainThreadScheduler, backgroundScheduler, RACScheduler.mainThreadScheduler ]; - expectCurrentSchedulers(mainThreadJumper, mainThreadJumper); - - NSArray *backgroundJumper = @[ backgroundScheduler, RACScheduler.mainThreadScheduler, backgroundScheduler ]; - expectCurrentSchedulers(backgroundJumper, backgroundJumper); + // Recursively schedules a block in each of the given schedulers and records + // the +currentScheduler at each step. It then expects the array of + // +currentSchedulers and the expected array to be equal. + // + // schedulers - The array of schedulers to recursively schedule. + // expectedCurrentSchedulers - The array of +currentSchedulers to expect. + void (^expectCurrentSchedulers)(NSArray *, NSArray *) = ^(NSArray *schedulers, NSArray *expectedCurrentSchedulers) { + NSMutableArray *currentSchedulerArray = [NSMutableArray array]; + expectCurrentSchedulersInner(schedulers, currentSchedulerArray); + expect(currentSchedulerArray).toEventually(equal(expectedCurrentSchedulers)); + }; + + RACScheduler *backgroundScheduler = [RACScheduler scheduler]; + + expectCurrentSchedulers(@[ backgroundScheduler, RACScheduler.immediateScheduler ], @[ backgroundScheduler, backgroundScheduler ]); + expectCurrentSchedulers(@[ backgroundScheduler, RACScheduler.subscriptionScheduler ], @[ backgroundScheduler, backgroundScheduler ]); + + NSArray *mainThreadJumper = @[ RACScheduler.mainThreadScheduler, backgroundScheduler, RACScheduler.mainThreadScheduler ]; + expectCurrentSchedulers(mainThreadJumper, mainThreadJumper); + + NSArray *backgroundJumper = @[ backgroundScheduler, RACScheduler.mainThreadScheduler, backgroundScheduler ]; + expectCurrentSchedulers(backgroundJumper, backgroundJumper); }); qck_describe(@"+mainThreadScheduler", ^{ - qck_it(@"should cancel scheduled blocks when disposed", ^{ - __block BOOL firstBlockRan = NO; - __block BOOL secondBlockRan = NO; + qck_it(@"should cancel scheduled blocks when disposed", ^{ + __block BOOL firstBlockRan = NO; + __block BOOL secondBlockRan = NO; - RACDisposable *disposable = [RACScheduler.mainThreadScheduler schedule:^{ - firstBlockRan = YES; - }]; + RACDisposable *disposable = [RACScheduler.mainThreadScheduler schedule:^{ + firstBlockRan = YES; + }]; - expect(disposable).notTo(beNil()); + expect(disposable).notTo(beNil()); - [RACScheduler.mainThreadScheduler schedule:^{ - secondBlockRan = YES; - }]; + [RACScheduler.mainThreadScheduler schedule:^{ + secondBlockRan = YES; + }]; - [disposable dispose]; + [disposable dispose]; - expect(@(secondBlockRan)).to(beFalsy()); - expect(@(secondBlockRan)).toEventually(beTruthy()); - expect(@(firstBlockRan)).to(beFalsy()); - }); + expect(@(secondBlockRan)).to(beFalsy()); + expect(@(secondBlockRan)).toEventually(beTruthy()); + expect(@(firstBlockRan)).to(beFalsy()); + }); - qck_it(@"should schedule future blocks", ^{ - __block BOOL done = NO; + qck_it(@"should schedule future blocks", ^{ + __block BOOL done = NO; - [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ - done = YES; - }]; + [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ + done = YES; + }]; - expect(@(done)).to(beFalsy()); - expect(@(done)).toEventually(beTruthy()); - }); + expect(@(done)).to(beFalsy()); + expect(@(done)).toEventually(beTruthy()); + }); - qck_it(@"should cancel future blocks when disposed", ^{ - __block BOOL firstBlockRan = NO; - __block BOOL secondBlockRan = NO; + qck_it(@"should cancel future blocks when disposed", ^{ + __block BOOL firstBlockRan = NO; + __block BOOL secondBlockRan = NO; - RACDisposable *disposable = [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ - firstBlockRan = YES; - }]; + RACDisposable *disposable = [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ + firstBlockRan = YES; + }]; - expect(disposable).notTo(beNil()); + expect(disposable).notTo(beNil()); - [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ - secondBlockRan = YES; - }]; + [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ + secondBlockRan = YES; + }]; - [disposable dispose]; + [disposable dispose]; - expect(@(secondBlockRan)).to(beFalsy()); - expect(@(secondBlockRan)).toEventually(beTruthy()); - expect(@(firstBlockRan)).to(beFalsy()); - }); + expect(@(secondBlockRan)).to(beFalsy()); + expect(@(secondBlockRan)).toEventually(beTruthy()); + expect(@(firstBlockRan)).to(beFalsy()); + }); - qck_it(@"should schedule recurring blocks", ^{ - __block NSUInteger count = 0; + qck_it(@"should schedule recurring blocks", ^{ + __block NSUInteger count = 0; - RACDisposable *disposable = [RACScheduler.mainThreadScheduler after:[NSDate date] repeatingEvery:0.05 withLeeway:0 schedule:^{ - count++; - }]; + RACDisposable *disposable = [RACScheduler.mainThreadScheduler after:[NSDate date] repeatingEvery:0.05 withLeeway:0 schedule:^{ + count++; + }]; - expect(@(count)).to(equal(@0)); - expect(@(count)).toEventually(beGreaterThanOrEqualTo(@1)); - expect(@(count)).toEventually(beGreaterThanOrEqualTo(@2)); - expect(@(count)).toEventually(beGreaterThanOrEqualTo(@3)); + expect(@(count)).to(equal(@0)); + expect(@(count)).toEventually(beGreaterThanOrEqualTo(@1)); + expect(@(count)).toEventually(beGreaterThanOrEqualTo(@2)); + expect(@(count)).toEventually(beGreaterThanOrEqualTo(@3)); - [disposable dispose]; - [NSRunLoop.mainRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [disposable dispose]; + [NSRunLoop.mainRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - expect(@(count)).to(beGreaterThanOrEqualTo(@3)); - }); + expect(@(count)).to(beGreaterThanOrEqualTo(@3)); + }); }); qck_describe(@"+scheduler", ^{ - __block RACScheduler *scheduler; - __block NSDate * (^futureDate)(void); + __block RACScheduler *scheduler; + __block NSDate * (^futureDate)(void); - qck_beforeEach(^{ - scheduler = [RACScheduler scheduler]; + qck_beforeEach(^{ + scheduler = [RACScheduler scheduler]; - futureDate = ^{ - return [NSDate dateWithTimeIntervalSinceNow:0.01]; - }; - }); + futureDate = ^{ + return [NSDate dateWithTimeIntervalSinceNow:0.01]; + }; + }); - qck_it(@"should cancel scheduled blocks when disposed", ^{ - __block atomic_bool firstBlockRan = NO; - __block atomic_bool secondBlockRan = NO; + qck_it(@"should cancel scheduled blocks when disposed", ^{ + __block atomic_bool firstBlockRan = NO; + __block atomic_bool secondBlockRan = NO; - // Start off on the scheduler so the enqueued blocks won't run until we - // return. - [scheduler schedule:^{ - RACDisposable *disposable = [scheduler schedule:^{ - firstBlockRan = YES; - }]; + // Start off on the scheduler so the enqueued blocks won't run until we + // return. + [scheduler schedule:^{ + RACDisposable *disposable = [scheduler schedule:^{ + firstBlockRan = YES; + }]; - expect(disposable).notTo(beNil()); + expect(disposable).notTo(beNil()); - [scheduler schedule:^{ - secondBlockRan = YES; - }]; + [scheduler schedule:^{ + secondBlockRan = YES; + }]; - [disposable dispose]; - }]; + [disposable dispose]; + }]; - expect(@(secondBlockRan)).toEventually(beTruthy()); - expect(@(firstBlockRan)).to(beFalsy()); - }); + expect(@(secondBlockRan)).toEventually(beTruthy()); + expect(@(firstBlockRan)).to(beFalsy()); + }); - qck_it(@"should schedule future blocks", ^{ - __block atomic_bool done = NO; + qck_it(@"should schedule future blocks", ^{ + __block atomic_bool done = NO; - [scheduler after:futureDate() schedule:^{ - done = YES; - }]; + [scheduler after:futureDate() schedule:^{ + done = YES; + }]; - expect(@(done)).to(beFalsy()); - expect(@(done)).toEventually(beTruthy()); - }); + expect(@(done)).to(beFalsy()); + expect(@(done)).toEventually(beTruthy()); + }); - qck_it(@"should cancel future blocks when disposed", ^{ - __block atomic_bool firstBlockRan = NO; - __block atomic_bool secondBlockRan = NO; + qck_it(@"should cancel future blocks when disposed", ^{ + __block atomic_bool firstBlockRan = NO; + __block atomic_bool secondBlockRan = NO; - NSDate *date = futureDate(); - RACDisposable *disposable = [scheduler after:date schedule:^{ - firstBlockRan = YES; - }]; + NSDate *date = futureDate(); + RACDisposable *disposable = [scheduler after:date schedule:^{ + firstBlockRan = YES; + }]; - expect(disposable).notTo(beNil()); - [disposable dispose]; + expect(disposable).notTo(beNil()); + [disposable dispose]; - [scheduler after:date schedule:^{ - secondBlockRan = YES; - }]; + [scheduler after:date schedule:^{ + secondBlockRan = YES; + }]; - expect(@(secondBlockRan)).to(beFalsy()); - expect(@(secondBlockRan)).toEventually(beTruthy()); - expect(@(firstBlockRan)).to(beFalsy()); - }); + expect(@(secondBlockRan)).to(beFalsy()); + expect(@(secondBlockRan)).toEventually(beTruthy()); + expect(@(firstBlockRan)).to(beFalsy()); + }); - qck_it(@"should schedule recurring blocks", ^{ - __block atomic_uint count = 0; + qck_it(@"should schedule recurring blocks", ^{ + __block atomic_uint count = 0; - RACDisposable *disposable = [scheduler after:[NSDate date] repeatingEvery:0.05 withLeeway:0 schedule:^{ - count++; - }]; + RACDisposable *disposable = [scheduler after:[NSDate date] repeatingEvery:0.05 withLeeway:0 schedule:^{ + count++; + }]; - expect(@(count)).to(beGreaterThanOrEqualTo(@0)); - expect(@(count)).toEventually(beGreaterThanOrEqualTo(@1)); - expect(@(count)).toEventually(beGreaterThanOrEqualTo(@2)); - expect(@(count)).toEventually(beGreaterThanOrEqualTo(@3)); + expect(@(count)).to(beGreaterThanOrEqualTo(@0)); + expect(@(count)).toEventually(beGreaterThanOrEqualTo(@1)); + expect(@(count)).toEventually(beGreaterThanOrEqualTo(@2)); + expect(@(count)).toEventually(beGreaterThanOrEqualTo(@3)); - [disposable dispose]; - [NSThread sleepForTimeInterval:0.1]; + [disposable dispose]; + [NSThread sleepForTimeInterval:0.1]; - expect(@(count)).to(beGreaterThanOrEqualTo(@3)); - }); + expect(@(count)).to(beGreaterThanOrEqualTo(@3)); + }); }); qck_describe(@"+subscriptionScheduler", ^{ - qck_describe(@"setting +currentScheduler", ^{ - __block RACScheduler *currentScheduler; - - qck_beforeEach(^{ - currentScheduler = nil; - }); - - qck_it(@"should be the +mainThreadScheduler when scheduled from the main queue", ^{ - dispatch_async(dispatch_get_main_queue(), ^{ - [RACScheduler.subscriptionScheduler schedule:^{ - currentScheduler = RACScheduler.currentScheduler; - }]; - }); - - expect(currentScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); - }); - - qck_it(@"should be a +scheduler when scheduled from an unknown queue", ^{ - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [RACScheduler.subscriptionScheduler schedule:^{ - currentScheduler = RACScheduler.currentScheduler; - }]; - }); - - expect(currentScheduler).toEventuallyNot(beNil()); - expect(currentScheduler).notTo(equal(RACScheduler.mainThreadScheduler)); - }); - - qck_it(@"should equal the background scheduler from which the block was scheduled", ^{ - RACScheduler *backgroundScheduler = [RACScheduler scheduler]; - [backgroundScheduler schedule:^{ - [RACScheduler.subscriptionScheduler schedule:^{ - currentScheduler = RACScheduler.currentScheduler; - }]; - }]; - - expect(currentScheduler).toEventually(equal(backgroundScheduler)); - }); - }); - - qck_it(@"should execute scheduled blocks immediately if it's in a scheduler already", ^{ - __block atomic_bool done = NO; - __block atomic_bool executedImmediately = NO; - - [[RACScheduler scheduler] schedule:^{ - [RACScheduler.subscriptionScheduler schedule:^{ - executedImmediately = YES; - }]; - - done = YES; - }]; - - expect(@(done)).toEventually(beTruthy()); - expect(@(executedImmediately)).to(beTruthy()); - }); + qck_describe(@"setting +currentScheduler", ^{ + __block RACScheduler *currentScheduler; + + qck_beforeEach(^{ + currentScheduler = nil; + }); + + qck_it(@"should be the +mainThreadScheduler when scheduled from the main queue", ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [RACScheduler.subscriptionScheduler schedule:^{ + currentScheduler = RACScheduler.currentScheduler; + }]; + }); + + expect(currentScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); + }); + + qck_it(@"should be a +scheduler when scheduled from an unknown queue", ^{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [RACScheduler.subscriptionScheduler schedule:^{ + currentScheduler = RACScheduler.currentScheduler; + }]; + }); + + expect(currentScheduler).toEventuallyNot(beNil()); + expect(currentScheduler).notTo(equal(RACScheduler.mainThreadScheduler)); + }); + + qck_it(@"should equal the background scheduler from which the block was scheduled", ^{ + RACScheduler *backgroundScheduler = [RACScheduler scheduler]; + [backgroundScheduler schedule:^{ + [RACScheduler.subscriptionScheduler schedule:^{ + currentScheduler = RACScheduler.currentScheduler; + }]; + }]; + + expect(currentScheduler).toEventually(equal(backgroundScheduler)); + }); + }); + + qck_it(@"should execute scheduled blocks immediately if it's in a scheduler already", ^{ + __block atomic_bool done = NO; + __block atomic_bool executedImmediately = NO; + + [[RACScheduler scheduler] schedule:^{ + [RACScheduler.subscriptionScheduler schedule:^{ + executedImmediately = YES; + }]; + + done = YES; + }]; + + expect(@(done)).toEventually(beTruthy()); + expect(@(executedImmediately)).to(beTruthy()); + }); }); qck_describe(@"+immediateScheduler", ^{ - qck_it(@"should immediately execute scheduled blocks", ^{ - __block BOOL executed = NO; - RACDisposable *disposable = [RACScheduler.immediateScheduler schedule:^{ - executed = YES; - }]; - - expect(disposable).to(beNil()); - expect(@(executed)).to(beTruthy()); - }); - - qck_it(@"should block for future scheduled blocks", ^{ - __block BOOL executed = NO; - RACDisposable *disposable = [RACScheduler.immediateScheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{ - executed = YES; - }]; - - expect(@(executed)).to(beTruthy()); - expect(disposable).to(beNil()); - }); + qck_it(@"should immediately execute scheduled blocks", ^{ + __block BOOL executed = NO; + RACDisposable *disposable = [RACScheduler.immediateScheduler schedule:^{ + executed = YES; + }]; + + expect(disposable).to(beNil()); + expect(@(executed)).to(beTruthy()); + }); + + qck_it(@"should block for future scheduled blocks", ^{ + __block BOOL executed = NO; + RACDisposable *disposable = [RACScheduler.immediateScheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{ + executed = YES; + }]; + + expect(@(executed)).to(beTruthy()); + expect(disposable).to(beNil()); + }); }); qck_describe(@"-scheduleRecursiveBlock:", ^{ - qck_describe(@"with a synchronous scheduler", ^{ - qck_it(@"should behave like a normal block when it doesn't invoke itself", ^{ - __block BOOL executed = NO; - [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { - expect(@(executed)).to(beFalsy()); - executed = YES; - }]; - - expect(@(executed)).to(beTruthy()); - }); - - qck_it(@"should reschedule itself after the caller completes", ^{ - __block NSUInteger count = 0; - [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { - NSUInteger thisCount = ++count; - if (thisCount < 3) { - recurse(); - - // The block shouldn't have been invoked again yet, only - // scheduled. - expect(@(count)).to(equal(@(thisCount))); - } - }]; - - expect(@(count)).to(equal(@3)); - }); - - qck_it(@"should unroll deep recursion", ^{ - static const NSUInteger depth = 100000; - __block NSUInteger scheduleCount = 0; - [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { - scheduleCount++; - - if (scheduleCount < depth) recurse(); - }]; - - expect(@(scheduleCount)).to(equal(@(depth))); - }); - }); - - qck_describe(@"with an asynchronous scheduler", ^{ - qck_it(@"should behave like a normal block when it doesn't invoke itself", ^{ - __block BOOL executed = NO; - [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { - expect(@(executed)).to(beFalsy()); - executed = YES; - }]; - - expect(@(executed)).toEventually(beTruthy()); - }); - - qck_it(@"should reschedule itself after the caller completes", ^{ - __block NSUInteger count = 0; - [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { - NSUInteger thisCount = ++count; - if (thisCount < 3) { - recurse(); - - // The block shouldn't have been invoked again yet, only - // scheduled. - expect(@(count)).to(equal(@(thisCount))); - } - }]; - - expect(@(count)).toEventually(equal(@3)); - }); - - qck_it(@"should reschedule when invoked asynchronously", ^{ - __block atomic_uint count = 0; - - RACScheduler *asynchronousScheduler = [RACScheduler scheduler]; - [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { - [asynchronousScheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{ - atomic_uint thisCount = ++count; - if (thisCount < 3) { - recurse(); - - // The block shouldn't have been invoked again yet, only - // scheduled. - expect(@(count)).to(equal(@(thisCount))); - } - }]; - }]; - - expect(@(count)).toEventually(equal(@3)); - }); - - qck_it(@"shouldn't reschedule itself when disposed", ^{ - __block atomic_uint count = 0; - __block RACDisposable *disposable = [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { - ++count; - - expect(disposable).notTo(beNil()); - [disposable dispose]; - - recurse(); - }]; - - expect(@(count)).toEventually(equal(@1)); - }); - }); + qck_describe(@"with a synchronous scheduler", ^{ + qck_it(@"should behave like a normal block when it doesn't invoke itself", ^{ + __block BOOL executed = NO; + [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { + expect(@(executed)).to(beFalsy()); + executed = YES; + }]; + + expect(@(executed)).to(beTruthy()); + }); + + qck_it(@"should reschedule itself after the caller completes", ^{ + __block NSUInteger count = 0; + [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { + NSUInteger thisCount = ++count; + if (thisCount < 3) { + recurse(); + + // The block shouldn't have been invoked again yet, only + // scheduled. + expect(@(count)).to(equal(@(thisCount))); + } + }]; + + expect(@(count)).to(equal(@3)); + }); + + qck_it(@"should unroll deep recursion", ^{ + static const NSUInteger depth = 100000; + __block NSUInteger scheduleCount = 0; + [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { + scheduleCount++; + + if (scheduleCount < depth) recurse(); + }]; + + expect(@(scheduleCount)).to(equal(@(depth))); + }); + }); + + qck_describe(@"with an asynchronous scheduler", ^{ + qck_it(@"should behave like a normal block when it doesn't invoke itself", ^{ + __block BOOL executed = NO; + [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { + expect(@(executed)).to(beFalsy()); + executed = YES; + }]; + + expect(@(executed)).toEventually(beTruthy()); + }); + + qck_it(@"should reschedule itself after the caller completes", ^{ + __block NSUInteger count = 0; + [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { + NSUInteger thisCount = ++count; + if (thisCount < 3) { + recurse(); + + // The block shouldn't have been invoked again yet, only + // scheduled. + expect(@(count)).to(equal(@(thisCount))); + } + }]; + + expect(@(count)).toEventually(equal(@3)); + }); + + qck_it(@"should reschedule when invoked asynchronously", ^{ + __block atomic_uint count = 0; + + RACScheduler *asynchronousScheduler = [RACScheduler scheduler]; + [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { + [asynchronousScheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{ + atomic_uint thisCount = ++count; + if (thisCount < 3) { + recurse(); + + // The block shouldn't have been invoked again yet, only + // scheduled. + expect(@(count)).to(equal(@(thisCount))); + } + }]; + }]; + + expect(@(count)).toEventually(equal(@3)); + }); + + qck_it(@"shouldn't reschedule itself when disposed", ^{ + __block atomic_uint count = 0; + __block RACDisposable *disposable = [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { + ++count; + + expect(disposable).notTo(beNil()); + [disposable dispose]; + + recurse(); + }]; + + expect(@(count)).toEventually(equal(@1)); + }); + }); }); qck_describe(@"subclassing", ^{ - __block RACTestExampleScheduler *scheduler; - - qck_beforeEach(^{ - scheduler = [[RACTestExampleScheduler alloc] initWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; - }); - - qck_it(@"should invoke blocks scheduled with -schedule:", ^{ - __block atomic_bool invoked = NO; - [scheduler schedule:^{ - invoked = YES; - }]; - - expect(@(invoked)).toEventually(beTruthy()); - }); - - qck_it(@"should invoke blocks scheduled with -after:schedule:", ^{ - __block atomic_bool invoked = NO; - [scheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{ - invoked = YES; - }]; - - expect(@(invoked)).toEventually(beTruthy()); - }); - - qck_it(@"should set a valid current scheduler", ^{ - __block RACScheduler *currentScheduler; - [scheduler schedule:^{ - currentScheduler = RACScheduler.currentScheduler; - }]; - - expect(currentScheduler).toEventually(equal(scheduler)); - }); + __block RACTestExampleScheduler *scheduler; + + qck_beforeEach(^{ + scheduler = [[RACTestExampleScheduler alloc] initWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + }); + + qck_it(@"should invoke blocks scheduled with -schedule:", ^{ + __block atomic_bool invoked = NO; + [scheduler schedule:^{ + invoked = YES; + }]; + + expect(@(invoked)).toEventually(beTruthy()); + }); + + qck_it(@"should invoke blocks scheduled with -after:schedule:", ^{ + __block atomic_bool invoked = NO; + [scheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{ + invoked = YES; + }]; + + expect(@(invoked)).toEventually(beTruthy()); + }); + + qck_it(@"should set a valid current scheduler", ^{ + __block RACScheduler *currentScheduler; + [scheduler schedule:^{ + currentScheduler = RACScheduler.currentScheduler; + }]; + + expect(currentScheduler).toEventually(equal(scheduler)); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACSequenceAdditionsSpec.m b/ReactiveObjCTests/RACSequenceAdditionsSpec.m index 1baed5a46..f391bf2c5 100644 --- a/ReactiveObjCTests/RACSequenceAdditionsSpec.m +++ b/ReactiveObjCTests/RACSequenceAdditionsSpec.m @@ -25,335 +25,335 @@ __block NSArray *numbers; qck_beforeEach(^{ - NSMutableArray *mutableNumbers = [NSMutableArray array]; - for (NSUInteger i = 0; i < 100; i++) { - [mutableNumbers addObject:@(i)]; - } + NSMutableArray *mutableNumbers = [NSMutableArray array]; + for (NSUInteger i = 0; i < 100; i++) { + [mutableNumbers addObject:@(i)]; + } - numbers = [mutableNumbers copy]; + numbers = [mutableNumbers copy]; }); qck_describe(@"NSArray sequences", ^{ - __block NSMutableArray *values; - __block RACSequence *sequence; - - qck_beforeEach(^{ - values = [numbers mutableCopy]; - sequence = values.rac_sequence; - expect(sequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: values - }; - }); - - qck_describe(@"should be immutable", ^{ - __block NSArray *unchangedValues; - - qck_beforeEach(^{ - unchangedValues = [values copy]; - [values addObject:@6]; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: unchangedValues - }; - }); - }); - - qck_it(@"should fast enumerate after zipping", ^{ - // This certain list of values causes issues, for some reason. - NSArray *values = @[ @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0 ]; - RACSequence *zippedSequence = [RACSequence zip:@[ values.rac_sequence, values.rac_sequence ] reduce:^(id obj1, id obj2) { - return obj1; - }]; - - NSMutableArray *collectedValues = [NSMutableArray array]; - for (id value in zippedSequence) { - [collectedValues addObject:value]; - } - - expect(collectedValues).to(equal(values)); - }); + __block NSMutableArray *values; + __block RACSequence *sequence; + + qck_beforeEach(^{ + values = [numbers mutableCopy]; + sequence = values.rac_sequence; + expect(sequence).notTo(beNil()); + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: values + }; + }); + + qck_describe(@"should be immutable", ^{ + __block NSArray *unchangedValues; + + qck_beforeEach(^{ + unchangedValues = [values copy]; + [values addObject:@6]; + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: unchangedValues + }; + }); + }); + + qck_it(@"should fast enumerate after zipping", ^{ + // This certain list of values causes issues, for some reason. + NSArray *values = @[ @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0 ]; + RACSequence *zippedSequence = [RACSequence zip:@[ values.rac_sequence, values.rac_sequence ] reduce:^(id obj1, id obj2) { + return obj1; + }]; + + NSMutableArray *collectedValues = [NSMutableArray array]; + for (id value in zippedSequence) { + [collectedValues addObject:value]; + } + + expect(collectedValues).to(equal(values)); + }); }); qck_describe(@"NSDictionary sequences", ^{ - __block NSMutableDictionary *dict; - - __block NSMutableArray *tuples; - __block RACSequence *tupleSequence; - - __block NSArray *keys; - __block RACSequence *keySequence; - - __block NSArray *values; - __block RACSequence *valueSequence; - - qck_beforeEach(^{ - dict = [@{ - @"foo": @"bar", - @"baz": @"buzz", - @5: NSNull.null - } mutableCopy]; - - tuples = [NSMutableArray array]; - for (id key in dict) { - RACTuple *tuple = RACTuplePack(key, dict[key]); - [tuples addObject:tuple]; - } - - tupleSequence = dict.rac_sequence; - expect(tupleSequence).notTo(beNil()); - - keys = [dict.allKeys copy]; - keySequence = dict.rac_keySequence; - expect(keySequence).notTo(beNil()); - - values = [dict.allValues copy]; - valueSequence = dict.rac_valueSequence; - expect(valueSequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: tupleSequence, - RACSequenceExampleExpectedValues: tuples - }; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: keySequence, - RACSequenceExampleExpectedValues: keys - }; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: valueSequence, - RACSequenceExampleExpectedValues: values - }; - }); - - qck_describe(@"should be immutable", ^{ - qck_beforeEach(^{ - dict[@"foo"] = @"rab"; - dict[@6] = @7; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: tupleSequence, - RACSequenceExampleExpectedValues: tuples - }; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: keySequence, - RACSequenceExampleExpectedValues: keys - }; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: valueSequence, - RACSequenceExampleExpectedValues: values - }; - }); - }); + __block NSMutableDictionary *dict; + + __block NSMutableArray *tuples; + __block RACSequence *tupleSequence; + + __block NSArray *keys; + __block RACSequence *keySequence; + + __block NSArray *values; + __block RACSequence *valueSequence; + + qck_beforeEach(^{ + dict = [@{ + @"foo": @"bar", + @"baz": @"buzz", + @5: NSNull.null + } mutableCopy]; + + tuples = [NSMutableArray array]; + for (id key in dict) { + RACTuple *tuple = RACTuplePack(key, dict[key]); + [tuples addObject:tuple]; + } + + tupleSequence = dict.rac_sequence; + expect(tupleSequence).notTo(beNil()); + + keys = [dict.allKeys copy]; + keySequence = dict.rac_keySequence; + expect(keySequence).notTo(beNil()); + + values = [dict.allValues copy]; + valueSequence = dict.rac_valueSequence; + expect(valueSequence).notTo(beNil()); + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: tupleSequence, + RACSequenceExampleExpectedValues: tuples + }; + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: keySequence, + RACSequenceExampleExpectedValues: keys + }; + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: valueSequence, + RACSequenceExampleExpectedValues: values + }; + }); + + qck_describe(@"should be immutable", ^{ + qck_beforeEach(^{ + dict[@"foo"] = @"rab"; + dict[@6] = @7; + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: tupleSequence, + RACSequenceExampleExpectedValues: tuples + }; + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: keySequence, + RACSequenceExampleExpectedValues: keys + }; + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: valueSequence, + RACSequenceExampleExpectedValues: values + }; + }); + }); }); qck_describe(@"NSOrderedSet sequences", ^{ - __block NSMutableOrderedSet *values; - __block RACSequence *sequence; - - qck_beforeEach(^{ - values = [NSMutableOrderedSet orderedSetWithArray:numbers]; - sequence = values.rac_sequence; - expect(sequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: values.array - }; - }); - - qck_describe(@"should be immutable", ^{ - __block NSArray *unchangedValues; - - qck_beforeEach(^{ - unchangedValues = [values.array copy]; - [values addObject:@6]; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: unchangedValues - }; - }); - }); + __block NSMutableOrderedSet *values; + __block RACSequence *sequence; + + qck_beforeEach(^{ + values = [NSMutableOrderedSet orderedSetWithArray:numbers]; + sequence = values.rac_sequence; + expect(sequence).notTo(beNil()); + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: values.array + }; + }); + + qck_describe(@"should be immutable", ^{ + __block NSArray *unchangedValues; + + qck_beforeEach(^{ + unchangedValues = [values.array copy]; + [values addObject:@6]; + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: unchangedValues + }; + }); + }); }); qck_describe(@"NSSet sequences", ^{ - __block NSMutableSet *values; - __block RACSequence *sequence; - - qck_beforeEach(^{ - values = [NSMutableSet setWithArray:numbers]; - sequence = values.rac_sequence; - expect(sequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: values.allObjects - }; - }); - - qck_describe(@"should be immutable", ^{ - __block NSArray *unchangedValues; - - qck_beforeEach(^{ - unchangedValues = [values.allObjects copy]; - [values addObject:@6]; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: unchangedValues - }; - }); - }); + __block NSMutableSet *values; + __block RACSequence *sequence; + + qck_beforeEach(^{ + values = [NSMutableSet setWithArray:numbers]; + sequence = values.rac_sequence; + expect(sequence).notTo(beNil()); + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: values.allObjects + }; + }); + + qck_describe(@"should be immutable", ^{ + __block NSArray *unchangedValues; + + qck_beforeEach(^{ + unchangedValues = [values.allObjects copy]; + [values addObject:@6]; + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: unchangedValues + }; + }); + }); }); qck_describe(@"NSString sequences", ^{ - __block NSMutableString *string; - __block NSArray *values; - __block RACSequence *sequence; - - qck_beforeEach(^{ - string = [@"foobar" mutableCopy]; - values = @[ @"f", @"o", @"o", @"b", @"a", @"r" ]; - sequence = string.rac_sequence; - expect(sequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: values - }; - }); - - qck_describe(@"should be immutable", ^{ - qck_beforeEach(^{ - [string appendString:@"buzz"]; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: values - }; - }); - }); - - qck_it(@"should work with composed characters", ^{ - NSString *string = @"\u2665\uFE0F\u2666\uFE0F"; - NSArray *expectedSequence = @[ @"\u2665\uFE0F", @"\u2666\uFE0F" ]; - expect(string.rac_sequence.array).to(equal(expectedSequence)); - }); + __block NSMutableString *string; + __block NSArray *values; + __block RACSequence *sequence; + + qck_beforeEach(^{ + string = [@"foobar" mutableCopy]; + values = @[ @"f", @"o", @"o", @"b", @"a", @"r" ]; + sequence = string.rac_sequence; + expect(sequence).notTo(beNil()); + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: values + }; + }); + + qck_describe(@"should be immutable", ^{ + qck_beforeEach(^{ + [string appendString:@"buzz"]; + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: values + }; + }); + }); + + qck_it(@"should work with composed characters", ^{ + NSString *string = @"\u2665\uFE0F\u2666\uFE0F"; + NSArray *expectedSequence = @[ @"\u2665\uFE0F", @"\u2666\uFE0F" ]; + expect(string.rac_sequence.array).to(equal(expectedSequence)); + }); }); qck_describe(@"RACTuple sequences", ^{ - __block RACTuple *tuple; - __block RACSequence *sequence; - - qck_beforeEach(^{ - tuple = RACTuplePack(@"foo", nil, @"bar", NSNull.null, RACTupleNil.tupleNil); - - sequence = tuple.rac_sequence; - expect(sequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: @[ @"foo", NSNull.null, @"bar", NSNull.null, NSNull.null ] - }; - }); + __block RACTuple *tuple; + __block RACSequence *sequence; + + qck_beforeEach(^{ + tuple = RACTuplePack(@"foo", nil, @"bar", NSNull.null, RACTupleNil.tupleNil); + + sequence = tuple.rac_sequence; + expect(sequence).notTo(beNil()); + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: @[ @"foo", NSNull.null, @"bar", NSNull.null, NSNull.null ] + }; + }); }); qck_describe(@"NSIndexSet sequences", ^{ - __block NSMutableIndexSet *values; - __block RACSequence *sequence; - - NSArray * (^valuesFromIndexSet)(NSIndexSet *indexSet) = ^NSArray *(NSIndexSet *indexSet) { - NSMutableArray *arr = [NSMutableArray array]; - [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - [arr addObject:@(idx)]; - }]; - - return [arr copy]; - }; - - qck_beforeEach(^{ - values = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 10)]; - sequence = values.rac_sequence; - expect(sequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: valuesFromIndexSet(values) - }; - }); - - qck_describe(@"should be immutable", ^{ - __block NSArray *unchangedValues; - - qck_beforeEach(^{ - unchangedValues = valuesFromIndexSet(values); - [values addIndex:20]; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: unchangedValues - }; - }); - }); - - qck_describe(@"should not fire if empty", ^{ - __block NSIndexSet *emptyIndexSet; - __block RACSequence *emptySequence; - - qck_beforeEach(^{ - emptyIndexSet = [NSIndexSet indexSet]; - emptySequence = emptyIndexSet.rac_sequence; - expect(emptySequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: emptySequence, - RACSequenceExampleExpectedValues: valuesFromIndexSet(emptyIndexSet) - }; - }); - }); + __block NSMutableIndexSet *values; + __block RACSequence *sequence; + + NSArray * (^valuesFromIndexSet)(NSIndexSet *indexSet) = ^NSArray *(NSIndexSet *indexSet) { + NSMutableArray *arr = [NSMutableArray array]; + [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + [arr addObject:@(idx)]; + }]; + + return [arr copy]; + }; + + qck_beforeEach(^{ + values = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 10)]; + sequence = values.rac_sequence; + expect(sequence).notTo(beNil()); + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: valuesFromIndexSet(values) + }; + }); + + qck_describe(@"should be immutable", ^{ + __block NSArray *unchangedValues; + + qck_beforeEach(^{ + unchangedValues = valuesFromIndexSet(values); + [values addIndex:20]; + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: unchangedValues + }; + }); + }); + + qck_describe(@"should not fire if empty", ^{ + __block NSIndexSet *emptyIndexSet; + __block RACSequence *emptySequence; + + qck_beforeEach(^{ + emptyIndexSet = [NSIndexSet indexSet]; + emptySequence = emptyIndexSet.rac_sequence; + expect(emptySequence).notTo(beNil()); + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: emptySequence, + RACSequenceExampleExpectedValues: valuesFromIndexSet(emptyIndexSet) + }; + }); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACSequenceExamples.m b/ReactiveObjCTests/RACSequenceExamples.m index 1d7268a9a..50b13f0b6 100644 --- a/ReactiveObjCTests/RACSequenceExamples.m +++ b/ReactiveObjCTests/RACSequenceExamples.m @@ -24,110 +24,110 @@ QuickConfigurationBegin(RACSequenceExampleGroups) + (void)configure:(Configuration *)configuration { - sharedExamples(RACSequenceExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block RACSequence *sequence; - __block NSArray *values; - - qck_beforeEach(^{ - sequence = exampleContext()[RACSequenceExampleSequence]; - values = [exampleContext()[RACSequenceExampleExpectedValues] copy]; - }); - - qck_it(@"should implement ", ^{ - NSMutableArray *collectedValues = [NSMutableArray array]; - for (id value in sequence) { - [collectedValues addObject:value]; - } - - expect(collectedValues).to(equal(values)); - }); - - qck_it(@"should return an array", ^{ - expect(sequence.array).to(equal(values)); - }); - - qck_describe(@"-signalWithScheduler:", ^{ - qck_it(@"should return an immediately scheduled signal", ^{ - RACSignal *signal = [sequence signalWithScheduler:RACScheduler.immediateScheduler]; - expect(signal.toArray).to(equal(values)); - }); - - qck_it(@"should return a background scheduled signal", ^{ - RACSignal *signal = [sequence signalWithScheduler:[RACScheduler scheduler]]; - expect(signal.toArray).to(equal(values)); - }); - - qck_it(@"should only evaluate one value per scheduling", ^{ - RACScheduler* scheduler = [RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh]; - RACSignal *signal = [sequence signalWithScheduler:scheduler]; - - __block atomic_bool flag = YES; - __block atomic_bool completed = NO; - [signal subscribeNext:^(id x) { - expect(@(flag)).to(beTruthy()); - flag = NO; - - [scheduler schedule:^{ - // This should get executed before the next value (which - // verifies that it's YES). - flag = YES; - }]; - } completed:^{ - completed = YES; - }]; - - expect(@(completed)).toEventually(beTruthy()); - }); - }); - - qck_it(@"should be equal to itself", ^{ - expect(sequence).to(equal(sequence)); - }); - - qck_it(@"should be equal to the same sequence of values", ^{ - RACSequence *newSequence = RACSequence.empty; - for (id value in values) { - RACSequence *valueSeq = [RACSequence return:value]; - expect(valueSeq).notTo(beNil()); - - newSequence = [newSequence concat:valueSeq]; - } - - expect(sequence).to(equal(newSequence)); - expect(@(sequence.hash)).to(equal(@(newSequence.hash))); - }); - - qck_it(@"should not be equal to a different sequence of values", ^{ - RACSequence *anotherSequence = [RACSequence return:@(-1)]; - expect(sequence).notTo(equal(anotherSequence)); - }); - - qck_it(@"should return an identical object for -copy", ^{ - expect([sequence copy]).to(beIdenticalTo(sequence)); - }); - - qck_it(@"should archive", ^{ - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:sequence]; - expect(data).notTo(beNil()); - - RACSequence *unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data]; - expect(unarchived).to(equal(sequence)); - }); - - qck_it(@"should fold right", ^{ - RACSequence *result = [sequence foldRightWithStart:[RACSequence empty] reduce:^(id first, RACSequence *rest) { - return [rest.head startWith:first]; - }]; - expect(result.array).to(equal(values)); - }); - - qck_it(@"should fold left", ^{ - RACSequence *result = [sequence foldLeftWithStart:[RACSequence empty] reduce:^(RACSequence *first, id rest) { - return [first concat:[RACSequence return:rest]]; - }]; - expect(result.array).to(equal(values)); - }); - }); + sharedExamples(RACSequenceExamples, ^(QCKDSLSharedExampleContext exampleContext) { + __block RACSequence *sequence; + __block NSArray *values; + + qck_beforeEach(^{ + sequence = exampleContext()[RACSequenceExampleSequence]; + values = [exampleContext()[RACSequenceExampleExpectedValues] copy]; + }); + + qck_it(@"should implement ", ^{ + NSMutableArray *collectedValues = [NSMutableArray array]; + for (id value in sequence) { + [collectedValues addObject:value]; + } + + expect(collectedValues).to(equal(values)); + }); + + qck_it(@"should return an array", ^{ + expect(sequence.array).to(equal(values)); + }); + + qck_describe(@"-signalWithScheduler:", ^{ + qck_it(@"should return an immediately scheduled signal", ^{ + RACSignal *signal = [sequence signalWithScheduler:RACScheduler.immediateScheduler]; + expect(signal.toArray).to(equal(values)); + }); + + qck_it(@"should return a background scheduled signal", ^{ + RACSignal *signal = [sequence signalWithScheduler:[RACScheduler scheduler]]; + expect(signal.toArray).to(equal(values)); + }); + + qck_it(@"should only evaluate one value per scheduling", ^{ + RACScheduler* scheduler = [RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh]; + RACSignal *signal = [sequence signalWithScheduler:scheduler]; + + __block atomic_bool flag = YES; + __block atomic_bool completed = NO; + [signal subscribeNext:^(id x) { + expect(@(flag)).to(beTruthy()); + flag = NO; + + [scheduler schedule:^{ + // This should get executed before the next value (which + // verifies that it's YES). + flag = YES; + }]; + } completed:^{ + completed = YES; + }]; + + expect(@(completed)).toEventually(beTruthy()); + }); + }); + + qck_it(@"should be equal to itself", ^{ + expect(sequence).to(equal(sequence)); + }); + + qck_it(@"should be equal to the same sequence of values", ^{ + RACSequence *newSequence = RACSequence.empty; + for (id value in values) { + RACSequence *valueSeq = [RACSequence return:value]; + expect(valueSeq).notTo(beNil()); + + newSequence = [newSequence concat:valueSeq]; + } + + expect(sequence).to(equal(newSequence)); + expect(@(sequence.hash)).to(equal(@(newSequence.hash))); + }); + + qck_it(@"should not be equal to a different sequence of values", ^{ + RACSequence *anotherSequence = [RACSequence return:@(-1)]; + expect(sequence).notTo(equal(anotherSequence)); + }); + + qck_it(@"should return an identical object for -copy", ^{ + expect([sequence copy]).to(beIdenticalTo(sequence)); + }); + + qck_it(@"should archive", ^{ + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:sequence]; + expect(data).notTo(beNil()); + + RACSequence *unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + expect(unarchived).to(equal(sequence)); + }); + + qck_it(@"should fold right", ^{ + RACSequence *result = [sequence foldRightWithStart:[RACSequence empty] reduce:^(id first, RACSequence *rest) { + return [rest.head startWith:first]; + }]; + expect(result.array).to(equal(values)); + }); + + qck_it(@"should fold left", ^{ + RACSequence *result = [sequence foldLeftWithStart:[RACSequence empty] reduce:^(RACSequence *first, id rest) { + return [first concat:[RACSequence return:rest]]; + }]; + expect(result.array).to(equal(values)); + }); + }); } QuickConfigurationEnd diff --git a/ReactiveObjCTests/RACSequenceSpec.m b/ReactiveObjCTests/RACSequenceSpec.m index 6d3feab58..05dd22993 100644 --- a/ReactiveObjCTests/RACSequenceSpec.m +++ b/ReactiveObjCTests/RACSequenceSpec.m @@ -25,421 +25,421 @@ QuickSpecBegin(RACSequenceSpec) qck_describe(@"RACStream", ^{ - id verifyValues = ^(RACSequence *sequence, NSArray *expectedValues) { - NSMutableArray *collectedValues = [NSMutableArray array]; - while (sequence.head != nil) { - [collectedValues addObject:sequence.head]; - sequence = sequence.tail; - } - - expect(collectedValues).to(equal(expectedValues)); - }; - - __block RACSequence *infiniteSequence = [RACSequence sequenceWithHeadBlock:^{ - return RACUnit.defaultUnit; - } tailBlock:^{ - return infiniteSequence; - }]; - - qck_itBehavesLike(RACStreamExamples, ^{ - return @{ - RACStreamExamplesClass: RACSequence.class, - RACStreamExamplesVerifyValuesBlock: verifyValues, - RACStreamExamplesInfiniteStream: infiniteSequence - }; - }); + id verifyValues = ^(RACSequence *sequence, NSArray *expectedValues) { + NSMutableArray *collectedValues = [NSMutableArray array]; + while (sequence.head != nil) { + [collectedValues addObject:sequence.head]; + sequence = sequence.tail; + } + + expect(collectedValues).to(equal(expectedValues)); + }; + + __block RACSequence *infiniteSequence = [RACSequence sequenceWithHeadBlock:^{ + return RACUnit.defaultUnit; + } tailBlock:^{ + return infiniteSequence; + }]; + + qck_itBehavesLike(RACStreamExamples, ^{ + return @{ + RACStreamExamplesClass: RACSequence.class, + RACStreamExamplesVerifyValuesBlock: verifyValues, + RACStreamExamplesInfiniteStream: infiniteSequence + }; + }); }); qck_describe(@"+sequenceWithHeadBlock:tailBlock:", ^{ - __block RACSequence *sequence; - __block BOOL headInvoked; - __block BOOL tailInvoked; - - qck_beforeEach(^{ - headInvoked = NO; - tailInvoked = NO; - - sequence = [RACSequence sequenceWithHeadBlock:^{ - headInvoked = YES; - return @0; - } tailBlock:^{ - tailInvoked = YES; - return [RACSequence return:@1]; - }]; - - expect(sequence).notTo(beNil()); - }); - - qck_it(@"should use the values from the head and tail blocks", ^{ - expect(sequence.head).to(equal(@0)); - expect(sequence.tail.head).to(equal(@1)); - expect(sequence.tail.tail).to(beNil()); - }); - - qck_it(@"should lazily invoke head and tail blocks", ^{ - expect(@(headInvoked)).to(beFalsy()); - expect(@(tailInvoked)).to(beFalsy()); - - expect(sequence.head).to(equal(@0)); - expect(@(headInvoked)).to(beTruthy()); - expect(@(tailInvoked)).to(beFalsy()); - - expect(sequence.tail).notTo(beNil()); - expect(@(tailInvoked)).to(beTruthy()); - }); - - qck_context(@"behaves like a sequence", ^{ - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: @[ @0, @1 ] - }; - }); - }); + __block RACSequence *sequence; + __block BOOL headInvoked; + __block BOOL tailInvoked; + + qck_beforeEach(^{ + headInvoked = NO; + tailInvoked = NO; + + sequence = [RACSequence sequenceWithHeadBlock:^{ + headInvoked = YES; + return @0; + } tailBlock:^{ + tailInvoked = YES; + return [RACSequence return:@1]; + }]; + + expect(sequence).notTo(beNil()); + }); + + qck_it(@"should use the values from the head and tail blocks", ^{ + expect(sequence.head).to(equal(@0)); + expect(sequence.tail.head).to(equal(@1)); + expect(sequence.tail.tail).to(beNil()); + }); + + qck_it(@"should lazily invoke head and tail blocks", ^{ + expect(@(headInvoked)).to(beFalsy()); + expect(@(tailInvoked)).to(beFalsy()); + + expect(sequence.head).to(equal(@0)); + expect(@(headInvoked)).to(beTruthy()); + expect(@(tailInvoked)).to(beFalsy()); + + expect(sequence.tail).notTo(beNil()); + expect(@(tailInvoked)).to(beTruthy()); + }); + + qck_context(@"behaves like a sequence", ^{ + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: @[ @0, @1 ] + }; + }); + }); }); qck_describe(@"empty sequences", ^{ - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: [RACSequence empty], - RACSequenceExampleExpectedValues: @[] - }; - }); + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: [RACSequence empty], + RACSequenceExampleExpectedValues: @[] + }; + }); }); qck_describe(@"non-empty sequences", ^{ - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]], - RACSequenceExampleExpectedValues: @[ @0, @1, @2 ] - }; - }); + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]], + RACSequenceExampleExpectedValues: @[ @0, @1, @2 ] + }; + }); }); qck_describe(@"eager sequences", ^{ - __block RACSequence *lazySequence; - __block BOOL headInvoked; - __block BOOL tailInvoked; - - NSArray *values = @[ @0, @1 ]; - - qck_beforeEach(^{ - headInvoked = NO; - tailInvoked = NO; - - lazySequence = [RACSequence sequenceWithHeadBlock:^{ - headInvoked = YES; - return @0; - } tailBlock:^{ - tailInvoked = YES; - return [RACSequence return:@1]; - }]; - - expect(lazySequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: lazySequence.eagerSequence, - RACSequenceExampleExpectedValues: values - }; - }); - - qck_it(@"should evaluate all values immediately", ^{ - RACSequence *eagerSequence = lazySequence.eagerSequence; - expect(@(headInvoked)).to(beTruthy()); - expect(@(tailInvoked)).to(beTruthy()); - expect(eagerSequence.array).to(equal(values)); - }); + __block RACSequence *lazySequence; + __block BOOL headInvoked; + __block BOOL tailInvoked; + + NSArray *values = @[ @0, @1 ]; + + qck_beforeEach(^{ + headInvoked = NO; + tailInvoked = NO; + + lazySequence = [RACSequence sequenceWithHeadBlock:^{ + headInvoked = YES; + return @0; + } tailBlock:^{ + tailInvoked = YES; + return [RACSequence return:@1]; + }]; + + expect(lazySequence).notTo(beNil()); + }); + + qck_itBehavesLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: lazySequence.eagerSequence, + RACSequenceExampleExpectedValues: values + }; + }); + + qck_it(@"should evaluate all values immediately", ^{ + RACSequence *eagerSequence = lazySequence.eagerSequence; + expect(@(headInvoked)).to(beTruthy()); + expect(@(tailInvoked)).to(beTruthy()); + expect(eagerSequence.array).to(equal(values)); + }); }); qck_describe(@"-take:", ^{ - qck_it(@"should complete take: without needing the head of the second item in the sequence", ^{ - __block NSUInteger valuesTaken = 0; - - __block RACSequence *sequence = [RACSequence sequenceWithHeadBlock:^{ - ++valuesTaken; - return RACUnit.defaultUnit; - } tailBlock:^{ - return sequence; - }]; - - NSArray *values = [sequence take:1].array; - expect(values).to(equal(@[ RACUnit.defaultUnit ])); - expect(@(valuesTaken)).to(equal(@1)); - }); + qck_it(@"should complete take: without needing the head of the second item in the sequence", ^{ + __block NSUInteger valuesTaken = 0; + + __block RACSequence *sequence = [RACSequence sequenceWithHeadBlock:^{ + ++valuesTaken; + return RACUnit.defaultUnit; + } tailBlock:^{ + return sequence; + }]; + + NSArray *values = [sequence take:1].array; + expect(values).to(equal(@[ RACUnit.defaultUnit ])); + expect(@(valuesTaken)).to(equal(@1)); + }); }); qck_describe(@"-bind:", ^{ - qck_it(@"should only evaluate head when the resulting sequence is evaluated", ^{ - __block BOOL headInvoked = NO; - - RACSequence *original = [RACSequence sequenceWithHeadBlock:^{ - headInvoked = YES; - return RACUnit.defaultUnit; - } tailBlock:^ id { - return nil; - }]; - - RACSequence *bound = [original bind:^{ - return ^(id value, BOOL *stop) { - return [RACSequence return:value]; - }; - }]; - - expect(bound).notTo(beNil()); - expect(@(headInvoked)).to(beFalsy()); - - expect(bound.head).to(equal(RACUnit.defaultUnit)); - expect(@(headInvoked)).to(beTruthy()); - }); + qck_it(@"should only evaluate head when the resulting sequence is evaluated", ^{ + __block BOOL headInvoked = NO; + + RACSequence *original = [RACSequence sequenceWithHeadBlock:^{ + headInvoked = YES; + return RACUnit.defaultUnit; + } tailBlock:^ id { + return nil; + }]; + + RACSequence *bound = [original bind:^{ + return ^(id value, BOOL *stop) { + return [RACSequence return:value]; + }; + }]; + + expect(bound).notTo(beNil()); + expect(@(headInvoked)).to(beFalsy()); + + expect(bound.head).to(equal(RACUnit.defaultUnit)); + expect(@(headInvoked)).to(beTruthy()); + }); }); qck_describe(@"-objectEnumerator", ^{ - qck_it(@"should only evaluate head as it's enumerated", ^{ - __block BOOL firstHeadInvoked = NO; - __block BOOL secondHeadInvoked = NO; - __block BOOL thirdHeadInvoked = NO; - - RACSequence *sequence = [RACSequence sequenceWithHeadBlock:^id{ - firstHeadInvoked = YES; - return @1; - } tailBlock:^RACSequence *{ - return [RACSequence sequenceWithHeadBlock:^id{ - secondHeadInvoked = YES; - return @2; - } tailBlock:^RACSequence *{ - return [RACSequence sequenceWithHeadBlock:^id{ - thirdHeadInvoked = YES; - return @3; - } tailBlock:^RACSequence *{ - return RACSequence.empty; - }]; - }]; - }]; - NSEnumerator *enumerator = sequence.objectEnumerator; - - expect(@(firstHeadInvoked)).to(beFalsy()); - expect(@(secondHeadInvoked)).to(beFalsy()); - expect(@(thirdHeadInvoked)).to(beFalsy()); - - expect([enumerator nextObject]).to(equal(@1)); - - expect(@(firstHeadInvoked)).to(beTruthy()); - expect(@(secondHeadInvoked)).to(beFalsy()); - expect(@(thirdHeadInvoked)).to(beFalsy()); - - expect([enumerator nextObject]).to(equal(@2)); - - expect(@(secondHeadInvoked)).to(beTruthy()); - expect(@(thirdHeadInvoked)).to(beFalsy()); - - expect([enumerator nextObject]).to(equal(@3)); - - expect(@(thirdHeadInvoked)).to(beTruthy()); - - expect([enumerator nextObject]).to(beNil()); - }); - - qck_it(@"should let the sequence dealloc as it's enumerated", ^{ - __block BOOL firstSequenceDeallocd = NO; - __block BOOL secondSequenceDeallocd = NO; - __block BOOL thirdSequenceDeallocd = NO; - - NSEnumerator *enumerator = nil; - - @autoreleasepool { - RACSequence *thirdSequence __attribute__((objc_precise_lifetime)) = [RACSequence sequenceWithHeadBlock:^id{ - return @3; - } tailBlock:^RACSequence *{ - return RACSequence.empty; - }]; - [thirdSequence.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - thirdSequenceDeallocd = YES; - }]]; - - RACSequence *secondSequence __attribute__((objc_precise_lifetime)) = [RACSequence sequenceWithHeadBlock:^id{ - return @2; - } tailBlock:^RACSequence *{ - return thirdSequence; - }]; - [secondSequence.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - secondSequenceDeallocd = YES; - }]]; - - RACSequence *firstSequence __attribute__((objc_precise_lifetime)) = [RACSequence sequenceWithHeadBlock:^id{ - return @1; - } tailBlock:^RACSequence *{ - return secondSequence; - }]; - [firstSequence.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - firstSequenceDeallocd = YES; - }]]; - - enumerator = firstSequence.objectEnumerator; - } - - @autoreleasepool { - expect([enumerator nextObject]).to(equal(@1)); - } - - @autoreleasepool { - expect([enumerator nextObject]).to(equal(@2)); - } - expect(@(firstSequenceDeallocd)).toEventually(beTruthy()); - - @autoreleasepool { - expect([enumerator nextObject]).to(equal(@3)); - } - expect(@(secondSequenceDeallocd)).toEventually(beTruthy()); - - @autoreleasepool { - expect([enumerator nextObject]).to(beNil()); - } - expect(@(thirdSequenceDeallocd)).toEventually(beTruthy()); - }); + qck_it(@"should only evaluate head as it's enumerated", ^{ + __block BOOL firstHeadInvoked = NO; + __block BOOL secondHeadInvoked = NO; + __block BOOL thirdHeadInvoked = NO; + + RACSequence *sequence = [RACSequence sequenceWithHeadBlock:^id{ + firstHeadInvoked = YES; + return @1; + } tailBlock:^RACSequence *{ + return [RACSequence sequenceWithHeadBlock:^id{ + secondHeadInvoked = YES; + return @2; + } tailBlock:^RACSequence *{ + return [RACSequence sequenceWithHeadBlock:^id{ + thirdHeadInvoked = YES; + return @3; + } tailBlock:^RACSequence *{ + return RACSequence.empty; + }]; + }]; + }]; + NSEnumerator *enumerator = sequence.objectEnumerator; + + expect(@(firstHeadInvoked)).to(beFalsy()); + expect(@(secondHeadInvoked)).to(beFalsy()); + expect(@(thirdHeadInvoked)).to(beFalsy()); + + expect([enumerator nextObject]).to(equal(@1)); + + expect(@(firstHeadInvoked)).to(beTruthy()); + expect(@(secondHeadInvoked)).to(beFalsy()); + expect(@(thirdHeadInvoked)).to(beFalsy()); + + expect([enumerator nextObject]).to(equal(@2)); + + expect(@(secondHeadInvoked)).to(beTruthy()); + expect(@(thirdHeadInvoked)).to(beFalsy()); + + expect([enumerator nextObject]).to(equal(@3)); + + expect(@(thirdHeadInvoked)).to(beTruthy()); + + expect([enumerator nextObject]).to(beNil()); + }); + + qck_it(@"should let the sequence dealloc as it's enumerated", ^{ + __block BOOL firstSequenceDeallocd = NO; + __block BOOL secondSequenceDeallocd = NO; + __block BOOL thirdSequenceDeallocd = NO; + + NSEnumerator *enumerator = nil; + + @autoreleasepool { + RACSequence *thirdSequence __attribute__((objc_precise_lifetime)) = [RACSequence sequenceWithHeadBlock:^id{ + return @3; + } tailBlock:^RACSequence *{ + return RACSequence.empty; + }]; + [thirdSequence.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + thirdSequenceDeallocd = YES; + }]]; + + RACSequence *secondSequence __attribute__((objc_precise_lifetime)) = [RACSequence sequenceWithHeadBlock:^id{ + return @2; + } tailBlock:^RACSequence *{ + return thirdSequence; + }]; + [secondSequence.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + secondSequenceDeallocd = YES; + }]]; + + RACSequence *firstSequence __attribute__((objc_precise_lifetime)) = [RACSequence sequenceWithHeadBlock:^id{ + return @1; + } tailBlock:^RACSequence *{ + return secondSequence; + }]; + [firstSequence.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + firstSequenceDeallocd = YES; + }]]; + + enumerator = firstSequence.objectEnumerator; + } + + @autoreleasepool { + expect([enumerator nextObject]).to(equal(@1)); + } + + @autoreleasepool { + expect([enumerator nextObject]).to(equal(@2)); + } + expect(@(firstSequenceDeallocd)).toEventually(beTruthy()); + + @autoreleasepool { + expect([enumerator nextObject]).to(equal(@3)); + } + expect(@(secondSequenceDeallocd)).toEventually(beTruthy()); + + @autoreleasepool { + expect([enumerator nextObject]).to(beNil()); + } + expect(@(thirdSequenceDeallocd)).toEventually(beTruthy()); + }); }); qck_it(@"shouldn't overflow the stack when deallocated on a background queue", ^{ - NSUInteger length = 10000; - NSMutableArray *values = [NSMutableArray arrayWithCapacity:length]; - for (NSUInteger i = 0; i < length; ++i) { - [values addObject:@(i)]; - } - - __block atomic_bool finished = NO; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - @autoreleasepool { - (void)[[values.rac_sequence map:^(id value) { - return value; - }] array]; - } - - finished = YES; - }); - - expect(@(finished)).toEventually(beTruthy()); + NSUInteger length = 10000; + NSMutableArray *values = [NSMutableArray arrayWithCapacity:length]; + for (NSUInteger i = 0; i < length; ++i) { + [values addObject:@(i)]; + } + + __block atomic_bool finished = NO; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + @autoreleasepool { + (void)[[values.rac_sequence map:^(id value) { + return value; + }] array]; + } + + finished = YES; + }); + + expect(@(finished)).toEventually(beTruthy()); }); qck_describe(@"-foldLeftWithStart:reduce:", ^{ - qck_it(@"should reduce with start first", ^{ - RACSequence *sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; - NSNumber *result = [sequence foldLeftWithStart:@3 reduce:^(NSNumber *first, NSNumber *rest) { - return first; - }]; - expect(result).to(equal(@3)); - }); - - qck_it(@"should be left associative", ^{ - RACSequence *sequence = [[[RACSequence return:@1] concat:[RACSequence return:@2]] concat:[RACSequence return:@3]]; - NSNumber *result = [sequence foldLeftWithStart:@0 reduce:^(NSNumber *first, NSNumber *rest) { - int difference = first.intValue - rest.intValue; - return @(difference); - }]; - expect(result).to(equal(@-6)); - }); + qck_it(@"should reduce with start first", ^{ + RACSequence *sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; + NSNumber *result = [sequence foldLeftWithStart:@3 reduce:^(NSNumber *first, NSNumber *rest) { + return first; + }]; + expect(result).to(equal(@3)); + }); + + qck_it(@"should be left associative", ^{ + RACSequence *sequence = [[[RACSequence return:@1] concat:[RACSequence return:@2]] concat:[RACSequence return:@3]]; + NSNumber *result = [sequence foldLeftWithStart:@0 reduce:^(NSNumber *first, NSNumber *rest) { + int difference = first.intValue - rest.intValue; + return @(difference); + }]; + expect(result).to(equal(@-6)); + }); }); qck_describe(@"-foldRightWithStart:reduce:", ^{ - qck_it(@"should be lazy", ^{ - __block BOOL headInvoked = NO; - __block BOOL tailInvoked = NO; - RACSequence *sequence = [RACSequence sequenceWithHeadBlock:^{ - headInvoked = YES; - return @0; - } tailBlock:^{ - tailInvoked = YES; - return [RACSequence return:@1]; - }]; - - NSNumber *result = [sequence foldRightWithStart:@2 reduce:^(NSNumber *first, RACSequence *rest) { - return first; - }]; - - expect(result).to(equal(@0)); - expect(@(headInvoked)).to(beTruthy()); - expect(@(tailInvoked)).to(beFalsy()); - }); - - qck_it(@"should reduce with start last", ^{ - RACSequence *sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; - NSNumber *result = [sequence foldRightWithStart:@3 reduce:^(NSNumber *first, RACSequence *rest) { - return rest.head; - }]; - expect(result).to(equal(@3)); - }); - - qck_it(@"should be right associative", ^{ - RACSequence *sequence = [[[RACSequence return:@1] concat:[RACSequence return:@2]] concat:[RACSequence return:@3]]; - NSNumber *result = [sequence foldRightWithStart:@0 reduce:^(NSNumber *first, RACSequence *rest) { - int difference = first.intValue - [rest.head intValue]; - return @(difference); - }]; - expect(result).to(equal(@2)); - }); + qck_it(@"should be lazy", ^{ + __block BOOL headInvoked = NO; + __block BOOL tailInvoked = NO; + RACSequence *sequence = [RACSequence sequenceWithHeadBlock:^{ + headInvoked = YES; + return @0; + } tailBlock:^{ + tailInvoked = YES; + return [RACSequence return:@1]; + }]; + + NSNumber *result = [sequence foldRightWithStart:@2 reduce:^(NSNumber *first, RACSequence *rest) { + return first; + }]; + + expect(result).to(equal(@0)); + expect(@(headInvoked)).to(beTruthy()); + expect(@(tailInvoked)).to(beFalsy()); + }); + + qck_it(@"should reduce with start last", ^{ + RACSequence *sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; + NSNumber *result = [sequence foldRightWithStart:@3 reduce:^(NSNumber *first, RACSequence *rest) { + return rest.head; + }]; + expect(result).to(equal(@3)); + }); + + qck_it(@"should be right associative", ^{ + RACSequence *sequence = [[[RACSequence return:@1] concat:[RACSequence return:@2]] concat:[RACSequence return:@3]]; + NSNumber *result = [sequence foldRightWithStart:@0 reduce:^(NSNumber *first, RACSequence *rest) { + int difference = first.intValue - [rest.head intValue]; + return @(difference); + }]; + expect(result).to(equal(@2)); + }); }); qck_describe(@"-any", ^{ - __block RACSequence *sequence; - qck_beforeEach(^{ - sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; - }); - - qck_it(@"should return true when at least one exists", ^{ - BOOL result = [sequence any:^ BOOL (NSNumber *value) { - return value.integerValue > 0; - }]; - expect(@(result)).to(beTruthy()); - }); - - qck_it(@"should return false when no such thing exists", ^{ - BOOL result = [sequence any:^ BOOL (NSNumber *value) { - return value.integerValue == 3; - }]; - expect(@(result)).to(beFalsy()); - }); + __block RACSequence *sequence; + qck_beforeEach(^{ + sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; + }); + + qck_it(@"should return true when at least one exists", ^{ + BOOL result = [sequence any:^ BOOL (NSNumber *value) { + return value.integerValue > 0; + }]; + expect(@(result)).to(beTruthy()); + }); + + qck_it(@"should return false when no such thing exists", ^{ + BOOL result = [sequence any:^ BOOL (NSNumber *value) { + return value.integerValue == 3; + }]; + expect(@(result)).to(beFalsy()); + }); }); qck_describe(@"-all", ^{ - __block RACSequence *sequence; - qck_beforeEach(^{ - sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; - }); - - qck_it(@"should return true when all values pass", ^{ - BOOL result = [sequence all:^ BOOL (NSNumber *value) { - return value.integerValue >= 0; - }]; - expect(@(result)).to(beTruthy()); - }); - - qck_it(@"should return false when at least one value fails", ^{ - BOOL result = [sequence all:^ BOOL (NSNumber *value) { - return value.integerValue < 2; - }]; - expect(@(result)).to(beFalsy()); - }); + __block RACSequence *sequence; + qck_beforeEach(^{ + sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; + }); + + qck_it(@"should return true when all values pass", ^{ + BOOL result = [sequence all:^ BOOL (NSNumber *value) { + return value.integerValue >= 0; + }]; + expect(@(result)).to(beTruthy()); + }); + + qck_it(@"should return false when at least one value fails", ^{ + BOOL result = [sequence all:^ BOOL (NSNumber *value) { + return value.integerValue < 2; + }]; + expect(@(result)).to(beFalsy()); + }); }); qck_describe(@"-objectPassingTest:", ^{ - __block RACSequence *sequence; - qck_beforeEach(^{ - sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; - }); - - qck_it(@"should return leftmost object that passes the test", ^{ - NSNumber *result = [sequence objectPassingTest:^ BOOL (NSNumber *value) { - return value.intValue > 0; - }]; - expect(result).to(equal(@1)); - }); - - qck_it(@"should return nil if no objects pass the test", ^{ - NSNumber *result = [sequence objectPassingTest:^ BOOL (NSNumber *value) { - return value.intValue < 0; - }]; - expect(result).to(beNil()); - }); + __block RACSequence *sequence; + qck_beforeEach(^{ + sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; + }); + + qck_it(@"should return leftmost object that passes the test", ^{ + NSNumber *result = [sequence objectPassingTest:^ BOOL (NSNumber *value) { + return value.intValue > 0; + }]; + expect(result).to(equal(@1)); + }); + + qck_it(@"should return nil if no objects pass the test", ^{ + NSNumber *result = [sequence objectPassingTest:^ BOOL (NSNumber *value) { + return value.intValue < 0; + }]; + expect(result).to(beNil()); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACSerialDisposableSpec.m b/ReactiveObjCTests/RACSerialDisposableSpec.m index 10e923744..0b892a4e2 100644 --- a/ReactiveObjCTests/RACSerialDisposableSpec.m +++ b/ReactiveObjCTests/RACSerialDisposableSpec.m @@ -16,149 +16,149 @@ QuickSpecBegin(RACSerialDisposableSpec) qck_it(@"should initialize with -init", ^{ - RACSerialDisposable *serial = [[RACSerialDisposable alloc] init]; - expect(serial).notTo(beNil()); - expect(serial.disposable).to(beNil()); + RACSerialDisposable *serial = [[RACSerialDisposable alloc] init]; + expect(serial).notTo(beNil()); + expect(serial.disposable).to(beNil()); }); qck_it(@"should initialize an inner disposable with -initWithBlock:", ^{ - __block BOOL disposed = NO; - RACSerialDisposable *serial = [RACSerialDisposable disposableWithBlock:^{ - disposed = YES; - }]; + __block BOOL disposed = NO; + RACSerialDisposable *serial = [RACSerialDisposable disposableWithBlock:^{ + disposed = YES; + }]; - expect(serial).notTo(beNil()); - expect(serial.disposable).notTo(beNil()); + expect(serial).notTo(beNil()); + expect(serial.disposable).notTo(beNil()); - [serial.disposable dispose]; - expect(@(serial.disposed)).to(beFalsy()); - expect(@(disposed)).to(beTruthy()); + [serial.disposable dispose]; + expect(@(serial.disposed)).to(beFalsy()); + expect(@(disposed)).to(beTruthy()); }); qck_it(@"should initialize with a disposable", ^{ - RACDisposable *inner = [[RACDisposable alloc] init]; - RACSerialDisposable *serial = [RACSerialDisposable serialDisposableWithDisposable:inner]; - expect(serial).notTo(beNil()); - expect(serial.disposable).to(equal(inner)); + RACDisposable *inner = [[RACDisposable alloc] init]; + RACSerialDisposable *serial = [RACSerialDisposable serialDisposableWithDisposable:inner]; + expect(serial).notTo(beNil()); + expect(serial.disposable).to(equal(inner)); }); qck_it(@"should dispose of the inner disposable", ^{ - __block BOOL disposed = NO; - RACDisposable *inner = [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; - - RACSerialDisposable *serial = [RACSerialDisposable serialDisposableWithDisposable:inner]; - expect(@(serial.disposed)).to(beFalsy()); - expect(@(disposed)).to(beFalsy()); - - [serial dispose]; - expect(@(serial.disposed)).to(beTruthy()); - expect(serial.disposable).to(beNil()); - expect(@(disposed)).to(beTruthy()); + __block BOOL disposed = NO; + RACDisposable *inner = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + + RACSerialDisposable *serial = [RACSerialDisposable serialDisposableWithDisposable:inner]; + expect(@(serial.disposed)).to(beFalsy()); + expect(@(disposed)).to(beFalsy()); + + [serial dispose]; + expect(@(serial.disposed)).to(beTruthy()); + expect(serial.disposable).to(beNil()); + expect(@(disposed)).to(beTruthy()); }); qck_it(@"should dispose of a new inner disposable if it's already been disposed", ^{ - __block BOOL disposed = NO; - RACDisposable *inner = [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; + __block BOOL disposed = NO; + RACDisposable *inner = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; - RACSerialDisposable *serial = [[RACSerialDisposable alloc] init]; - expect(@(serial.disposed)).to(beFalsy()); + RACSerialDisposable *serial = [[RACSerialDisposable alloc] init]; + expect(@(serial.disposed)).to(beFalsy()); - [serial dispose]; - expect(@(serial.disposed)).to(beTruthy()); - expect(@(disposed)).to(beFalsy()); + [serial dispose]; + expect(@(serial.disposed)).to(beTruthy()); + expect(@(disposed)).to(beFalsy()); - serial.disposable = inner; - expect(@(disposed)).to(beTruthy()); - expect(serial.disposable).to(beNil()); + serial.disposable = inner; + expect(@(disposed)).to(beTruthy()); + expect(serial.disposable).to(beNil()); }); qck_it(@"should allow the inner disposable to be set to nil", ^{ - __block BOOL disposed = NO; - RACDisposable *inner = [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; + __block BOOL disposed = NO; + RACDisposable *inner = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; - RACSerialDisposable *serial = [RACSerialDisposable serialDisposableWithDisposable:inner]; - expect(@(disposed)).to(beFalsy()); + RACSerialDisposable *serial = [RACSerialDisposable serialDisposableWithDisposable:inner]; + expect(@(disposed)).to(beFalsy()); - serial.disposable = nil; - expect(serial.disposable).to(beNil()); + serial.disposable = nil; + expect(serial.disposable).to(beNil()); - serial.disposable = inner; - expect(serial.disposable).to(equal(inner)); + serial.disposable = inner; + expect(serial.disposable).to(equal(inner)); - [serial dispose]; - expect(@(disposed)).to(beTruthy()); - expect(serial.disposable).to(beNil()); + [serial dispose]; + expect(@(disposed)).to(beTruthy()); + expect(serial.disposable).to(beNil()); }); qck_it(@"should swap inner disposables", ^{ - __block BOOL firstDisposed = NO; - RACDisposable *first = [RACDisposable disposableWithBlock:^{ - firstDisposed = YES; - }]; + __block BOOL firstDisposed = NO; + RACDisposable *first = [RACDisposable disposableWithBlock:^{ + firstDisposed = YES; + }]; - __block BOOL secondDisposed = NO; - RACDisposable *second = [RACDisposable disposableWithBlock:^{ - secondDisposed = YES; - }]; + __block BOOL secondDisposed = NO; + RACDisposable *second = [RACDisposable disposableWithBlock:^{ + secondDisposed = YES; + }]; - RACSerialDisposable *serial = [RACSerialDisposable serialDisposableWithDisposable:first]; - expect([serial swapInDisposable:second]).to(equal(first)); + RACSerialDisposable *serial = [RACSerialDisposable serialDisposableWithDisposable:first]; + expect([serial swapInDisposable:second]).to(equal(first)); - expect(@(serial.disposed)).to(beFalsy()); - expect(@(firstDisposed)).to(beFalsy()); - expect(@(secondDisposed)).to(beFalsy()); + expect(@(serial.disposed)).to(beFalsy()); + expect(@(firstDisposed)).to(beFalsy()); + expect(@(secondDisposed)).to(beFalsy()); - [serial dispose]; - expect(@(serial.disposed)).to(beTruthy()); - expect(serial.disposable).to(beNil()); + [serial dispose]; + expect(@(serial.disposed)).to(beTruthy()); + expect(serial.disposable).to(beNil()); - expect(@(firstDisposed)).to(beFalsy()); - expect(@(secondDisposed)).to(beTruthy()); + expect(@(firstDisposed)).to(beFalsy()); + expect(@(secondDisposed)).to(beTruthy()); }); qck_it(@"should release the inner disposable upon deallocation", ^{ - __weak RACDisposable *weakInnerDisposable; - __weak RACSerialDisposable *weakSerialDisposable; + __weak RACDisposable *weakInnerDisposable; + __weak RACSerialDisposable *weakSerialDisposable; - @autoreleasepool { - RACDisposable *innerDisposable __attribute__((objc_precise_lifetime)) = [[RACDisposable alloc] init]; - weakInnerDisposable = innerDisposable; + @autoreleasepool { + RACDisposable *innerDisposable __attribute__((objc_precise_lifetime)) = [[RACDisposable alloc] init]; + weakInnerDisposable = innerDisposable; - RACSerialDisposable *serialDisposable __attribute__((objc_precise_lifetime)) = [[RACSerialDisposable alloc] init]; - serialDisposable.disposable = innerDisposable; - weakSerialDisposable = serialDisposable; - } + RACSerialDisposable *serialDisposable __attribute__((objc_precise_lifetime)) = [[RACSerialDisposable alloc] init]; + serialDisposable.disposable = innerDisposable; + weakSerialDisposable = serialDisposable; + } - expect(weakSerialDisposable).to(beNil()); - expect(weakInnerDisposable).to(beNil()); + expect(weakSerialDisposable).to(beNil()); + expect(weakInnerDisposable).to(beNil()); }); qck_it(@"should not crash when racing between swapInDisposable and disposable", ^{ - __block atomic_bool stop = NO; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long long)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - stop = YES; - }); - - RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - while (!stop) { - [serialDisposable swapInDisposable:[RACDisposable disposableWithBlock:^{}]]; - } - }); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - while (!stop) { - (void)[serialDisposable disposable]; - } - }); - - expect(@(stop)).toEventually(beTruthy()); + __block atomic_bool stop = NO; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long long)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + stop = YES; + }); + + RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + while (!stop) { + [serialDisposable swapInDisposable:[RACDisposable disposableWithBlock:^{}]]; + } + }); + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + while (!stop) { + (void)[serialDisposable disposable]; + } + }); + + expect(@(stop)).toEventually(beTruthy()); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACStreamExamples.m b/ReactiveObjCTests/RACStreamExamples.m index cd99c1c4d..43fbbce9d 100644 --- a/ReactiveObjCTests/RACStreamExamples.m +++ b/ReactiveObjCTests/RACStreamExamples.m @@ -23,648 +23,648 @@ QuickConfigurationBegin(RACStreamExampleGroups) + (void)configure:(Configuration *)configuration { - sharedExamples(RACStreamExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block Class streamClass; - __block void (^verifyValues)(RACStream *, NSArray *); - __block RACStream *infiniteStream; - - __block RACStream *(^streamWithValues)(NSArray *); - - qck_beforeEach(^{ - streamClass = exampleContext()[RACStreamExamplesClass]; - verifyValues = exampleContext()[RACStreamExamplesVerifyValuesBlock]; - infiniteStream = exampleContext()[RACStreamExamplesInfiniteStream]; - streamWithValues = [^(NSArray *values) { - RACStream *stream = [streamClass empty]; - - for (id value in values) { - stream = [stream concat:[streamClass return:value]]; - } - - return stream; - } copy]; - }); - - qck_it(@"should return an empty stream", ^{ - RACStream *stream = [streamClass empty]; - verifyValues(stream, @[]); - }); - - qck_it(@"should lift a value into a stream", ^{ - RACStream *stream = [streamClass return:RACUnit.defaultUnit]; - verifyValues(stream, @[ RACUnit.defaultUnit ]); - }); - - qck_describe(@"-concat:", ^{ - qck_it(@"should concatenate two streams", ^{ - RACStream *stream = [[streamClass return:@0] concat:[streamClass return:@1]]; - verifyValues(stream, @[ @0, @1 ]); - }); - - qck_it(@"should concatenate three streams", ^{ - RACStream *stream = [[[streamClass return:@0] concat:[streamClass return:@1]] concat:[streamClass return:@2]]; - verifyValues(stream, @[ @0, @1, @2 ]); - }); - - qck_it(@"should concatenate around an empty stream", ^{ - RACStream *stream = [[[streamClass return:@0] concat:[streamClass empty]] concat:[streamClass return:@2]]; - verifyValues(stream, @[ @0, @2 ]); - }); - }); - - qck_it(@"should flatten", ^{ - RACStream *stream = [[streamClass return:[streamClass return:RACUnit.defaultUnit]] flatten]; - verifyValues(stream, @[ RACUnit.defaultUnit ]); - }); - - qck_describe(@"-bind:", ^{ - qck_it(@"should return the result of binding a single value", ^{ - RACStream *stream = [[streamClass return:@0] bind:^{ - return ^(NSNumber *value, BOOL *stop) { - NSNumber *newValue = @(value.integerValue + 1); - return [streamClass return:newValue]; - }; - }]; - - verifyValues(stream, @[ @1 ]); - }); - - qck_it(@"should concatenate the result of binding multiple values", ^{ - RACStream *baseStream = streamWithValues(@[ @0, @1 ]); - RACStream *stream = [baseStream bind:^{ - return ^(NSNumber *value, BOOL *stop) { - NSNumber *newValue = @(value.integerValue + 1); - return [streamClass return:newValue]; - }; - }]; - - verifyValues(stream, @[ @1, @2 ]); - }); - - qck_it(@"should concatenate with an empty result from binding a value", ^{ - RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); - RACStream *stream = [baseStream bind:^{ - return ^(NSNumber *value, BOOL *stop) { - if (value.integerValue == 1) return [streamClass empty]; - - NSNumber *newValue = @(value.integerValue + 1); - return [streamClass return:newValue]; - }; - }]; - - verifyValues(stream, @[ @1, @3 ]); - }); - - qck_it(@"should terminate immediately when returning nil", ^{ - RACStream *stream = [infiniteStream bind:^{ - return ^ id (id _, BOOL *stop) { - return nil; - }; - }]; - - verifyValues(stream, @[]); - }); - - qck_it(@"should terminate after one value when setting 'stop'", ^{ - RACStream *stream = [infiniteStream bind:^{ - return ^ id (id value, BOOL *stop) { - *stop = YES; - return [streamClass return:value]; - }; - }]; - - verifyValues(stream, @[ RACUnit.defaultUnit ]); - }); - - qck_it(@"should terminate immediately when returning nil and setting 'stop'", ^{ - RACStream *stream = [infiniteStream bind:^{ - return ^ id (id _, BOOL *stop) { - *stop = YES; - return nil; - }; - }]; - - verifyValues(stream, @[]); - }); - - qck_it(@"should be restartable even with block state", ^{ - NSArray *values = @[ @0, @1, @2 ]; - RACStream *baseStream = streamWithValues(values); - - RACStream *countingStream = [baseStream bind:^{ - __block NSUInteger counter = 0; - - return ^(id x, BOOL *stop) { - return [streamClass return:@(counter++)]; - }; - }]; - - verifyValues(countingStream, @[ @0, @1, @2 ]); - verifyValues(countingStream, @[ @0, @1, @2 ]); - }); - - qck_it(@"should be interleavable even with block state", ^{ - NSArray *values = @[ @0, @1, @2 ]; - RACStream *baseStream = streamWithValues(values); - - RACStream *countingStream = [baseStream bind:^{ - __block NSUInteger counter = 0; - - return ^(id x, BOOL *stop) { - return [streamClass return:@(counter++)]; - }; - }]; - - // Just so +zip:reduce: thinks this is a unique stream. - RACStream *anotherStream = [[streamClass empty] concat:countingStream]; - - RACStream *zipped = [streamClass zip:@[ countingStream, anotherStream ] reduce:^(NSNumber *v1, NSNumber *v2) { - return @(v1.integerValue + v2.integerValue); - }]; - - verifyValues(zipped, @[ @0, @2, @4 ]); - }); - }); - - qck_describe(@"-flattenMap:", ^{ - qck_it(@"should return a single mapped result", ^{ - RACStream *stream = [[streamClass return:@0] flattenMap:^(NSNumber *value) { - NSNumber *newValue = @(value.integerValue + 1); - return [streamClass return:newValue]; - }]; - - verifyValues(stream, @[ @1 ]); - }); - - qck_it(@"should concatenate the results of mapping multiple values", ^{ - RACStream *baseStream = streamWithValues(@[ @0, @1 ]); - RACStream *stream = [baseStream flattenMap:^(NSNumber *value) { - NSNumber *newValue = @(value.integerValue + 1); - return [streamClass return:newValue]; - }]; - - verifyValues(stream, @[ @1, @2 ]); - }); - - qck_it(@"should concatenate with an empty result from mapping a value", ^{ - RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); - RACStream *stream = [baseStream flattenMap:^(NSNumber *value) { - if (value.integerValue == 1) return [streamClass empty]; - - NSNumber *newValue = @(value.integerValue + 1); - return [streamClass return:newValue]; - }]; - - verifyValues(stream, @[ @1, @3 ]); - }); - - qck_it(@"should treat nil streams like empty streams", ^{ - RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); - RACStream *stream = [baseStream flattenMap:^ RACStream * (NSNumber *value) { - if (value.integerValue == 1) return nil; - - NSNumber *newValue = @(value.integerValue + 1); - return [streamClass return:newValue]; - }]; - - verifyValues(stream, @[ @1, @3 ]); - }); - }); - - qck_it(@"should map", ^{ - RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); - RACStream *stream = [baseStream map:^(NSNumber *value) { - return @(value.integerValue + 1); - }]; - - verifyValues(stream, @[ @1, @2, @3 ]); - }); - - qck_it(@"should map and replace", ^{ - RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); - RACStream *stream = [baseStream mapReplace:RACUnit.defaultUnit]; - - verifyValues(stream, @[ RACUnit.defaultUnit, RACUnit.defaultUnit, RACUnit.defaultUnit ]); - }); - - qck_it(@"should filter", ^{ - RACStream *baseStream = streamWithValues(@[ @0, @1, @2, @3, @4, @5, @6 ]); - RACStream *stream = [baseStream filter:^ BOOL (NSNumber *value) { - return value.integerValue % 2 == 0; - }]; - - verifyValues(stream, @[ @0, @2, @4, @6 ]); - }); - - qck_describe(@"-ignore:", ^{ - qck_it(@"should ignore a value", ^{ - RACStream *baseStream = streamWithValues(@[ @0, @1, @2, @3, @4, @5, @6 ]); - RACStream *stream = [baseStream ignore:@1]; - - verifyValues(stream, @[ @0, @2, @3, @4, @5, @6 ]); - }); - - qck_it(@"should ignore based on object equality", ^{ - RACStream *baseStream = streamWithValues(@[ @"0", @"1", @"2", @"3", @"4", @"5", @"6" ]); - - NSMutableString *valueToIgnore = [[NSMutableString alloc] init]; - [valueToIgnore appendString:@"1"]; - RACStream *stream = [baseStream ignore:valueToIgnore]; - - verifyValues(stream, @[ @"0", @"2", @"3", @"4", @"5", @"6" ]); - }); - }); - - qck_it(@"should start with a value", ^{ - RACStream *stream = [[streamClass return:@1] startWith:@0]; - verifyValues(stream, @[ @0, @1 ]); - }); - - qck_describe(@"-skip:", ^{ - __block NSArray *values; - __block RACStream *stream; - - qck_beforeEach(^{ - values = @[ @0, @1, @2 ]; - stream = streamWithValues(values); - }); - - qck_it(@"should skip any valid number of values", ^{ - for (NSUInteger i = 0; i < values.count; i++) { - verifyValues([stream skip:i], [values subarrayWithRange:NSMakeRange(i, values.count - i)]); - } - }); - - qck_it(@"should return an empty stream when skipping too many values", ^{ - verifyValues([stream skip:4], @[]); - }); - }); - - qck_describe(@"-take:", ^{ - qck_describe(@"with three values", ^{ - __block NSArray *values; - __block RACStream *stream; - - qck_beforeEach(^{ - values = @[ @0, @1, @2 ]; - stream = streamWithValues(values); - }); - - qck_it(@"should take any valid number of values", ^{ - for (NSUInteger i = 0; i < values.count; i++) { - verifyValues([stream take:i], [values subarrayWithRange:NSMakeRange(0, i)]); - } - }); - - qck_it(@"should return the same stream when taking too many values", ^{ - verifyValues([stream take:4], values); - }); - }); - - qck_it(@"should take and terminate from an infinite stream", ^{ - verifyValues([infiniteStream take:0], @[]); - verifyValues([infiniteStream take:1], @[ RACUnit.defaultUnit ]); - verifyValues([infiniteStream take:2], @[ RACUnit.defaultUnit, RACUnit.defaultUnit ]); - }); - - qck_it(@"should take and terminate from a single-item stream", ^{ - NSArray *values = @[ RACUnit.defaultUnit ]; - RACStream *stream = streamWithValues(values); - verifyValues([stream take:1], values); - }); - }); - - qck_describe(@"zip stream creation methods", ^{ - __block NSArray *valuesOne; - - __block RACStream *streamOne; - __block RACStream *streamTwo; - __block RACStream *streamThree; - __block NSArray *threeStreams; - - __block NSArray *oneStreamTuples; - __block NSArray *twoStreamTuples; - __block NSArray *threeStreamTuples; - - qck_beforeEach(^{ - valuesOne = @[ @"Ada", @"Bob", @"Dea" ]; - NSArray *valuesTwo = @[ @"eats", @"cooks", @"jumps" ]; - NSArray *valuesThree = @[ @"fish", @"bear", @"rock" ]; - - streamOne = streamWithValues(valuesOne); - streamTwo = streamWithValues(valuesTwo); - streamThree = streamWithValues(valuesThree); - threeStreams = @[ streamOne, streamTwo, streamThree ]; - - oneStreamTuples = @[ - RACTuplePack(valuesOne[0]), - RACTuplePack(valuesOne[1]), - RACTuplePack(valuesOne[2]), - ]; - - twoStreamTuples = @[ - RACTuplePack(valuesOne[0], valuesTwo[0]), - RACTuplePack(valuesOne[1], valuesTwo[1]), - RACTuplePack(valuesOne[2], valuesTwo[2]), - ]; - - threeStreamTuples = @[ - RACTuplePack(valuesOne[0], valuesTwo[0], valuesThree[0]), - RACTuplePack(valuesOne[1], valuesTwo[1], valuesThree[1]), - RACTuplePack(valuesOne[2], valuesTwo[2], valuesThree[2]), - ]; - }); - - qck_describe(@"-zipWith:", ^{ - qck_it(@"should make a stream of tuples", ^{ - RACStream *stream = [streamOne zipWith:streamTwo]; - verifyValues(stream, twoStreamTuples); - }); - - qck_it(@"should truncate streams", ^{ - RACStream *shortStream = streamWithValues(@[ @"now", @"later" ]); - RACStream *stream = [streamOne zipWith:shortStream]; - - verifyValues(stream, @[ - RACTuplePack(valuesOne[0], @"now"), - RACTuplePack(valuesOne[1], @"later") - ]); - }); - - qck_it(@"should work on infinite streams", ^{ - RACStream *stream = [streamOne zipWith:infiniteStream]; - verifyValues(stream, @[ - RACTuplePack(valuesOne[0], RACUnit.defaultUnit), - RACTuplePack(valuesOne[1], RACUnit.defaultUnit), - RACTuplePack(valuesOne[2], RACUnit.defaultUnit) - ]); - }); - - qck_it(@"should handle multiples of the same stream", ^{ - RACStream *stream = [streamOne zipWith:streamOne]; - verifyValues(stream, @[ - RACTuplePack(valuesOne[0], valuesOne[0]), - RACTuplePack(valuesOne[1], valuesOne[1]), - RACTuplePack(valuesOne[2], valuesOne[2]), - ]); - }); - }); - - qck_describe(@"+zip:reduce:", ^{ - qck_it(@"should reduce values", ^{ - RACStream *stream = [streamClass zip:threeStreams reduce:^ NSString * (id x, id y, id z) { - return [NSString stringWithFormat:@"%@ %@ %@", x, y, z]; - }]; - verifyValues(stream, @[ @"Ada eats fish", @"Bob cooks bear", @"Dea jumps rock" ]); - }); - - qck_it(@"should truncate streams", ^{ - RACStream *shortStream = streamWithValues(@[ @"now", @"later" ]); - NSArray *streams = [threeStreams arrayByAddingObject:shortStream]; - RACStream *stream = [streamClass zip:streams reduce:^ NSString * (id w, id x, id y, id z) { - return [NSString stringWithFormat:@"%@ %@ %@ %@", w, x, y, z]; - }]; - verifyValues(stream, @[ @"Ada eats fish now", @"Bob cooks bear later" ]); - }); - - qck_it(@"should work on infinite streams", ^{ - NSArray *streams = [threeStreams arrayByAddingObject:infiniteStream]; - RACStream *stream = [streamClass zip:streams reduce:^ NSString * (id w, id x, id y, id z) { - return [NSString stringWithFormat:@"%@ %@ %@", w, x, y]; - }]; - verifyValues(stream, @[ @"Ada eats fish", @"Bob cooks bear", @"Dea jumps rock" ]); - }); - - qck_it(@"should handle multiples of the same stream", ^{ - NSArray *streams = @[ streamOne, streamOne, streamTwo, streamThree, streamTwo, streamThree ]; - RACStream *stream = [streamClass zip:streams reduce:^ NSString * (id x1, id x2, id y1, id z1, id y2, id z2) { - return [NSString stringWithFormat:@"%@ %@ %@ %@ %@ %@", x1, x2, y1, z1, y2, z2]; - }]; - verifyValues(stream, @[ @"Ada Ada eats fish eats fish", @"Bob Bob cooks bear cooks bear", @"Dea Dea jumps rock jumps rock" ]); - }); - }); - - qck_describe(@"+zip:", ^{ - qck_it(@"should make a stream of tuples out of single value", ^{ - RACStream *stream = [streamClass zip:@[ streamOne ]]; - verifyValues(stream, oneStreamTuples); - }); - - qck_it(@"should make a stream of tuples out of an array of streams", ^{ - RACStream *stream = [streamClass zip:threeStreams]; - verifyValues(stream, threeStreamTuples); - }); - - qck_it(@"should make an empty stream if given an empty array", ^{ - RACStream *stream = [streamClass zip:@[]]; - verifyValues(stream, @[]); - }); - - qck_it(@"should make a stream of tuples out of an enumerator of streams", ^{ - RACStream *stream = [streamClass zip:threeStreams.objectEnumerator]; - verifyValues(stream, threeStreamTuples); - }); - - qck_it(@"should make an empty stream if given an empty enumerator", ^{ - RACStream *stream = [streamClass zip:@[].objectEnumerator]; - verifyValues(stream, @[]); - }); - }); - }); - - qck_describe(@"+concat:", ^{ - __block NSArray *streams = nil; - __block NSArray *result = nil; - - qck_beforeEach(^{ - RACStream *a = [streamClass return:@0]; - RACStream *b = [streamClass empty]; - RACStream *c = streamWithValues(@[ @1, @2, @3 ]); - RACStream *d = [streamClass return:@4]; - RACStream *e = [streamClass return:@5]; - RACStream *f = [streamClass empty]; - RACStream *g = [streamClass empty]; - RACStream *h = streamWithValues(@[ @6, @7 ]); - streams = @[ a, b, c, d, e, f, g, h ]; - result = @[ @0, @1, @2, @3, @4, @5, @6, @7 ]; - }); - - qck_it(@"should concatenate an array of streams", ^{ - RACStream *stream = [streamClass concat:streams]; - verifyValues(stream, result); - }); - - qck_it(@"should concatenate an enumerator of streams", ^{ - RACStream *stream = [streamClass concat:streams.objectEnumerator]; - verifyValues(stream, result); - }); - }); - - qck_describe(@"scanning", ^{ - NSArray *values = @[ @1, @2, @3, @4 ]; - - __block RACStream *stream; - - qck_beforeEach(^{ - stream = streamWithValues(values); - }); - - qck_it(@"should scan", ^{ - RACStream *scanned = [stream scanWithStart:@0 reduce:^(NSNumber *running, NSNumber *next) { - return @(running.integerValue + next.integerValue); - }]; - - verifyValues(scanned, @[ @1, @3, @6, @10 ]); - }); - - qck_it(@"should scan with index", ^{ - RACStream *scanned = [stream scanWithStart:@0 reduceWithIndex:^(NSNumber *running, NSNumber *next, NSUInteger index) { - return @(running.integerValue + next.integerValue + (NSInteger)index); - }]; - - verifyValues(scanned, @[ @1, @4, @9, @16 ]); - }); - }); - - qck_describe(@"taking with a predicate", ^{ - NSArray *values = @[ @0, @1, @2, @3, @0, @2, @4 ]; - - __block RACStream *stream; - - qck_beforeEach(^{ - stream = streamWithValues(values); - }); - - qck_it(@"should take until a predicate is true", ^{ - RACStream *taken = [stream takeUntilBlock:^ BOOL (NSNumber *x) { - return x.integerValue >= 3; - }]; - - verifyValues(taken, @[ @0, @1, @2 ]); - }); - - qck_it(@"should take while a predicate is true", ^{ - RACStream *taken = [stream takeWhileBlock:^ BOOL (NSNumber *x) { - return x.integerValue <= 1; - }]; - - verifyValues(taken, @[ @0, @1 ]); - }); - - qck_it(@"should take a full stream", ^{ - RACStream *taken = [stream takeWhileBlock:^ BOOL (NSNumber *x) { - return x.integerValue <= 10; - }]; - - verifyValues(taken, values); - }); - - qck_it(@"should return an empty stream", ^{ - RACStream *taken = [stream takeWhileBlock:^ BOOL (NSNumber *x) { - return x.integerValue < 0; - }]; - - verifyValues(taken, @[]); - }); - - qck_it(@"should terminate an infinite stream", ^{ - RACStream *infiniteCounter = [infiniteStream scanWithStart:@0 reduce:^(NSNumber *running, id _) { - return @(running.unsignedIntegerValue + 1); - }]; - - RACStream *taken = [infiniteCounter takeWhileBlock:^ BOOL (NSNumber *x) { - return x.integerValue <= 5; - }]; - - verifyValues(taken, @[ @1, @2, @3, @4, @5 ]); - }); - }); - - qck_describe(@"skipping with a predicate", ^{ - NSArray *values = @[ @0, @1, @2, @3, @0, @2, @4 ]; - - __block RACStream *stream; - - qck_beforeEach(^{ - stream = streamWithValues(values); - }); - - qck_it(@"should skip until a predicate is true", ^{ - RACStream *taken = [stream skipUntilBlock:^ BOOL (NSNumber *x) { - return x.integerValue >= 3; - }]; - - verifyValues(taken, @[ @3, @0, @2, @4 ]); - }); - - qck_it(@"should skip while a predicate is true", ^{ - RACStream *taken = [stream skipWhileBlock:^ BOOL (NSNumber *x) { - return x.integerValue <= 1; - }]; - - verifyValues(taken, @[ @2, @3, @0, @2, @4 ]); - }); - - qck_it(@"should skip a full stream", ^{ - RACStream *taken = [stream skipWhileBlock:^ BOOL (NSNumber *x) { - return x.integerValue <= 10; - }]; - - verifyValues(taken, @[]); - }); - - qck_it(@"should finish skipping immediately", ^{ - RACStream *taken = [stream skipWhileBlock:^ BOOL (NSNumber *x) { - return x.integerValue < 0; - }]; - - verifyValues(taken, values); - }); - }); - - qck_describe(@"-combinePreviousWithStart:reduce:", ^{ - NSArray *values = @[ @1, @2, @3 ]; - __block RACStream *stream; - qck_beforeEach(^{ - stream = streamWithValues(values); - }); - - qck_it(@"should pass the previous next into the reduce block", ^{ - NSMutableArray *previouses = [NSMutableArray array]; - RACStream *mapped = [stream combinePreviousWithStart:nil reduce:^(id previous, id next) { - [previouses addObject:previous ?: RACTupleNil.tupleNil]; - return next; - }]; - - verifyValues(mapped, @[ @1, @2, @3 ]); - - NSArray *expected = @[ RACTupleNil.tupleNil, @1, @2 ]; - expect(previouses).to(equal(expected)); - }); - - qck_it(@"should send the combined value", ^{ - RACStream *mapped = [stream combinePreviousWithStart:@1 reduce:^(NSNumber *previous, NSNumber *next) { - return [NSString stringWithFormat:@"%lu - %lu", (unsigned long)previous.unsignedIntegerValue, (unsigned long)next.unsignedIntegerValue]; - }]; - - verifyValues(mapped, @[ @"1 - 1", @"1 - 2", @"2 - 3" ]); - }); - }); - - qck_it(@"should reduce tuples", ^{ - RACStream *stream = streamWithValues(@[ - RACTuplePack(@"foo", @"bar"), - RACTuplePack(@"buzz", @"baz"), - RACTuplePack(@"", @"_") - ]); - - RACStream *reduced = [stream reduceEach:^(NSString *a, NSString *b) { - return [a stringByAppendingString:b]; - }]; - - verifyValues(reduced, @[ @"foobar", @"buzzbaz", @"_" ]); - }); - }); + sharedExamples(RACStreamExamples, ^(QCKDSLSharedExampleContext exampleContext) { + __block Class streamClass; + __block void (^verifyValues)(RACStream *, NSArray *); + __block RACStream *infiniteStream; + + __block RACStream *(^streamWithValues)(NSArray *); + + qck_beforeEach(^{ + streamClass = exampleContext()[RACStreamExamplesClass]; + verifyValues = exampleContext()[RACStreamExamplesVerifyValuesBlock]; + infiniteStream = exampleContext()[RACStreamExamplesInfiniteStream]; + streamWithValues = [^(NSArray *values) { + RACStream *stream = [streamClass empty]; + + for (id value in values) { + stream = [stream concat:[streamClass return:value]]; + } + + return stream; + } copy]; + }); + + qck_it(@"should return an empty stream", ^{ + RACStream *stream = [streamClass empty]; + verifyValues(stream, @[]); + }); + + qck_it(@"should lift a value into a stream", ^{ + RACStream *stream = [streamClass return:RACUnit.defaultUnit]; + verifyValues(stream, @[ RACUnit.defaultUnit ]); + }); + + qck_describe(@"-concat:", ^{ + qck_it(@"should concatenate two streams", ^{ + RACStream *stream = [[streamClass return:@0] concat:[streamClass return:@1]]; + verifyValues(stream, @[ @0, @1 ]); + }); + + qck_it(@"should concatenate three streams", ^{ + RACStream *stream = [[[streamClass return:@0] concat:[streamClass return:@1]] concat:[streamClass return:@2]]; + verifyValues(stream, @[ @0, @1, @2 ]); + }); + + qck_it(@"should concatenate around an empty stream", ^{ + RACStream *stream = [[[streamClass return:@0] concat:[streamClass empty]] concat:[streamClass return:@2]]; + verifyValues(stream, @[ @0, @2 ]); + }); + }); + + qck_it(@"should flatten", ^{ + RACStream *stream = [[streamClass return:[streamClass return:RACUnit.defaultUnit]] flatten]; + verifyValues(stream, @[ RACUnit.defaultUnit ]); + }); + + qck_describe(@"-bind:", ^{ + qck_it(@"should return the result of binding a single value", ^{ + RACStream *stream = [[streamClass return:@0] bind:^{ + return ^(NSNumber *value, BOOL *stop) { + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }; + }]; + + verifyValues(stream, @[ @1 ]); + }); + + qck_it(@"should concatenate the result of binding multiple values", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1 ]); + RACStream *stream = [baseStream bind:^{ + return ^(NSNumber *value, BOOL *stop) { + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }; + }]; + + verifyValues(stream, @[ @1, @2 ]); + }); + + qck_it(@"should concatenate with an empty result from binding a value", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); + RACStream *stream = [baseStream bind:^{ + return ^(NSNumber *value, BOOL *stop) { + if (value.integerValue == 1) return [streamClass empty]; + + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }; + }]; + + verifyValues(stream, @[ @1, @3 ]); + }); + + qck_it(@"should terminate immediately when returning nil", ^{ + RACStream *stream = [infiniteStream bind:^{ + return ^ id (id _, BOOL *stop) { + return nil; + }; + }]; + + verifyValues(stream, @[]); + }); + + qck_it(@"should terminate after one value when setting 'stop'", ^{ + RACStream *stream = [infiniteStream bind:^{ + return ^ id (id value, BOOL *stop) { + *stop = YES; + return [streamClass return:value]; + }; + }]; + + verifyValues(stream, @[ RACUnit.defaultUnit ]); + }); + + qck_it(@"should terminate immediately when returning nil and setting 'stop'", ^{ + RACStream *stream = [infiniteStream bind:^{ + return ^ id (id _, BOOL *stop) { + *stop = YES; + return nil; + }; + }]; + + verifyValues(stream, @[]); + }); + + qck_it(@"should be restartable even with block state", ^{ + NSArray *values = @[ @0, @1, @2 ]; + RACStream *baseStream = streamWithValues(values); + + RACStream *countingStream = [baseStream bind:^{ + __block NSUInteger counter = 0; + + return ^(id x, BOOL *stop) { + return [streamClass return:@(counter++)]; + }; + }]; + + verifyValues(countingStream, @[ @0, @1, @2 ]); + verifyValues(countingStream, @[ @0, @1, @2 ]); + }); + + qck_it(@"should be interleavable even with block state", ^{ + NSArray *values = @[ @0, @1, @2 ]; + RACStream *baseStream = streamWithValues(values); + + RACStream *countingStream = [baseStream bind:^{ + __block NSUInteger counter = 0; + + return ^(id x, BOOL *stop) { + return [streamClass return:@(counter++)]; + }; + }]; + + // Just so +zip:reduce: thinks this is a unique stream. + RACStream *anotherStream = [[streamClass empty] concat:countingStream]; + + RACStream *zipped = [streamClass zip:@[ countingStream, anotherStream ] reduce:^(NSNumber *v1, NSNumber *v2) { + return @(v1.integerValue + v2.integerValue); + }]; + + verifyValues(zipped, @[ @0, @2, @4 ]); + }); + }); + + qck_describe(@"-flattenMap:", ^{ + qck_it(@"should return a single mapped result", ^{ + RACStream *stream = [[streamClass return:@0] flattenMap:^(NSNumber *value) { + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }]; + + verifyValues(stream, @[ @1 ]); + }); + + qck_it(@"should concatenate the results of mapping multiple values", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1 ]); + RACStream *stream = [baseStream flattenMap:^(NSNumber *value) { + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }]; + + verifyValues(stream, @[ @1, @2 ]); + }); + + qck_it(@"should concatenate with an empty result from mapping a value", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); + RACStream *stream = [baseStream flattenMap:^(NSNumber *value) { + if (value.integerValue == 1) return [streamClass empty]; + + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }]; + + verifyValues(stream, @[ @1, @3 ]); + }); + + qck_it(@"should treat nil streams like empty streams", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); + RACStream *stream = [baseStream flattenMap:^ RACStream * (NSNumber *value) { + if (value.integerValue == 1) return nil; + + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }]; + + verifyValues(stream, @[ @1, @3 ]); + }); + }); + + qck_it(@"should map", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); + RACStream *stream = [baseStream map:^(NSNumber *value) { + return @(value.integerValue + 1); + }]; + + verifyValues(stream, @[ @1, @2, @3 ]); + }); + + qck_it(@"should map and replace", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); + RACStream *stream = [baseStream mapReplace:RACUnit.defaultUnit]; + + verifyValues(stream, @[ RACUnit.defaultUnit, RACUnit.defaultUnit, RACUnit.defaultUnit ]); + }); + + qck_it(@"should filter", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2, @3, @4, @5, @6 ]); + RACStream *stream = [baseStream filter:^ BOOL (NSNumber *value) { + return value.integerValue % 2 == 0; + }]; + + verifyValues(stream, @[ @0, @2, @4, @6 ]); + }); + + qck_describe(@"-ignore:", ^{ + qck_it(@"should ignore a value", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2, @3, @4, @5, @6 ]); + RACStream *stream = [baseStream ignore:@1]; + + verifyValues(stream, @[ @0, @2, @3, @4, @5, @6 ]); + }); + + qck_it(@"should ignore based on object equality", ^{ + RACStream *baseStream = streamWithValues(@[ @"0", @"1", @"2", @"3", @"4", @"5", @"6" ]); + + NSMutableString *valueToIgnore = [[NSMutableString alloc] init]; + [valueToIgnore appendString:@"1"]; + RACStream *stream = [baseStream ignore:valueToIgnore]; + + verifyValues(stream, @[ @"0", @"2", @"3", @"4", @"5", @"6" ]); + }); + }); + + qck_it(@"should start with a value", ^{ + RACStream *stream = [[streamClass return:@1] startWith:@0]; + verifyValues(stream, @[ @0, @1 ]); + }); + + qck_describe(@"-skip:", ^{ + __block NSArray *values; + __block RACStream *stream; + + qck_beforeEach(^{ + values = @[ @0, @1, @2 ]; + stream = streamWithValues(values); + }); + + qck_it(@"should skip any valid number of values", ^{ + for (NSUInteger i = 0; i < values.count; i++) { + verifyValues([stream skip:i], [values subarrayWithRange:NSMakeRange(i, values.count - i)]); + } + }); + + qck_it(@"should return an empty stream when skipping too many values", ^{ + verifyValues([stream skip:4], @[]); + }); + }); + + qck_describe(@"-take:", ^{ + qck_describe(@"with three values", ^{ + __block NSArray *values; + __block RACStream *stream; + + qck_beforeEach(^{ + values = @[ @0, @1, @2 ]; + stream = streamWithValues(values); + }); + + qck_it(@"should take any valid number of values", ^{ + for (NSUInteger i = 0; i < values.count; i++) { + verifyValues([stream take:i], [values subarrayWithRange:NSMakeRange(0, i)]); + } + }); + + qck_it(@"should return the same stream when taking too many values", ^{ + verifyValues([stream take:4], values); + }); + }); + + qck_it(@"should take and terminate from an infinite stream", ^{ + verifyValues([infiniteStream take:0], @[]); + verifyValues([infiniteStream take:1], @[ RACUnit.defaultUnit ]); + verifyValues([infiniteStream take:2], @[ RACUnit.defaultUnit, RACUnit.defaultUnit ]); + }); + + qck_it(@"should take and terminate from a single-item stream", ^{ + NSArray *values = @[ RACUnit.defaultUnit ]; + RACStream *stream = streamWithValues(values); + verifyValues([stream take:1], values); + }); + }); + + qck_describe(@"zip stream creation methods", ^{ + __block NSArray *valuesOne; + + __block RACStream *streamOne; + __block RACStream *streamTwo; + __block RACStream *streamThree; + __block NSArray *threeStreams; + + __block NSArray *oneStreamTuples; + __block NSArray *twoStreamTuples; + __block NSArray *threeStreamTuples; + + qck_beforeEach(^{ + valuesOne = @[ @"Ada", @"Bob", @"Dea" ]; + NSArray *valuesTwo = @[ @"eats", @"cooks", @"jumps" ]; + NSArray *valuesThree = @[ @"fish", @"bear", @"rock" ]; + + streamOne = streamWithValues(valuesOne); + streamTwo = streamWithValues(valuesTwo); + streamThree = streamWithValues(valuesThree); + threeStreams = @[ streamOne, streamTwo, streamThree ]; + + oneStreamTuples = @[ + RACTuplePack(valuesOne[0]), + RACTuplePack(valuesOne[1]), + RACTuplePack(valuesOne[2]), + ]; + + twoStreamTuples = @[ + RACTuplePack(valuesOne[0], valuesTwo[0]), + RACTuplePack(valuesOne[1], valuesTwo[1]), + RACTuplePack(valuesOne[2], valuesTwo[2]), + ]; + + threeStreamTuples = @[ + RACTuplePack(valuesOne[0], valuesTwo[0], valuesThree[0]), + RACTuplePack(valuesOne[1], valuesTwo[1], valuesThree[1]), + RACTuplePack(valuesOne[2], valuesTwo[2], valuesThree[2]), + ]; + }); + + qck_describe(@"-zipWith:", ^{ + qck_it(@"should make a stream of tuples", ^{ + RACStream *stream = [streamOne zipWith:streamTwo]; + verifyValues(stream, twoStreamTuples); + }); + + qck_it(@"should truncate streams", ^{ + RACStream *shortStream = streamWithValues(@[ @"now", @"later" ]); + RACStream *stream = [streamOne zipWith:shortStream]; + + verifyValues(stream, @[ + RACTuplePack(valuesOne[0], @"now"), + RACTuplePack(valuesOne[1], @"later") + ]); + }); + + qck_it(@"should work on infinite streams", ^{ + RACStream *stream = [streamOne zipWith:infiniteStream]; + verifyValues(stream, @[ + RACTuplePack(valuesOne[0], RACUnit.defaultUnit), + RACTuplePack(valuesOne[1], RACUnit.defaultUnit), + RACTuplePack(valuesOne[2], RACUnit.defaultUnit) + ]); + }); + + qck_it(@"should handle multiples of the same stream", ^{ + RACStream *stream = [streamOne zipWith:streamOne]; + verifyValues(stream, @[ + RACTuplePack(valuesOne[0], valuesOne[0]), + RACTuplePack(valuesOne[1], valuesOne[1]), + RACTuplePack(valuesOne[2], valuesOne[2]), + ]); + }); + }); + + qck_describe(@"+zip:reduce:", ^{ + qck_it(@"should reduce values", ^{ + RACStream *stream = [streamClass zip:threeStreams reduce:^ NSString * (id x, id y, id z) { + return [NSString stringWithFormat:@"%@ %@ %@", x, y, z]; + }]; + verifyValues(stream, @[ @"Ada eats fish", @"Bob cooks bear", @"Dea jumps rock" ]); + }); + + qck_it(@"should truncate streams", ^{ + RACStream *shortStream = streamWithValues(@[ @"now", @"later" ]); + NSArray *streams = [threeStreams arrayByAddingObject:shortStream]; + RACStream *stream = [streamClass zip:streams reduce:^ NSString * (id w, id x, id y, id z) { + return [NSString stringWithFormat:@"%@ %@ %@ %@", w, x, y, z]; + }]; + verifyValues(stream, @[ @"Ada eats fish now", @"Bob cooks bear later" ]); + }); + + qck_it(@"should work on infinite streams", ^{ + NSArray *streams = [threeStreams arrayByAddingObject:infiniteStream]; + RACStream *stream = [streamClass zip:streams reduce:^ NSString * (id w, id x, id y, id z) { + return [NSString stringWithFormat:@"%@ %@ %@", w, x, y]; + }]; + verifyValues(stream, @[ @"Ada eats fish", @"Bob cooks bear", @"Dea jumps rock" ]); + }); + + qck_it(@"should handle multiples of the same stream", ^{ + NSArray *streams = @[ streamOne, streamOne, streamTwo, streamThree, streamTwo, streamThree ]; + RACStream *stream = [streamClass zip:streams reduce:^ NSString * (id x1, id x2, id y1, id z1, id y2, id z2) { + return [NSString stringWithFormat:@"%@ %@ %@ %@ %@ %@", x1, x2, y1, z1, y2, z2]; + }]; + verifyValues(stream, @[ @"Ada Ada eats fish eats fish", @"Bob Bob cooks bear cooks bear", @"Dea Dea jumps rock jumps rock" ]); + }); + }); + + qck_describe(@"+zip:", ^{ + qck_it(@"should make a stream of tuples out of single value", ^{ + RACStream *stream = [streamClass zip:@[ streamOne ]]; + verifyValues(stream, oneStreamTuples); + }); + + qck_it(@"should make a stream of tuples out of an array of streams", ^{ + RACStream *stream = [streamClass zip:threeStreams]; + verifyValues(stream, threeStreamTuples); + }); + + qck_it(@"should make an empty stream if given an empty array", ^{ + RACStream *stream = [streamClass zip:@[]]; + verifyValues(stream, @[]); + }); + + qck_it(@"should make a stream of tuples out of an enumerator of streams", ^{ + RACStream *stream = [streamClass zip:threeStreams.objectEnumerator]; + verifyValues(stream, threeStreamTuples); + }); + + qck_it(@"should make an empty stream if given an empty enumerator", ^{ + RACStream *stream = [streamClass zip:@[].objectEnumerator]; + verifyValues(stream, @[]); + }); + }); + }); + + qck_describe(@"+concat:", ^{ + __block NSArray *streams = nil; + __block NSArray *result = nil; + + qck_beforeEach(^{ + RACStream *a = [streamClass return:@0]; + RACStream *b = [streamClass empty]; + RACStream *c = streamWithValues(@[ @1, @2, @3 ]); + RACStream *d = [streamClass return:@4]; + RACStream *e = [streamClass return:@5]; + RACStream *f = [streamClass empty]; + RACStream *g = [streamClass empty]; + RACStream *h = streamWithValues(@[ @6, @7 ]); + streams = @[ a, b, c, d, e, f, g, h ]; + result = @[ @0, @1, @2, @3, @4, @5, @6, @7 ]; + }); + + qck_it(@"should concatenate an array of streams", ^{ + RACStream *stream = [streamClass concat:streams]; + verifyValues(stream, result); + }); + + qck_it(@"should concatenate an enumerator of streams", ^{ + RACStream *stream = [streamClass concat:streams.objectEnumerator]; + verifyValues(stream, result); + }); + }); + + qck_describe(@"scanning", ^{ + NSArray *values = @[ @1, @2, @3, @4 ]; + + __block RACStream *stream; + + qck_beforeEach(^{ + stream = streamWithValues(values); + }); + + qck_it(@"should scan", ^{ + RACStream *scanned = [stream scanWithStart:@0 reduce:^(NSNumber *running, NSNumber *next) { + return @(running.integerValue + next.integerValue); + }]; + + verifyValues(scanned, @[ @1, @3, @6, @10 ]); + }); + + qck_it(@"should scan with index", ^{ + RACStream *scanned = [stream scanWithStart:@0 reduceWithIndex:^(NSNumber *running, NSNumber *next, NSUInteger index) { + return @(running.integerValue + next.integerValue + (NSInteger)index); + }]; + + verifyValues(scanned, @[ @1, @4, @9, @16 ]); + }); + }); + + qck_describe(@"taking with a predicate", ^{ + NSArray *values = @[ @0, @1, @2, @3, @0, @2, @4 ]; + + __block RACStream *stream; + + qck_beforeEach(^{ + stream = streamWithValues(values); + }); + + qck_it(@"should take until a predicate is true", ^{ + RACStream *taken = [stream takeUntilBlock:^ BOOL (NSNumber *x) { + return x.integerValue >= 3; + }]; + + verifyValues(taken, @[ @0, @1, @2 ]); + }); + + qck_it(@"should take while a predicate is true", ^{ + RACStream *taken = [stream takeWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue <= 1; + }]; + + verifyValues(taken, @[ @0, @1 ]); + }); + + qck_it(@"should take a full stream", ^{ + RACStream *taken = [stream takeWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue <= 10; + }]; + + verifyValues(taken, values); + }); + + qck_it(@"should return an empty stream", ^{ + RACStream *taken = [stream takeWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue < 0; + }]; + + verifyValues(taken, @[]); + }); + + qck_it(@"should terminate an infinite stream", ^{ + RACStream *infiniteCounter = [infiniteStream scanWithStart:@0 reduce:^(NSNumber *running, id _) { + return @(running.unsignedIntegerValue + 1); + }]; + + RACStream *taken = [infiniteCounter takeWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue <= 5; + }]; + + verifyValues(taken, @[ @1, @2, @3, @4, @5 ]); + }); + }); + + qck_describe(@"skipping with a predicate", ^{ + NSArray *values = @[ @0, @1, @2, @3, @0, @2, @4 ]; + + __block RACStream *stream; + + qck_beforeEach(^{ + stream = streamWithValues(values); + }); + + qck_it(@"should skip until a predicate is true", ^{ + RACStream *taken = [stream skipUntilBlock:^ BOOL (NSNumber *x) { + return x.integerValue >= 3; + }]; + + verifyValues(taken, @[ @3, @0, @2, @4 ]); + }); + + qck_it(@"should skip while a predicate is true", ^{ + RACStream *taken = [stream skipWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue <= 1; + }]; + + verifyValues(taken, @[ @2, @3, @0, @2, @4 ]); + }); + + qck_it(@"should skip a full stream", ^{ + RACStream *taken = [stream skipWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue <= 10; + }]; + + verifyValues(taken, @[]); + }); + + qck_it(@"should finish skipping immediately", ^{ + RACStream *taken = [stream skipWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue < 0; + }]; + + verifyValues(taken, values); + }); + }); + + qck_describe(@"-combinePreviousWithStart:reduce:", ^{ + NSArray *values = @[ @1, @2, @3 ]; + __block RACStream *stream; + qck_beforeEach(^{ + stream = streamWithValues(values); + }); + + qck_it(@"should pass the previous next into the reduce block", ^{ + NSMutableArray *previouses = [NSMutableArray array]; + RACStream *mapped = [stream combinePreviousWithStart:nil reduce:^(id previous, id next) { + [previouses addObject:previous ?: RACTupleNil.tupleNil]; + return next; + }]; + + verifyValues(mapped, @[ @1, @2, @3 ]); + + NSArray *expected = @[ RACTupleNil.tupleNil, @1, @2 ]; + expect(previouses).to(equal(expected)); + }); + + qck_it(@"should send the combined value", ^{ + RACStream *mapped = [stream combinePreviousWithStart:@1 reduce:^(NSNumber *previous, NSNumber *next) { + return [NSString stringWithFormat:@"%lu - %lu", (unsigned long)previous.unsignedIntegerValue, (unsigned long)next.unsignedIntegerValue]; + }]; + + verifyValues(mapped, @[ @"1 - 1", @"1 - 2", @"2 - 3" ]); + }); + }); + + qck_it(@"should reduce tuples", ^{ + RACStream *stream = streamWithValues(@[ + RACTuplePack(@"foo", @"bar"), + RACTuplePack(@"buzz", @"baz"), + RACTuplePack(@"", @"_") + ]); + + RACStream *reduced = [stream reduceEach:^(NSString *a, NSString *b) { + return [a stringByAppendingString:b]; + }]; + + verifyValues(reduced, @[ @"foobar", @"buzzbaz", @"_" ]); + }); + }); } QuickConfigurationEnd diff --git a/ReactiveObjCTests/RACSubclassObject.m b/ReactiveObjCTests/RACSubclassObject.m index dd8ae6c9e..8c85344c4 100644 --- a/ReactiveObjCTests/RACSubclassObject.m +++ b/ReactiveObjCTests/RACSubclassObject.m @@ -12,27 +12,27 @@ @implementation RACSubclassObject - (void)forwardInvocation:(NSInvocation *)invocation { - self.forwardedSelector = invocation.selector; + self.forwardedSelector = invocation.selector; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { - NSParameterAssert(selector != NULL); + NSParameterAssert(selector != NULL); - NSMethodSignature *signature = [super methodSignatureForSelector:selector]; - if (signature != nil) return signature; + NSMethodSignature *signature = [super methodSignatureForSelector:selector]; + if (signature != nil) return signature; - return [super methodSignatureForSelector:@selector(description)]; + return [super methodSignatureForSelector:@selector(description)]; } - (NSString *)combineObjectValue:(id)objectValue andIntegerValue:(NSInteger)integerValue { - NSString *appended = [[objectValue description] stringByAppendingString:@"SUBCLASS"]; - return [super combineObjectValue:appended andIntegerValue:integerValue]; + NSString *appended = [[objectValue description] stringByAppendingString:@"SUBCLASS"]; + return [super combineObjectValue:appended andIntegerValue:integerValue]; } - (void)setObjectValue:(id)objectValue andSecondObjectValue:(id)secondObjectValue { - [RACScheduler.currentScheduler schedule:^{ - [super setObjectValue:objectValue andSecondObjectValue:secondObjectValue]; - }]; + [RACScheduler.currentScheduler schedule:^{ + [super setObjectValue:objectValue andSecondObjectValue:secondObjectValue]; + }]; } @end diff --git a/ReactiveObjCTests/RACSubjectSpec.m b/ReactiveObjCTests/RACSubjectSpec.m index dc71cd2b8..ae815b3e7 100644 --- a/ReactiveObjCTests/RACSubjectSpec.m +++ b/ReactiveObjCTests/RACSubjectSpec.m @@ -30,9 +30,9 @@ @interface RACTestSubscriber : NSObject @implementation RACTestSubscriber - (instancetype)init { - self = [super init]; - _disposable = [RACDisposable new]; - return self; + self = [super init]; + _disposable = [RACDisposable new]; + return self; } - (void)sendNext:(id)value {} @@ -40,7 +40,7 @@ - (void)sendError:(NSError *)error {} - (void)sendCompleted {} - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable { - [disposable addDisposable:self.disposable]; + [disposable addDisposable:self.disposable]; } @end @@ -48,320 +48,320 @@ - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable { QuickSpecBegin(RACSubjectSpec) qck_describe(@"RACSubject", ^{ - __block RACSubject *subject; - __block NSMutableArray *values; - - __block BOOL success; - __block NSError *error; - - qck_beforeEach(^{ - values = [NSMutableArray array]; - - subject = [RACSubject subject]; - success = YES; - error = nil; - - [subject subscribeNext:^(id value) { - [values addObject:value]; - } error:^(NSError *e) { - error = e; - success = NO; - } completed:^{ - success = YES; - }]; - }); - - qck_it(@"should dispose the paired disposable when a subscription terminates", ^{ - RACSubject* subject = [RACSubject new]; - RACTestSubscriber* subscriber = [RACTestSubscriber new]; - - [[subject subscribe:subscriber] dispose]; - - expect(@(subscriber.disposable.disposed)).to(beTruthy()); - }); - - qck_itBehavesLike(RACSubscriberExamples, ^{ - return @{ - RACSubscriberExampleSubscriber: subject, - RACSubscriberExampleValuesReceivedBlock: [^{ return [values copy]; } copy], - RACSubscriberExampleErrorReceivedBlock: [^{ return error; } copy], - RACSubscriberExampleSuccessBlock: [^{ return success; } copy] - }; - }); + __block RACSubject *subject; + __block NSMutableArray *values; + + __block BOOL success; + __block NSError *error; + + qck_beforeEach(^{ + values = [NSMutableArray array]; + + subject = [RACSubject subject]; + success = YES; + error = nil; + + [subject subscribeNext:^(id value) { + [values addObject:value]; + } error:^(NSError *e) { + error = e; + success = NO; + } completed:^{ + success = YES; + }]; + }); + + qck_it(@"should dispose the paired disposable when a subscription terminates", ^{ + RACSubject* subject = [RACSubject new]; + RACTestSubscriber* subscriber = [RACTestSubscriber new]; + + [[subject subscribe:subscriber] dispose]; + + expect(@(subscriber.disposable.disposed)).to(beTruthy()); + }); + + qck_itBehavesLike(RACSubscriberExamples, ^{ + return @{ + RACSubscriberExampleSubscriber: subject, + RACSubscriberExampleValuesReceivedBlock: [^{ return [values copy]; } copy], + RACSubscriberExampleErrorReceivedBlock: [^{ return error; } copy], + RACSubscriberExampleSuccessBlock: [^{ return success; } copy] + }; + }); }); qck_describe(@"RACReplaySubject", ^{ - __block RACReplaySubject *subject = nil; + __block RACReplaySubject *subject = nil; - qck_describe(@"with a capacity of 1", ^{ - qck_beforeEach(^{ - subject = [RACReplaySubject replaySubjectWithCapacity:1]; - }); + qck_describe(@"with a capacity of 1", ^{ + qck_beforeEach(^{ + subject = [RACReplaySubject replaySubjectWithCapacity:1]; + }); - qck_it(@"should send the last value", ^{ - id firstValue = @"blah"; - id secondValue = @"more blah"; + qck_it(@"should send the last value", ^{ + id firstValue = @"blah"; + id secondValue = @"more blah"; - [subject sendNext:firstValue]; - [subject sendNext:secondValue]; + [subject sendNext:firstValue]; + [subject sendNext:secondValue]; - __block id valueReceived = nil; - [subject subscribeNext:^(id x) { - valueReceived = x; - }]; + __block id valueReceived = nil; + [subject subscribeNext:^(id x) { + valueReceived = x; + }]; - expect(valueReceived).to(equal(secondValue)); - }); - - qck_it(@"should send the last value to new subscribers after completion", ^{ - id firstValue = @"blah"; - id secondValue = @"more blah"; - - __block id valueReceived = nil; - __block NSUInteger nextsReceived = 0; - - [subject sendNext:firstValue]; - [subject sendNext:secondValue]; - - expect(@(nextsReceived)).to(equal(@0)); - expect(valueReceived).to(beNil()); - - [subject sendCompleted]; - - [subject subscribeNext:^(id x) { - valueReceived = x; - nextsReceived++; - }]; - - expect(@(nextsReceived)).to(equal(@1)); - expect(valueReceived).to(equal(secondValue)); - }); - - qck_it(@"should not send any values to new subscribers if none were sent originally", ^{ - [subject sendCompleted]; - - __block BOOL nextInvoked = NO; - [subject subscribeNext:^(id x) { - nextInvoked = YES; - }]; - - expect(@(nextInvoked)).to(beFalsy()); - }); - - qck_it(@"should resend errors", ^{ - NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil]; - [subject sendError:error]; - - __block BOOL errorSent = NO; - [subject subscribeError:^(NSError *sentError) { - expect(sentError).to(equal(error)); - errorSent = YES; - }]; - - expect(@(errorSent)).to(beTruthy()); - }); - - qck_it(@"should resend nil errors", ^{ - [subject sendError:nil]; - - __block BOOL errorSent = NO; - [subject subscribeError:^(NSError *sentError) { - expect(sentError).to(beNil()); - errorSent = YES; - }]; - - expect(@(errorSent)).to(beTruthy()); - }); - }); - - qck_describe(@"with an unlimited capacity", ^{ - qck_beforeEach(^{ - subject = [RACReplaySubject subject]; - }); - - qck_itBehavesLike(RACSubscriberExamples, ^{ - return @{ - RACSubscriberExampleSubscriber: subject, - RACSubscriberExampleValuesReceivedBlock: [^{ - NSMutableArray *values = [NSMutableArray array]; - - // This subscription should synchronously dump all values already - // received into 'values'. - [subject subscribeNext:^(id value) { - [values addObject:value]; - }]; - - return values; - } copy], - RACSubscriberExampleErrorReceivedBlock: [^{ - __block NSError *error = nil; - - [subject subscribeError:^(NSError *x) { - error = x; - }]; - - return error; - } copy], - RACSubscriberExampleSuccessBlock: [^{ - __block BOOL success = YES; - - [subject subscribeError:^(NSError *x) { - success = NO; - }]; - - return success; - } copy] - }; - }); - - qck_it(@"should send both values to new subscribers after completion", ^{ - id firstValue = @"blah"; - id secondValue = @"more blah"; - - [subject sendNext:firstValue]; - [subject sendNext:secondValue]; - [subject sendCompleted]; - - __block BOOL completed = NO; - NSMutableArray *valuesReceived = [NSMutableArray array]; - [subject subscribeNext:^(id x) { - [valuesReceived addObject:x]; - } completed:^{ - completed = YES; - }]; - - expect(valuesReceived).to(haveCount(@2)); - NSArray *expected = [NSArray arrayWithObjects:firstValue, secondValue, nil]; - expect(valuesReceived).to(equal(expected)); - expect(@(completed)).to(beTruthy()); - }); - - qck_it(@"should send values in the same order live as when replaying", ^{ - NSUInteger count = 49317; - - // Just leak it, ain't no thang. - __unsafe_unretained volatile id *values = (__unsafe_unretained id *)calloc(count, sizeof(*values)); - __block atomic_int nextIndex = 0; - - [subject subscribeNext:^(NSNumber *value) { - int32_t indexPlusOne = ++nextIndex; - values[indexPlusOne - 1] = value; - }]; - - dispatch_queue_t queue = dispatch_queue_create("org.reactivecocoa.ReactiveObjC.RACSubjectSpec", DISPATCH_QUEUE_CONCURRENT); - dispatch_suspend(queue); - - for (NSUInteger i = 0; i < count; i++) { - dispatch_async(queue, ^{ - [subject sendNext:@(i)]; - }); - } - - dispatch_resume(queue); - dispatch_barrier_sync(queue, ^{ - [subject sendCompleted]; - }); - - atomic_thread_fence(memory_order_seq_cst); - - NSArray *liveValues = [NSArray arrayWithObjects:(id *)values count:(NSUInteger)nextIndex]; - expect(liveValues).to(haveCount(@(count))); - - NSArray *replayedValues = subject.toArray; - expect(replayedValues).to(haveCount(@(count))); - - // It should return the same ordering for multiple invocations too. - expect(replayedValues).to(equal(subject.toArray)); - - [replayedValues enumerateObjectsUsingBlock:^(id value, NSUInteger index, BOOL *stop) { - expect(liveValues[index]).to(equal(value)); - }]; - }); - - qck_it(@"should have a current scheduler when replaying", ^{ - [subject sendNext:RACUnit.defaultUnit]; - - __block atomic_bool hasCurrentScheduler = NO; - [subject subscribeNext:^(id x) { - hasCurrentScheduler = RACScheduler.currentScheduler != nil; - }]; - - expect(hasCurrentScheduler).notTo(beNil()); - - hasCurrentScheduler = NO; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [subject subscribeNext:^(id x) { - hasCurrentScheduler = RACScheduler.currentScheduler != nil; - }]; - }); - - expect(hasCurrentScheduler).toEventually(beTruthy()); - }); - - qck_it(@"should stop replaying when the subscription is disposed", ^{ - NSMutableArray *values = [NSMutableArray array]; - - [subject sendNext:@0]; - [subject sendNext:@1]; + expect(valueReceived).to(equal(secondValue)); + }); + + qck_it(@"should send the last value to new subscribers after completion", ^{ + id firstValue = @"blah"; + id secondValue = @"more blah"; + + __block id valueReceived = nil; + __block NSUInteger nextsReceived = 0; + + [subject sendNext:firstValue]; + [subject sendNext:secondValue]; + + expect(@(nextsReceived)).to(equal(@0)); + expect(valueReceived).to(beNil()); + + [subject sendCompleted]; + + [subject subscribeNext:^(id x) { + valueReceived = x; + nextsReceived++; + }]; + + expect(@(nextsReceived)).to(equal(@1)); + expect(valueReceived).to(equal(secondValue)); + }); + + qck_it(@"should not send any values to new subscribers if none were sent originally", ^{ + [subject sendCompleted]; + + __block BOOL nextInvoked = NO; + [subject subscribeNext:^(id x) { + nextInvoked = YES; + }]; + + expect(@(nextInvoked)).to(beFalsy()); + }); + + qck_it(@"should resend errors", ^{ + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil]; + [subject sendError:error]; + + __block BOOL errorSent = NO; + [subject subscribeError:^(NSError *sentError) { + expect(sentError).to(equal(error)); + errorSent = YES; + }]; + + expect(@(errorSent)).to(beTruthy()); + }); + + qck_it(@"should resend nil errors", ^{ + [subject sendError:nil]; + + __block BOOL errorSent = NO; + [subject subscribeError:^(NSError *sentError) { + expect(sentError).to(beNil()); + errorSent = YES; + }]; + + expect(@(errorSent)).to(beTruthy()); + }); + }); + + qck_describe(@"with an unlimited capacity", ^{ + qck_beforeEach(^{ + subject = [RACReplaySubject subject]; + }); + + qck_itBehavesLike(RACSubscriberExamples, ^{ + return @{ + RACSubscriberExampleSubscriber: subject, + RACSubscriberExampleValuesReceivedBlock: [^{ + NSMutableArray *values = [NSMutableArray array]; + + // This subscription should synchronously dump all values already + // received into 'values'. + [subject subscribeNext:^(id value) { + [values addObject:value]; + }]; + + return values; + } copy], + RACSubscriberExampleErrorReceivedBlock: [^{ + __block NSError *error = nil; + + [subject subscribeError:^(NSError *x) { + error = x; + }]; + + return error; + } copy], + RACSubscriberExampleSuccessBlock: [^{ + __block BOOL success = YES; + + [subject subscribeError:^(NSError *x) { + success = NO; + }]; + + return success; + } copy] + }; + }); + + qck_it(@"should send both values to new subscribers after completion", ^{ + id firstValue = @"blah"; + id secondValue = @"more blah"; + + [subject sendNext:firstValue]; + [subject sendNext:secondValue]; + [subject sendCompleted]; + + __block BOOL completed = NO; + NSMutableArray *valuesReceived = [NSMutableArray array]; + [subject subscribeNext:^(id x) { + [valuesReceived addObject:x]; + } completed:^{ + completed = YES; + }]; + + expect(valuesReceived).to(haveCount(@2)); + NSArray *expected = [NSArray arrayWithObjects:firstValue, secondValue, nil]; + expect(valuesReceived).to(equal(expected)); + expect(@(completed)).to(beTruthy()); + }); + + qck_it(@"should send values in the same order live as when replaying", ^{ + NSUInteger count = 49317; + + // Just leak it, ain't no thang. + __unsafe_unretained volatile id *values = (__unsafe_unretained id *)calloc(count, sizeof(*values)); + __block atomic_int nextIndex = 0; + + [subject subscribeNext:^(NSNumber *value) { + int32_t indexPlusOne = ++nextIndex; + values[indexPlusOne - 1] = value; + }]; + + dispatch_queue_t queue = dispatch_queue_create("org.reactivecocoa.ReactiveObjC.RACSubjectSpec", DISPATCH_QUEUE_CONCURRENT); + dispatch_suspend(queue); + + for (NSUInteger i = 0; i < count; i++) { + dispatch_async(queue, ^{ + [subject sendNext:@(i)]; + }); + } + + dispatch_resume(queue); + dispatch_barrier_sync(queue, ^{ + [subject sendCompleted]; + }); + + atomic_thread_fence(memory_order_seq_cst); + + NSArray *liveValues = [NSArray arrayWithObjects:(id *)values count:(NSUInteger)nextIndex]; + expect(liveValues).to(haveCount(@(count))); + + NSArray *replayedValues = subject.toArray; + expect(replayedValues).to(haveCount(@(count))); + + // It should return the same ordering for multiple invocations too. + expect(replayedValues).to(equal(subject.toArray)); + + [replayedValues enumerateObjectsUsingBlock:^(id value, NSUInteger index, BOOL *stop) { + expect(liveValues[index]).to(equal(value)); + }]; + }); + + qck_it(@"should have a current scheduler when replaying", ^{ + [subject sendNext:RACUnit.defaultUnit]; + + __block atomic_bool hasCurrentScheduler = NO; + [subject subscribeNext:^(id x) { + hasCurrentScheduler = RACScheduler.currentScheduler != nil; + }]; + + expect(hasCurrentScheduler).notTo(beNil()); + + hasCurrentScheduler = NO; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [subject subscribeNext:^(id x) { + hasCurrentScheduler = RACScheduler.currentScheduler != nil; + }]; + }); + + expect(hasCurrentScheduler).toEventually(beTruthy()); + }); + + qck_it(@"should stop replaying when the subscription is disposed", ^{ + NSMutableArray *values = [NSMutableArray array]; + + [subject sendNext:@0]; + [subject sendNext:@1]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - __block RACDisposable *disposable = [subject subscribeNext:^(id x) { - expect(disposable).notTo(beNil()); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + __block RACDisposable *disposable = [subject subscribeNext:^(id x) { + expect(disposable).notTo(beNil()); - [values addObject:x]; - [disposable dispose]; - }]; - }); + [values addObject:x]; + [disposable dispose]; + }]; + }); - expect(values).toEventually(equal(@[ @0 ])); - }); + expect(values).toEventually(equal(@[ @0 ])); + }); - qck_it(@"should finish replaying before completing", ^{ - [subject sendNext:@1]; - - __block id received; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [subject subscribeNext:^(id x) { - received = x; - }]; + qck_it(@"should finish replaying before completing", ^{ + [subject sendNext:@1]; + + __block id received; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [subject subscribeNext:^(id x) { + received = x; + }]; - [subject sendCompleted]; - }); + [subject sendCompleted]; + }); - expect(received).toEventually(equal(@1)); - }); + expect(received).toEventually(equal(@1)); + }); - qck_it(@"should finish replaying before erroring", ^{ - [subject sendNext:@1]; + qck_it(@"should finish replaying before erroring", ^{ + [subject sendNext:@1]; - __block id received; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [subject subscribeNext:^(id x) { - received = x; - }]; + __block id received; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [subject subscribeNext:^(id x) { + received = x; + }]; - [subject sendError:[NSError errorWithDomain:@"blah" code:-99 userInfo:nil]]; - }); + [subject sendError:[NSError errorWithDomain:@"blah" code:-99 userInfo:nil]]; + }); - expect(received).toEventually(equal(@1)); - }); + expect(received).toEventually(equal(@1)); + }); - qck_it(@"should finish replaying before sending new values", ^{ - [subject sendNext:@1]; + qck_it(@"should finish replaying before sending new values", ^{ + [subject sendNext:@1]; - NSMutableArray *received = [NSMutableArray array]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [subject subscribeNext:^(id x) { - [received addObject:x]; - }]; + NSMutableArray *received = [NSMutableArray array]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [subject subscribeNext:^(id x) { + [received addObject:x]; + }]; - [subject sendNext:@2]; - }); + [subject sendNext:@2]; + }); - NSArray *expected = @[ @1, @2 ]; - expect(received).toEventually(equal(expected)); - }); - }); + NSArray *expected = @[ @1, @2 ]; + expect(received).toEventually(equal(expected)); + }); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACSubscriberExamples.m b/ReactiveObjCTests/RACSubscriberExamples.m index 54a5a2725..106b8e4c7 100644 --- a/ReactiveObjCTests/RACSubscriberExamples.m +++ b/ReactiveObjCTests/RACSubscriberExamples.m @@ -26,165 +26,165 @@ QuickConfigurationBegin(RACSubscriberExampleGroups) + (void)configure:(Configuration *)configuration { - sharedExamples(RACSubscriberExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block NSArray * (^valuesReceived)(void); - __block NSError * (^errorReceived)(void); - __block BOOL (^success)(void); - __block id subscriber; - - qck_beforeEach(^{ - valuesReceived = exampleContext()[RACSubscriberExampleValuesReceivedBlock]; - errorReceived = exampleContext()[RACSubscriberExampleErrorReceivedBlock]; - success = exampleContext()[RACSubscriberExampleSuccessBlock]; - subscriber = exampleContext()[RACSubscriberExampleSubscriber]; - expect(subscriber).notTo(beNil()); - }); - - qck_it(@"should accept a nil error", ^{ - [subscriber sendError:nil]; - - expect(@(success())).to(beFalsy()); - expect(errorReceived()).to(beNil()); - expect(valuesReceived()).to(equal(@[])); - }); - - qck_describe(@"with values", ^{ - __block NSSet *values; - - qck_beforeEach(^{ - NSMutableSet *mutableValues = [NSMutableSet set]; - for (NSUInteger i = 0; i < 20; i++) { - [mutableValues addObject:@(i)]; - } - - values = [mutableValues copy]; - }); - - qck_it(@"should send nexts serially, even when delivered from multiple threads", ^{ - NSArray *allValues = values.allObjects; - dispatch_apply(allValues.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), [^(size_t index) { - [subscriber sendNext:allValues[index]]; - } copy]); - - expect(@(success())).to(beTruthy()); - expect(errorReceived()).to(beNil()); - - NSSet *valuesReceivedSet = [NSSet setWithArray:valuesReceived()]; - expect(valuesReceivedSet).to(equal(values)); - }); - }); - - qck_describe(@"multiple subscriptions", ^{ - __block RACSubject *first; - __block RACSubject *second; - - qck_beforeEach(^{ - first = [RACSubject subject]; - [first subscribe:subscriber]; - - second = [RACSubject subject]; - [second subscribe:subscriber]; - }); - - qck_it(@"should send values from all subscriptions", ^{ - [first sendNext:@"foo"]; - [second sendNext:@"bar"]; - [first sendNext:@"buzz"]; - [second sendNext:@"baz"]; - - expect(@(success())).to(beTruthy()); - expect(errorReceived()).to(beNil()); - - NSArray *expected = @[ @"foo", @"bar", @"buzz", @"baz" ]; - expect(valuesReceived()).to(equal(expected)); - }); - - qck_it(@"should terminate after the first error from any subscription", ^{ - NSError *error = [NSError errorWithDomain:@"" code:-1 userInfo:nil]; - - [first sendNext:@"foo"]; - [second sendError:error]; - [first sendNext:@"buzz"]; - - expect(@(success())).to(beFalsy()); - expect(errorReceived()).to(equal(error)); - - NSArray *expected = @[ @"foo" ]; - expect(valuesReceived()).to(equal(expected)); - }); - - qck_it(@"should terminate after the first completed from any subscription", ^{ - [first sendNext:@"foo"]; - [second sendNext:@"bar"]; - [first sendCompleted]; - [second sendNext:@"baz"]; - - expect(@(success())).to(beTruthy()); - expect(errorReceived()).to(beNil()); - - NSArray *expected = @[ @"foo", @"bar" ]; - expect(valuesReceived()).to(equal(expected)); - }); - - qck_it(@"should dispose of all current subscriptions upon termination", ^{ - __block BOOL firstDisposed = NO; - RACSignal *firstDisposableSignal = [RACSignal createSignal:^(id subscriber) { - return [RACDisposable disposableWithBlock:^{ - firstDisposed = YES; - }]; - }]; - - __block BOOL secondDisposed = NO; - RACSignal *secondDisposableSignal = [RACSignal createSignal:^(id subscriber) { - return [RACDisposable disposableWithBlock:^{ - secondDisposed = YES; - }]; - }]; - - [firstDisposableSignal subscribe:subscriber]; - [secondDisposableSignal subscribe:subscriber]; - - expect(@(firstDisposed)).to(beFalsy()); - expect(@(secondDisposed)).to(beFalsy()); - - [first sendCompleted]; - - expect(@(firstDisposed)).to(beTruthy()); - expect(@(secondDisposed)).to(beTruthy()); - }); - - qck_it(@"should dispose of future subscriptions upon termination", ^{ - __block BOOL disposed = NO; - RACSignal *disposableSignal = [RACSignal createSignal:^(id subscriber) { - return [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; - }]; - - [first sendCompleted]; - expect(@(disposed)).to(beFalsy()); - - [disposableSignal subscribe:subscriber]; - expect(@(disposed)).to(beTruthy()); - }); - }); - - qck_describe(@"memory management", ^{ - qck_it(@"should not retain disposed disposables", ^{ - __block BOOL disposableDeallocd = NO; - @autoreleasepool { - RACCompoundDisposable *disposable __attribute__((objc_precise_lifetime)) = [RACCompoundDisposable disposableWithBlock:^{}]; - [disposable.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - disposableDeallocd = YES; - }]]; - - [subscriber didSubscribeWithDisposable:disposable]; - [disposable dispose]; - } - expect(@(disposableDeallocd)).to(beTruthy()); - }); - }); - }); + sharedExamples(RACSubscriberExamples, ^(QCKDSLSharedExampleContext exampleContext) { + __block NSArray * (^valuesReceived)(void); + __block NSError * (^errorReceived)(void); + __block BOOL (^success)(void); + __block id subscriber; + + qck_beforeEach(^{ + valuesReceived = exampleContext()[RACSubscriberExampleValuesReceivedBlock]; + errorReceived = exampleContext()[RACSubscriberExampleErrorReceivedBlock]; + success = exampleContext()[RACSubscriberExampleSuccessBlock]; + subscriber = exampleContext()[RACSubscriberExampleSubscriber]; + expect(subscriber).notTo(beNil()); + }); + + qck_it(@"should accept a nil error", ^{ + [subscriber sendError:nil]; + + expect(@(success())).to(beFalsy()); + expect(errorReceived()).to(beNil()); + expect(valuesReceived()).to(equal(@[])); + }); + + qck_describe(@"with values", ^{ + __block NSSet *values; + + qck_beforeEach(^{ + NSMutableSet *mutableValues = [NSMutableSet set]; + for (NSUInteger i = 0; i < 20; i++) { + [mutableValues addObject:@(i)]; + } + + values = [mutableValues copy]; + }); + + qck_it(@"should send nexts serially, even when delivered from multiple threads", ^{ + NSArray *allValues = values.allObjects; + dispatch_apply(allValues.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), [^(size_t index) { + [subscriber sendNext:allValues[index]]; + } copy]); + + expect(@(success())).to(beTruthy()); + expect(errorReceived()).to(beNil()); + + NSSet *valuesReceivedSet = [NSSet setWithArray:valuesReceived()]; + expect(valuesReceivedSet).to(equal(values)); + }); + }); + + qck_describe(@"multiple subscriptions", ^{ + __block RACSubject *first; + __block RACSubject *second; + + qck_beforeEach(^{ + first = [RACSubject subject]; + [first subscribe:subscriber]; + + second = [RACSubject subject]; + [second subscribe:subscriber]; + }); + + qck_it(@"should send values from all subscriptions", ^{ + [first sendNext:@"foo"]; + [second sendNext:@"bar"]; + [first sendNext:@"buzz"]; + [second sendNext:@"baz"]; + + expect(@(success())).to(beTruthy()); + expect(errorReceived()).to(beNil()); + + NSArray *expected = @[ @"foo", @"bar", @"buzz", @"baz" ]; + expect(valuesReceived()).to(equal(expected)); + }); + + qck_it(@"should terminate after the first error from any subscription", ^{ + NSError *error = [NSError errorWithDomain:@"" code:-1 userInfo:nil]; + + [first sendNext:@"foo"]; + [second sendError:error]; + [first sendNext:@"buzz"]; + + expect(@(success())).to(beFalsy()); + expect(errorReceived()).to(equal(error)); + + NSArray *expected = @[ @"foo" ]; + expect(valuesReceived()).to(equal(expected)); + }); + + qck_it(@"should terminate after the first completed from any subscription", ^{ + [first sendNext:@"foo"]; + [second sendNext:@"bar"]; + [first sendCompleted]; + [second sendNext:@"baz"]; + + expect(@(success())).to(beTruthy()); + expect(errorReceived()).to(beNil()); + + NSArray *expected = @[ @"foo", @"bar" ]; + expect(valuesReceived()).to(equal(expected)); + }); + + qck_it(@"should dispose of all current subscriptions upon termination", ^{ + __block BOOL firstDisposed = NO; + RACSignal *firstDisposableSignal = [RACSignal createSignal:^(id subscriber) { + return [RACDisposable disposableWithBlock:^{ + firstDisposed = YES; + }]; + }]; + + __block BOOL secondDisposed = NO; + RACSignal *secondDisposableSignal = [RACSignal createSignal:^(id subscriber) { + return [RACDisposable disposableWithBlock:^{ + secondDisposed = YES; + }]; + }]; + + [firstDisposableSignal subscribe:subscriber]; + [secondDisposableSignal subscribe:subscriber]; + + expect(@(firstDisposed)).to(beFalsy()); + expect(@(secondDisposed)).to(beFalsy()); + + [first sendCompleted]; + + expect(@(firstDisposed)).to(beTruthy()); + expect(@(secondDisposed)).to(beTruthy()); + }); + + qck_it(@"should dispose of future subscriptions upon termination", ^{ + __block BOOL disposed = NO; + RACSignal *disposableSignal = [RACSignal createSignal:^(id subscriber) { + return [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + }]; + + [first sendCompleted]; + expect(@(disposed)).to(beFalsy()); + + [disposableSignal subscribe:subscriber]; + expect(@(disposed)).to(beTruthy()); + }); + }); + + qck_describe(@"memory management", ^{ + qck_it(@"should not retain disposed disposables", ^{ + __block BOOL disposableDeallocd = NO; + @autoreleasepool { + RACCompoundDisposable *disposable __attribute__((objc_precise_lifetime)) = [RACCompoundDisposable disposableWithBlock:^{}]; + [disposable.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + disposableDeallocd = YES; + }]]; + + [subscriber didSubscribeWithDisposable:disposable]; + [disposable dispose]; + } + expect(@(disposableDeallocd)).to(beTruthy()); + }); + }); + }); } QuickConfigurationEnd diff --git a/ReactiveObjCTests/RACSubscriberSpec.m b/ReactiveObjCTests/RACSubscriberSpec.m index 0ff9fba90..5ba5da3f9 100644 --- a/ReactiveObjCTests/RACSubscriberSpec.m +++ b/ReactiveObjCTests/RACSubscriberSpec.m @@ -27,104 +27,104 @@ __block NSError *error; qck_beforeEach(^{ - values = [NSMutableArray array]; + values = [NSMutableArray array]; - finished = NO; - nextsAfterFinished = 0; + finished = NO; + nextsAfterFinished = 0; - success = YES; - error = nil; + success = YES; + error = nil; - subscriber = [RACSubscriber subscriberWithNext:^(id value) { - if (finished) OSAtomicIncrement32Barrier(&nextsAfterFinished); + subscriber = [RACSubscriber subscriberWithNext:^(id value) { + if (finished) OSAtomicIncrement32Barrier(&nextsAfterFinished); - [values addObject:value]; - } error:^(NSError *e) { - error = e; - success = NO; - } completed:^{ - success = YES; - }]; + [values addObject:value]; + } error:^(NSError *e) { + error = e; + success = NO; + } completed:^{ + success = YES; + }]; }); qck_itBehavesLike(RACSubscriberExamples, ^{ - return @{ - RACSubscriberExampleSubscriber: subscriber, - RACSubscriberExampleValuesReceivedBlock: [^{ return [values copy]; } copy], - RACSubscriberExampleErrorReceivedBlock: [^{ return error; } copy], - RACSubscriberExampleSuccessBlock: [^{ return success; } copy] - }; + return @{ + RACSubscriberExampleSubscriber: subscriber, + RACSubscriberExampleValuesReceivedBlock: [^{ return [values copy]; } copy], + RACSubscriberExampleErrorReceivedBlock: [^{ return error; } copy], + RACSubscriberExampleSuccessBlock: [^{ return success; } copy] + }; }); qck_describe(@"finishing", ^{ - __block void (^sendValues)(void); - __block BOOL expectedSuccess; + __block void (^sendValues)(void); + __block BOOL expectedSuccess; - __block dispatch_group_t dispatchGroup; - __block dispatch_queue_t concurrentQueue; + __block dispatch_group_t dispatchGroup; + __block dispatch_queue_t concurrentQueue; - qck_beforeEach(^{ - dispatchGroup = dispatch_group_create(); - expect(dispatchGroup).notTo(beNil()); + qck_beforeEach(^{ + dispatchGroup = dispatch_group_create(); + expect(dispatchGroup).notTo(beNil()); - concurrentQueue = dispatch_queue_create("org.reactivecocoa.ReactiveObjC.RACSubscriberSpec", DISPATCH_QUEUE_CONCURRENT); - expect(concurrentQueue).notTo(beNil()); + concurrentQueue = dispatch_queue_create("org.reactivecocoa.ReactiveObjC.RACSubscriberSpec", DISPATCH_QUEUE_CONCURRENT); + expect(concurrentQueue).notTo(beNil()); - dispatch_suspend(concurrentQueue); + dispatch_suspend(concurrentQueue); - sendValues = [^{ - for (NSUInteger i = 0; i < 15; i++) { - dispatch_group_async(dispatchGroup, concurrentQueue, ^{ - [subscriber sendNext:@(i)]; - }); - } - } copy]; + sendValues = [^{ + for (NSUInteger i = 0; i < 15; i++) { + dispatch_group_async(dispatchGroup, concurrentQueue, ^{ + [subscriber sendNext:@(i)]; + }); + } + } copy]; - sendValues(); - }); + sendValues(); + }); - qck_afterEach(^{ - sendValues(); - dispatch_resume(concurrentQueue); + qck_afterEach(^{ + sendValues(); + dispatch_resume(concurrentQueue); - // Time out after one second. - dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)); - expect(@(dispatch_group_wait(dispatchGroup, time))).to(equal(@0)); + // Time out after one second. + dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)); + expect(@(dispatch_group_wait(dispatchGroup, time))).to(equal(@0)); - dispatchGroup = NULL; - concurrentQueue = NULL; + dispatchGroup = NULL; + concurrentQueue = NULL; - expect(@(nextsAfterFinished)).to(equal(@0)); + expect(@(nextsAfterFinished)).to(equal(@0)); - if (expectedSuccess) { - expect(@(success)).to(beTruthy()); - expect(error).to(beNil()); - } else { - expect(@(success)).to(beFalsy()); - } - }); + if (expectedSuccess) { + expect(@(success)).to(beTruthy()); + expect(error).to(beNil()); + } else { + expect(@(success)).to(beFalsy()); + } + }); - qck_it(@"should never invoke next after sending completed", ^{ - expectedSuccess = YES; + qck_it(@"should never invoke next after sending completed", ^{ + expectedSuccess = YES; - dispatch_group_async(dispatchGroup, concurrentQueue, ^{ - [subscriber sendCompleted]; + dispatch_group_async(dispatchGroup, concurrentQueue, ^{ + [subscriber sendCompleted]; - finished = YES; - OSMemoryBarrier(); - }); - }); + finished = YES; + OSMemoryBarrier(); + }); + }); - qck_it(@"should never invoke next after sending error", ^{ - expectedSuccess = NO; + qck_it(@"should never invoke next after sending error", ^{ + expectedSuccess = NO; - dispatch_group_async(dispatchGroup, concurrentQueue, ^{ - [subscriber sendError:nil]; + dispatch_group_async(dispatchGroup, concurrentQueue, ^{ + [subscriber sendError:nil]; - finished = YES; - OSMemoryBarrier(); - }); - }); + finished = YES; + OSMemoryBarrier(); + }); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACSubscriptingAssignmentTrampolineSpec.m b/ReactiveObjCTests/RACSubscriptingAssignmentTrampolineSpec.m index 9ed26e3da..d0e120f9d 100644 --- a/ReactiveObjCTests/RACSubscriptingAssignmentTrampolineSpec.m +++ b/ReactiveObjCTests/RACSubscriptingAssignmentTrampolineSpec.m @@ -17,20 +17,20 @@ QuickSpecBegin(RACSubscriptingAssignmentTrampolineSpec) id setupBlock = ^(RACTestObject *testObject, NSString *keyPath, id nilValue, RACSignal *signal) { - [[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:testObject nilValue:nilValue][keyPath] = signal; + [[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:testObject nilValue:nilValue][keyPath] = signal; }; qck_itBehavesLike(RACPropertySignalExamples, ^{ - return @{ RACPropertySignalExamplesSetupBlock: setupBlock }; + return @{ RACPropertySignalExamplesSetupBlock: setupBlock }; }); qck_it(@"should expand the RAC macro properly", ^{ - RACSubject *subject = [RACSubject subject]; - RACTestObject *testObject = [[RACTestObject alloc] init]; - RAC(testObject, objectValue) = subject; + RACSubject *subject = [RACSubject subject]; + RACTestObject *testObject = [[RACTestObject alloc] init]; + RAC(testObject, objectValue) = subject; - [subject sendNext:@1]; - expect(testObject.objectValue).to(equal(@1)); + [subject sendNext:@1]; + expect(testObject.objectValue).to(equal(@1)); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACTargetQueueSchedulerSpec.m b/ReactiveObjCTests/RACTargetQueueSchedulerSpec.m index c112bcd4a..0dca0a3a0 100644 --- a/ReactiveObjCTests/RACTargetQueueSchedulerSpec.m +++ b/ReactiveObjCTests/RACTargetQueueSchedulerSpec.m @@ -15,35 +15,35 @@ QuickSpecBegin(RACTargetQueueSchedulerSpec) qck_it(@"should have a valid current scheduler", ^{ - dispatch_queue_t queue = dispatch_queue_create("test-queue", DISPATCH_QUEUE_SERIAL); - RACScheduler *scheduler = [[RACTargetQueueScheduler alloc] initWithName:@"test-scheduler" targetQueue:queue]; - __block RACScheduler *currentScheduler; - [scheduler schedule:^{ - currentScheduler = RACScheduler.currentScheduler; - }]; - - expect(currentScheduler).toEventually(equal(scheduler)); + dispatch_queue_t queue = dispatch_queue_create("test-queue", DISPATCH_QUEUE_SERIAL); + RACScheduler *scheduler = [[RACTargetQueueScheduler alloc] initWithName:@"test-scheduler" targetQueue:queue]; + __block RACScheduler *currentScheduler; + [scheduler schedule:^{ + currentScheduler = RACScheduler.currentScheduler; + }]; + + expect(currentScheduler).toEventually(equal(scheduler)); }); qck_it(@"should schedule blocks FIFO even when given a concurrent queue", ^{ - dispatch_queue_t queue = dispatch_queue_create("test-queue", DISPATCH_QUEUE_CONCURRENT); - RACScheduler *scheduler = [[RACTargetQueueScheduler alloc] initWithName:@"test-scheduler" targetQueue:queue]; - __block volatile int32_t startedCount = 0; - __block volatile uint32_t waitInFirst = 1; - [scheduler schedule:^{ - OSAtomicIncrement32Barrier(&startedCount); - while (waitInFirst == 1) ; - }]; + dispatch_queue_t queue = dispatch_queue_create("test-queue", DISPATCH_QUEUE_CONCURRENT); + RACScheduler *scheduler = [[RACTargetQueueScheduler alloc] initWithName:@"test-scheduler" targetQueue:queue]; + __block volatile int32_t startedCount = 0; + __block volatile uint32_t waitInFirst = 1; + [scheduler schedule:^{ + OSAtomicIncrement32Barrier(&startedCount); + while (waitInFirst == 1) ; + }]; - [scheduler schedule:^{ - OSAtomicIncrement32Barrier(&startedCount); - }]; + [scheduler schedule:^{ + OSAtomicIncrement32Barrier(&startedCount); + }]; - expect(@(startedCount)).toEventually(equal(@1)); + expect(@(startedCount)).toEventually(equal(@1)); - OSAtomicAnd32Barrier(0, &waitInFirst); + OSAtomicAnd32Barrier(0, &waitInFirst); - expect(@(startedCount)).toEventually(equal(@2)); + expect(@(startedCount)).toEventually(equal(@2)); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACTestExampleScheduler.m b/ReactiveObjCTests/RACTestExampleScheduler.m index 75031bf9e..de1d3d5c1 100644 --- a/ReactiveObjCTests/RACTestExampleScheduler.m +++ b/ReactiveObjCTests/RACTestExampleScheduler.m @@ -14,26 +14,26 @@ @implementation RACTestExampleScheduler #pragma mark Lifecycle - (instancetype)initWithQueue:(dispatch_queue_t)queue { - return [super initWithName:nil queue:queue]; + return [super initWithName:nil queue:queue]; } #pragma mark RACScheduler - (RACDisposable *)schedule:(void (^)(void))block { - dispatch_async(self.queue, ^{ - [self performAsCurrentScheduler:block]; - }); + dispatch_async(self.queue, ^{ + [self performAsCurrentScheduler:block]; + }); - return nil; + return nil; } - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { - dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)([date timeIntervalSinceNow] * NSEC_PER_SEC)); - dispatch_after(when, self.queue, ^{ - [self performAsCurrentScheduler:block]; - }); + dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)([date timeIntervalSinceNow] * NSEC_PER_SEC)); + dispatch_after(when, self.queue, ^{ + [self performAsCurrentScheduler:block]; + }); - return nil; + return nil; } @end diff --git a/ReactiveObjCTests/RACTestObject.h b/ReactiveObjCTests/RACTestObject.h index 98dd7393c..4260fcbab 100644 --- a/ReactiveObjCTests/RACTestObject.h +++ b/ReactiveObjCTests/RACTestObject.h @@ -10,8 +10,8 @@ #import typedef struct { - long long integerField; - double doubleField; + long long integerField; + double doubleField; } RACTestStruct; @protocol RACTestProtocol diff --git a/ReactiveObjCTests/RACTestObject.m b/ReactiveObjCTests/RACTestObject.m index 66aac010a..46c6e28a1 100644 --- a/ReactiveObjCTests/RACTestObject.m +++ b/ReactiveObjCTests/RACTestObject.m @@ -11,111 +11,111 @@ @implementation RACTestObject - (void)dealloc { - free(_charPointerValue); - free((void *)_constCharPointerValue); + free(_charPointerValue); + free((void *)_constCharPointerValue); } - (void)setNilValueForKey:(NSString *)key { - if (!self.catchSetNilValueForKey) [super setNilValueForKey:key]; + if (!self.catchSetNilValueForKey) [super setNilValueForKey:key]; } - (void)setCharPointerValue:(char *)charPointerValue { - if (charPointerValue == _charPointerValue) return; - free(_charPointerValue); - _charPointerValue = strdup(charPointerValue); + if (charPointerValue == _charPointerValue) return; + free(_charPointerValue); + _charPointerValue = strdup(charPointerValue); } - (void)setConstCharPointerValue:(const char *)constCharPointerValue { - if (constCharPointerValue == _constCharPointerValue) return; - free((void *)_constCharPointerValue); - _constCharPointerValue = strdup(constCharPointerValue); + if (constCharPointerValue == _constCharPointerValue) return; + free((void *)_constCharPointerValue); + _constCharPointerValue = strdup(constCharPointerValue); } - (void)setObjectValue:(id)objectValue andIntegerValue:(NSInteger)integerValue { - self.hasInvokedSetObjectValueAndIntegerValue = YES; - self.objectValue = objectValue; - self.integerValue = integerValue; + self.hasInvokedSetObjectValueAndIntegerValue = YES; + self.objectValue = objectValue; + self.integerValue = integerValue; } - (void)setObjectValue:(id)objectValue andSecondObjectValue:(id)secondObjectValue { - self.hasInvokedSetObjectValueAndSecondObjectValue = YES; - self.objectValue = objectValue; - self.secondObjectValue = secondObjectValue; + self.hasInvokedSetObjectValueAndSecondObjectValue = YES; + self.objectValue = objectValue; + self.secondObjectValue = secondObjectValue; } - (void)setSlowObjectValue:(id)value { - [NSThread sleepForTimeInterval:0.02]; - _slowObjectValue = value; + [NSThread sleepForTimeInterval:0.02]; + _slowObjectValue = value; } - (NSString *)combineObjectValue:(id)objectValue andIntegerValue:(NSInteger)integerValue { - return [NSString stringWithFormat:@"%@: %ld", objectValue, (long)integerValue]; + return [NSString stringWithFormat:@"%@: %ld", objectValue, (long)integerValue]; } - (NSString *)combineObjectValue:(id)objectValue andSecondObjectValue:(id)secondObjectValue { - return [NSString stringWithFormat:@"%@: %@", objectValue, secondObjectValue]; + return [NSString stringWithFormat:@"%@: %@", objectValue, secondObjectValue]; } - (void)lifeIsGood:(id)sender { - + } + (void)lifeIsGood:(id)sender { - + } - (NSRange)returnRangeValueWithObjectValue:(id)objectValue andIntegerValue:(NSInteger)integerValue { - return NSMakeRange((NSUInteger)[objectValue integerValue], (NSUInteger)integerValue); + return NSMakeRange((NSUInteger)[objectValue integerValue], (NSUInteger)integerValue); } - (RACTestObject *)dynamicObjectProperty { - return [self dynamicObjectMethod]; + return [self dynamicObjectMethod]; } - (RACTestObject *)dynamicObjectMethod { - RACTestObject *testObject = [[RACTestObject alloc] init]; - testObject.integerValue = 42; - return testObject; + RACTestObject *testObject = [[RACTestObject alloc] init]; + testObject.integerValue = 42; + return testObject; } - (void)write5ToIntPointer:(int *)intPointer { - NSCParameterAssert(intPointer != NULL); - *intPointer = 5; + NSCParameterAssert(intPointer != NULL); + *intPointer = 5; } - (NSInteger)doubleInteger:(NSInteger)integer { - return integer * 2; + return integer * 2; } - (char *)doubleString:(char *)string { - size_t doubledSize = strlen(string) * 2 + 1; - char *doubledString = malloc(sizeof(char) * doubledSize); + size_t doubledSize = strlen(string) * 2 + 1; + char *doubledString = malloc(sizeof(char) * doubledSize); - doubledString[0] = '\0'; - strlcat(doubledString, string, doubledSize); - strlcat(doubledString, string, doubledSize); + doubledString[0] = '\0'; + strlcat(doubledString, string, doubledSize); + strlcat(doubledString, string, doubledSize); - dispatch_async(dispatch_get_main_queue(), ^{ - free(doubledString); - }); + dispatch_async(dispatch_get_main_queue(), ^{ + free(doubledString); + }); - return doubledString; + return doubledString; } - (const char *)doubleConstString:(const char *)string { - return [self doubleString:(char *)string]; + return [self doubleString:(char *)string]; } - (RACTestStruct)doubleStruct:(RACTestStruct)testStruct { - testStruct.integerField *= 2; - testStruct.doubleField *= 2; - return testStruct; + testStruct.integerField *= 2; + testStruct.doubleField *= 2; + return testStruct; } - (dispatch_block_t)wrapBlock:(dispatch_block_t)block { - return ^{ - block(); - }; + return ^{ + block(); + }; } @end diff --git a/ReactiveObjCTests/RACTestSchedulerSpec.m b/ReactiveObjCTests/RACTestSchedulerSpec.m index 39f2bc2b4..ccdb3ea85 100644 --- a/ReactiveObjCTests/RACTestSchedulerSpec.m +++ b/ReactiveObjCTests/RACTestSchedulerSpec.m @@ -16,163 +16,163 @@ __block RACTestScheduler *scheduler; qck_beforeEach(^{ - scheduler = [[RACTestScheduler alloc] init]; - expect(scheduler).notTo(beNil()); + scheduler = [[RACTestScheduler alloc] init]; + expect(scheduler).notTo(beNil()); }); qck_it(@"should do nothing when stepping while empty", ^{ - [scheduler step]; - [scheduler step:5]; - [scheduler stepAll]; + [scheduler step]; + [scheduler step:5]; + [scheduler stepAll]; }); qck_it(@"should execute the earliest enqueued block when stepping", ^{ - __block BOOL firstExecuted = NO; - [scheduler schedule:^{ - firstExecuted = YES; - }]; + __block BOOL firstExecuted = NO; + [scheduler schedule:^{ + firstExecuted = YES; + }]; - __block BOOL secondExecuted = NO; - [scheduler schedule:^{ - secondExecuted = YES; - }]; + __block BOOL secondExecuted = NO; + [scheduler schedule:^{ + secondExecuted = YES; + }]; - expect(@(firstExecuted)).to(beFalsy()); - expect(@(secondExecuted)).to(beFalsy()); + expect(@(firstExecuted)).to(beFalsy()); + expect(@(secondExecuted)).to(beFalsy()); - [scheduler step]; - expect(@(firstExecuted)).to(beTruthy()); - expect(@(secondExecuted)).to(beFalsy()); + [scheduler step]; + expect(@(firstExecuted)).to(beTruthy()); + expect(@(secondExecuted)).to(beFalsy()); - [scheduler step]; - expect(@(secondExecuted)).to(beTruthy()); + [scheduler step]; + expect(@(secondExecuted)).to(beTruthy()); }); qck_it(@"should step multiple times", ^{ - __block BOOL firstExecuted = NO; - [scheduler schedule:^{ - firstExecuted = YES; - }]; - - __block BOOL secondExecuted = NO; - [scheduler schedule:^{ - secondExecuted = YES; - }]; - - __block BOOL thirdExecuted = NO; - [scheduler schedule:^{ - thirdExecuted = YES; - }]; - - expect(@(firstExecuted)).to(beFalsy()); - expect(@(secondExecuted)).to(beFalsy()); - expect(@(thirdExecuted)).to(beFalsy()); - - [scheduler step:2]; - expect(@(firstExecuted)).to(beTruthy()); - expect(@(secondExecuted)).to(beTruthy()); - expect(@(thirdExecuted)).to(beFalsy()); - - [scheduler step:1]; - expect(@(thirdExecuted)).to(beTruthy()); + __block BOOL firstExecuted = NO; + [scheduler schedule:^{ + firstExecuted = YES; + }]; + + __block BOOL secondExecuted = NO; + [scheduler schedule:^{ + secondExecuted = YES; + }]; + + __block BOOL thirdExecuted = NO; + [scheduler schedule:^{ + thirdExecuted = YES; + }]; + + expect(@(firstExecuted)).to(beFalsy()); + expect(@(secondExecuted)).to(beFalsy()); + expect(@(thirdExecuted)).to(beFalsy()); + + [scheduler step:2]; + expect(@(firstExecuted)).to(beTruthy()); + expect(@(secondExecuted)).to(beTruthy()); + expect(@(thirdExecuted)).to(beFalsy()); + + [scheduler step:1]; + expect(@(thirdExecuted)).to(beTruthy()); }); qck_it(@"should step through all scheduled blocks", ^{ - __block NSUInteger executions = 0; - for (NSUInteger i = 0; i < 10; i++) { - [scheduler schedule:^{ - executions++; - }]; - } + __block NSUInteger executions = 0; + for (NSUInteger i = 0; i < 10; i++) { + [scheduler schedule:^{ + executions++; + }]; + } - expect(@(executions)).to(equal(@0)); + expect(@(executions)).to(equal(@0)); - [scheduler stepAll]; - expect(@(executions)).to(equal(@10)); + [scheduler stepAll]; + expect(@(executions)).to(equal(@10)); }); qck_it(@"should execute blocks in date order when stepping", ^{ - __block BOOL laterExecuted = NO; - [scheduler after:[NSDate distantFuture] schedule:^{ - laterExecuted = YES; - }]; + __block BOOL laterExecuted = NO; + [scheduler after:[NSDate distantFuture] schedule:^{ + laterExecuted = YES; + }]; - __block BOOL earlierExecuted = NO; - [scheduler after:[NSDate dateWithTimeIntervalSinceNow:20] schedule:^{ - earlierExecuted = YES; - }]; + __block BOOL earlierExecuted = NO; + [scheduler after:[NSDate dateWithTimeIntervalSinceNow:20] schedule:^{ + earlierExecuted = YES; + }]; - expect(@(earlierExecuted)).to(beFalsy()); - expect(@(laterExecuted)).to(beFalsy()); + expect(@(earlierExecuted)).to(beFalsy()); + expect(@(laterExecuted)).to(beFalsy()); - [scheduler step]; - expect(@(earlierExecuted)).to(beTruthy()); - expect(@(laterExecuted)).to(beFalsy()); + [scheduler step]; + expect(@(earlierExecuted)).to(beTruthy()); + expect(@(laterExecuted)).to(beFalsy()); - [scheduler step]; - expect(@(laterExecuted)).to(beTruthy()); + [scheduler step]; + expect(@(laterExecuted)).to(beTruthy()); }); qck_it(@"should execute delayed blocks in date order when stepping", ^{ - __block BOOL laterExecuted = NO; - [scheduler afterDelay:100 schedule:^{ - laterExecuted = YES; - }]; + __block BOOL laterExecuted = NO; + [scheduler afterDelay:100 schedule:^{ + laterExecuted = YES; + }]; - __block BOOL earlierExecuted = NO; - [scheduler afterDelay:50 schedule:^{ - earlierExecuted = YES; - }]; + __block BOOL earlierExecuted = NO; + [scheduler afterDelay:50 schedule:^{ + earlierExecuted = YES; + }]; - expect(@(earlierExecuted)).to(beFalsy()); - expect(@(laterExecuted)).to(beFalsy()); + expect(@(earlierExecuted)).to(beFalsy()); + expect(@(laterExecuted)).to(beFalsy()); - [scheduler step]; - expect(@(earlierExecuted)).to(beTruthy()); - expect(@(laterExecuted)).to(beFalsy()); + [scheduler step]; + expect(@(earlierExecuted)).to(beTruthy()); + expect(@(laterExecuted)).to(beFalsy()); - [scheduler step]; - expect(@(laterExecuted)).to(beTruthy()); + [scheduler step]; + expect(@(laterExecuted)).to(beTruthy()); }); qck_it(@"should execute a repeating blocks in date order", ^{ - __block NSUInteger firstExecutions = 0; - [scheduler after:[NSDate dateWithTimeIntervalSinceNow:20] repeatingEvery:5 withLeeway:0 schedule:^{ - firstExecutions++; - }]; - - __block NSUInteger secondExecutions = 0; - [scheduler after:[NSDate dateWithTimeIntervalSinceNow:22] repeatingEvery:10 withLeeway:0 schedule:^{ - secondExecutions++; - }]; - - expect(@(firstExecutions)).to(equal(@0)); - expect(@(secondExecutions)).to(equal(@0)); - - // 20 ticks - [scheduler step]; - expect(@(firstExecutions)).to(equal(@1)); - expect(@(secondExecutions)).to(equal(@0)); - - // 22 ticks - [scheduler step]; - expect(@(firstExecutions)).to(equal(@1)); - expect(@(secondExecutions)).to(equal(@1)); - - // 25 ticks - [scheduler step]; - expect(@(firstExecutions)).to(equal(@2)); - expect(@(secondExecutions)).to(equal(@1)); - - // 30 ticks - [scheduler step]; - expect(@(firstExecutions)).to(equal(@3)); - expect(@(secondExecutions)).to(equal(@1)); - - // 32 ticks - [scheduler step]; - expect(@(firstExecutions)).to(equal(@3)); - expect(@(secondExecutions)).to(equal(@2)); + __block NSUInteger firstExecutions = 0; + [scheduler after:[NSDate dateWithTimeIntervalSinceNow:20] repeatingEvery:5 withLeeway:0 schedule:^{ + firstExecutions++; + }]; + + __block NSUInteger secondExecutions = 0; + [scheduler after:[NSDate dateWithTimeIntervalSinceNow:22] repeatingEvery:10 withLeeway:0 schedule:^{ + secondExecutions++; + }]; + + expect(@(firstExecutions)).to(equal(@0)); + expect(@(secondExecutions)).to(equal(@0)); + + // 20 ticks + [scheduler step]; + expect(@(firstExecutions)).to(equal(@1)); + expect(@(secondExecutions)).to(equal(@0)); + + // 22 ticks + [scheduler step]; + expect(@(firstExecutions)).to(equal(@1)); + expect(@(secondExecutions)).to(equal(@1)); + + // 25 ticks + [scheduler step]; + expect(@(firstExecutions)).to(equal(@2)); + expect(@(secondExecutions)).to(equal(@1)); + + // 30 ticks + [scheduler step]; + expect(@(firstExecutions)).to(equal(@3)); + expect(@(secondExecutions)).to(equal(@1)); + + // 32 ticks + [scheduler step]; + expect(@(firstExecutions)).to(equal(@3)); + expect(@(secondExecutions)).to(equal(@2)); }); QuickSpecEnd diff --git a/ReactiveObjCTests/RACTestUIButton.m b/ReactiveObjCTests/RACTestUIButton.m index cff413f68..11a7957d3 100644 --- a/ReactiveObjCTests/RACTestUIButton.m +++ b/ReactiveObjCTests/RACTestUIButton.m @@ -11,8 +11,8 @@ @implementation RACTestUIButton + (instancetype)button { - RACTestUIButton *button = [self buttonWithType:UIButtonTypeCustom]; - return button; + RACTestUIButton *button = [self buttonWithType:UIButtonTypeCustom]; + return button; } // Required for unit testing – controls don't work normally @@ -20,7 +20,7 @@ + (instancetype)button { -(void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" - [target performSelector:action withObject:self]; + [target performSelector:action withObject:self]; #pragma clang diagnostic pop } diff --git a/ReactiveObjCTests/RACTupleSpec.m b/ReactiveObjCTests/RACTupleSpec.m index cd0d3535a..6ae21a70d 100644 --- a/ReactiveObjCTests/RACTupleSpec.m +++ b/ReactiveObjCTests/RACTupleSpec.m @@ -15,220 +15,220 @@ QuickSpecBegin(RACTupleSpec) qck_describe(@"RACTupleUnpack", ^{ - qck_it(@"should unpack a single value", ^{ - RACTupleUnpack(RACUnit *value) = [RACTuple tupleWithObjects:RACUnit.defaultUnit, nil]; - expect(value).to(equal(RACUnit.defaultUnit)); - }); + qck_it(@"should unpack a single value", ^{ + RACTupleUnpack(RACUnit *value) = [RACTuple tupleWithObjects:RACUnit.defaultUnit, nil]; + expect(value).to(equal(RACUnit.defaultUnit)); + }); - qck_it(@"should translate RACTupleNil", ^{ - RACTupleUnpack(id value) = [RACTuple tupleWithObjects:RACTupleNil.tupleNil, nil]; - expect(value).to(beNil()); - }); + qck_it(@"should translate RACTupleNil", ^{ + RACTupleUnpack(id value) = [RACTuple tupleWithObjects:RACTupleNil.tupleNil, nil]; + expect(value).to(beNil()); + }); - qck_it(@"should unpack multiple values", ^{ - RACTupleUnpack(NSString *str, NSNumber *num) = [RACTuple tupleWithObjects:@"foobar", @5, nil]; + qck_it(@"should unpack multiple values", ^{ + RACTupleUnpack(NSString *str, NSNumber *num) = [RACTuple tupleWithObjects:@"foobar", @5, nil]; - expect(str).to(equal(@"foobar")); - expect(num).to(equal(@5)); - }); + expect(str).to(equal(@"foobar")); + expect(num).to(equal(@5)); + }); - qck_it(@"should fill in missing values with nil", ^{ - RACTupleUnpack(NSString *str, NSNumber *num) = [RACTuple tupleWithObjects:@"foobar", nil]; + qck_it(@"should fill in missing values with nil", ^{ + RACTupleUnpack(NSString *str, NSNumber *num) = [RACTuple tupleWithObjects:@"foobar", nil]; - expect(str).to(equal(@"foobar")); - expect(num).to(beNil()); - }); + expect(str).to(equal(@"foobar")); + expect(num).to(beNil()); + }); - qck_it(@"should skip any values not assigned to", ^{ - RACTupleUnpack(NSString *str, NSNumber *num) = [RACTuple tupleWithObjects:@"foobar", @5, RACUnit.defaultUnit, nil]; + qck_it(@"should skip any values not assigned to", ^{ + RACTupleUnpack(NSString *str, NSNumber *num) = [RACTuple tupleWithObjects:@"foobar", @5, RACUnit.defaultUnit, nil]; - expect(str).to(equal(@"foobar")); - expect(num).to(equal(@5)); - }); + expect(str).to(equal(@"foobar")); + expect(num).to(equal(@5)); + }); - qck_it(@"should keep an unpacked value alive when captured in a block", ^{ - __weak id weakPtr = nil; - id (^block)(void) = nil; + qck_it(@"should keep an unpacked value alive when captured in a block", ^{ + __weak id weakPtr = nil; + id (^block)(void) = nil; - @autoreleasepool { - RACTupleUnpack(NSString *str) = [RACTuple tupleWithObjects:[[NSMutableString alloc] init], nil]; + @autoreleasepool { + RACTupleUnpack(NSString *str) = [RACTuple tupleWithObjects:[[NSMutableString alloc] init], nil]; - weakPtr = str; - expect(weakPtr).notTo(beNil()); + weakPtr = str; + expect(weakPtr).notTo(beNil()); - block = [^{ - return str; - } copy]; - } + block = [^{ + return str; + } copy]; + } - expect(weakPtr).notTo(beNil()); - expect(block()).to(equal(weakPtr)); - }); + expect(weakPtr).notTo(beNil()); + expect(block()).to(equal(weakPtr)); + }); }); qck_describe(@"RACTuplePack", ^{ - qck_it(@"should pack a single value", ^{ - RACTuple *tuple = [RACTuple tupleWithObjects:RACUnit.defaultUnit, nil]; - expect(RACTuplePack(RACUnit.defaultUnit)).to(equal(tuple)); - }); - - qck_it(@"should translate nil", ^{ - RACTuple *tuple = [RACTuple tupleWithObjects:RACTupleNil.tupleNil, nil]; - expect(RACTuplePack(nil)).to(equal(tuple)); - }); - - qck_it(@"should pack multiple values", ^{ - NSString *string = @"foobar"; - NSNumber *number = @5; - RACTuple *tuple = [RACTuple tupleWithObjects:string, number, nil]; - expect(RACTuplePack(string, number)).to(equal(tuple)); - }); + qck_it(@"should pack a single value", ^{ + RACTuple *tuple = [RACTuple tupleWithObjects:RACUnit.defaultUnit, nil]; + expect(RACTuplePack(RACUnit.defaultUnit)).to(equal(tuple)); + }); + + qck_it(@"should translate nil", ^{ + RACTuple *tuple = [RACTuple tupleWithObjects:RACTupleNil.tupleNil, nil]; + expect(RACTuplePack(nil)).to(equal(tuple)); + }); + + qck_it(@"should pack multiple values", ^{ + NSString *string = @"foobar"; + NSNumber *number = @5; + RACTuple *tuple = [RACTuple tupleWithObjects:string, number, nil]; + expect(RACTuplePack(string, number)).to(equal(tuple)); + }); }); qck_describe(@"-tupleByAddingObject:", ^{ - __block RACTuple *tuple; - - qck_beforeEach(^{ - tuple = RACTuplePack(@"foo", nil, @"bar"); - }); - - qck_it(@"should add a non-nil object", ^{ - RACTuple *newTuple = [tuple tupleByAddingObject:@"buzz"]; - expect(@(newTuple.count)).to(equal(@4)); - expect(newTuple[0]).to(equal(@"foo")); - expect(newTuple[1]).to(beNil()); - expect(newTuple[2]).to(equal(@"bar")); - expect(newTuple[3]).to(equal(@"buzz")); - }); - - qck_it(@"should add nil", ^{ - RACTuple *newTuple = [tuple tupleByAddingObject:nil]; - expect(@(newTuple.count)).to(equal(@4)); - expect(newTuple[0]).to(equal(@"foo")); - expect(newTuple[1]).to(beNil()); - expect(newTuple[2]).to(equal(@"bar")); - expect(newTuple[3]).to(beNil()); - }); - - qck_it(@"should add NSNull", ^{ - RACTuple *newTuple = [tuple tupleByAddingObject:NSNull.null]; - expect(@(newTuple.count)).to(equal(@4)); - expect(newTuple[0]).to(equal(@"foo")); - expect(newTuple[1]).to(beNil()); - expect(newTuple[2]).to(equal(@"bar")); - expect(newTuple[3]).to(equal(NSNull.null)); - }); + __block RACTuple *tuple; + + qck_beforeEach(^{ + tuple = RACTuplePack(@"foo", nil, @"bar"); + }); + + qck_it(@"should add a non-nil object", ^{ + RACTuple *newTuple = [tuple tupleByAddingObject:@"buzz"]; + expect(@(newTuple.count)).to(equal(@4)); + expect(newTuple[0]).to(equal(@"foo")); + expect(newTuple[1]).to(beNil()); + expect(newTuple[2]).to(equal(@"bar")); + expect(newTuple[3]).to(equal(@"buzz")); + }); + + qck_it(@"should add nil", ^{ + RACTuple *newTuple = [tuple tupleByAddingObject:nil]; + expect(@(newTuple.count)).to(equal(@4)); + expect(newTuple[0]).to(equal(@"foo")); + expect(newTuple[1]).to(beNil()); + expect(newTuple[2]).to(equal(@"bar")); + expect(newTuple[3]).to(beNil()); + }); + + qck_it(@"should add NSNull", ^{ + RACTuple *newTuple = [tuple tupleByAddingObject:NSNull.null]; + expect(@(newTuple.count)).to(equal(@4)); + expect(newTuple[0]).to(equal(@"foo")); + expect(newTuple[1]).to(beNil()); + expect(newTuple[2]).to(equal(@"bar")); + expect(newTuple[3]).to(equal(NSNull.null)); + }); }); qck_describe(@"RACTuple subclasses", ^{ - qck_describe(@"equality to RACTuple", ^{ - qck_context(@"RACOneTuple", ^{ - qck_it(@"should be equal", ^{ - RACOneTuple *tupleSubclass = [RACOneTuple pack:@"foo"]; - RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[ @"foo" ]]; - expect(tupleSubclass).to(equal(tuple)); - expect(tuple).to(equal(tupleSubclass)); - }); - }); - - qck_context(@"RACTwoTuple", ^{ - qck_it(@"should be equal", ^{ - RACTwoTuple *tupleSubclass = [RACTwoTuple pack:@"foo" :@"bar"]; - RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[ @"foo", @"bar" ]]; - expect(tupleSubclass).to(equal(tuple)); - expect(tuple).to(equal(tupleSubclass)); - }); - }); - - qck_context(@"RACThreeTuple", ^{ - qck_it(@"should be equal", ^{ - RACThreeTuple *tupleSubclass = [RACThreeTuple pack:@"foo" :@"bar" :@"buzz"]; - RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[ @"foo", @"bar", @"buzz" ]]; - expect(tupleSubclass).to(equal(tuple)); - expect(tuple).to(equal(tupleSubclass)); - }); - }); - - qck_context(@"RACFourTuple", ^{ - qck_it(@"should be equal", ^{ - RACFourTuple *tupleSubclass = [RACFourTuple pack:@"foo" :@"bar" :@"buzz" :@"fizz"]; - RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[ @"foo", @"bar", @"buzz", @"fizz" ]]; - expect(tupleSubclass).to(equal(tuple)); - expect(tuple).to(equal(tupleSubclass)); - }); - }); - - qck_context(@"RACFiveTuple", ^{ - qck_it(@"should be equal", ^{ - RACFiveTuple *tupleSubclass = [RACFiveTuple pack:@"foo" :@"bar" :@"buzz" :@"fizz" :@"bizz"]; - RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[ @"foo", @"bar", @"buzz", @"fizz", @"bizz" ]]; - expect(tupleSubclass).to(equal(tuple)); - expect(tuple).to(equal(tupleSubclass)); - }); - }); - }); - - qck_describe(@"RACTuplePack", ^{ - qck_context(@"RACOneTuple", ^{ - qck_it(@"should be produced by packing", ^{ - expect(RACTuplePack(@"foo")).to(beAnInstanceOf(RACOneTuple.class)); - }); - }); - - qck_context(@"RACTwoTuple", ^{ - qck_it(@"should be produced by packing", ^{ - expect(RACTuplePack(@"foo", @"bar")).to(beAnInstanceOf(RACTwoTuple.class)); - }); - }); - - qck_context(@"RACThreeTuple", ^{ - qck_it(@"should be produced by packing", ^{ - expect(RACTuplePack(@"foo", @"bar", @"buzz")).to(beAnInstanceOf(RACThreeTuple.class)); - }); - }); - - qck_context(@"RACFourTuple", ^{ - qck_it(@"should be produced by packing", ^{ - expect(RACTuplePack(@"foo", @"bar", @"buzz", @"fizz")).to(beAnInstanceOf(RACFourTuple.class)); - }); - }); - - qck_context(@"RACFiveTuple", ^{ - qck_it(@"should be produced by packing", ^{ - expect(RACTuplePack(@"foo", @"bar", @"buzz", @"fizz", @"bizz")).to(beAnInstanceOf(RACFiveTuple.class)); - }); - }); - }); - - qck_describe(@"-tupleByAddingObject:", ^{ - qck_context(@"RACOneTuple", ^{ - qck_it(@"should produce a RACTwoTuple", ^{ - RACTwoTuple *tuple = [RACTuplePack(@"foo") tupleByAddingObject:@"buzz"]; - expect(tuple).to(beAnInstanceOf(RACTwoTuple.class)); - }); - }); - - qck_context(@"RACTwoTuple", ^{ - qck_it(@"should produce a RACThreeTuple", ^{ - RACThreeTuple *tuple = [RACTuplePack(@"foo", @"bar") tupleByAddingObject:@"buzz"]; - expect(tuple).to(beAnInstanceOf(RACThreeTuple.class)); - }); - }); - - qck_context(@"RACThreeTuple", ^{ - qck_it(@"should produce a RACFourTuple", ^{ - RACFourTuple *tuple = [RACTuplePack(@"foo", @"bar", @"buzz") tupleByAddingObject:@"fizz"]; - expect(tuple).to(beAnInstanceOf(RACFourTuple.class)); - }); - }); - - qck_context(@"RACFourTuple", ^{ - qck_it(@"should produce a RACFiveTuple", ^{ - RACFiveTuple *tuple = [RACTuplePack(@"foo", @"bar", @"buzz", @"fizz") tupleByAddingObject:@"bizz"]; - expect(tuple).to(beAnInstanceOf(RACFiveTuple.class)); - }); - }); - }); + qck_describe(@"equality to RACTuple", ^{ + qck_context(@"RACOneTuple", ^{ + qck_it(@"should be equal", ^{ + RACOneTuple *tupleSubclass = [RACOneTuple pack:@"foo"]; + RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[ @"foo" ]]; + expect(tupleSubclass).to(equal(tuple)); + expect(tuple).to(equal(tupleSubclass)); + }); + }); + + qck_context(@"RACTwoTuple", ^{ + qck_it(@"should be equal", ^{ + RACTwoTuple *tupleSubclass = [RACTwoTuple pack:@"foo" :@"bar"]; + RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[ @"foo", @"bar" ]]; + expect(tupleSubclass).to(equal(tuple)); + expect(tuple).to(equal(tupleSubclass)); + }); + }); + + qck_context(@"RACThreeTuple", ^{ + qck_it(@"should be equal", ^{ + RACThreeTuple *tupleSubclass = [RACThreeTuple pack:@"foo" :@"bar" :@"buzz"]; + RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[ @"foo", @"bar", @"buzz" ]]; + expect(tupleSubclass).to(equal(tuple)); + expect(tuple).to(equal(tupleSubclass)); + }); + }); + + qck_context(@"RACFourTuple", ^{ + qck_it(@"should be equal", ^{ + RACFourTuple *tupleSubclass = [RACFourTuple pack:@"foo" :@"bar" :@"buzz" :@"fizz"]; + RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[ @"foo", @"bar", @"buzz", @"fizz" ]]; + expect(tupleSubclass).to(equal(tuple)); + expect(tuple).to(equal(tupleSubclass)); + }); + }); + + qck_context(@"RACFiveTuple", ^{ + qck_it(@"should be equal", ^{ + RACFiveTuple *tupleSubclass = [RACFiveTuple pack:@"foo" :@"bar" :@"buzz" :@"fizz" :@"bizz"]; + RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[ @"foo", @"bar", @"buzz", @"fizz", @"bizz" ]]; + expect(tupleSubclass).to(equal(tuple)); + expect(tuple).to(equal(tupleSubclass)); + }); + }); + }); + + qck_describe(@"RACTuplePack", ^{ + qck_context(@"RACOneTuple", ^{ + qck_it(@"should be produced by packing", ^{ + expect(RACTuplePack(@"foo")).to(beAnInstanceOf(RACOneTuple.class)); + }); + }); + + qck_context(@"RACTwoTuple", ^{ + qck_it(@"should be produced by packing", ^{ + expect(RACTuplePack(@"foo", @"bar")).to(beAnInstanceOf(RACTwoTuple.class)); + }); + }); + + qck_context(@"RACThreeTuple", ^{ + qck_it(@"should be produced by packing", ^{ + expect(RACTuplePack(@"foo", @"bar", @"buzz")).to(beAnInstanceOf(RACThreeTuple.class)); + }); + }); + + qck_context(@"RACFourTuple", ^{ + qck_it(@"should be produced by packing", ^{ + expect(RACTuplePack(@"foo", @"bar", @"buzz", @"fizz")).to(beAnInstanceOf(RACFourTuple.class)); + }); + }); + + qck_context(@"RACFiveTuple", ^{ + qck_it(@"should be produced by packing", ^{ + expect(RACTuplePack(@"foo", @"bar", @"buzz", @"fizz", @"bizz")).to(beAnInstanceOf(RACFiveTuple.class)); + }); + }); + }); + + qck_describe(@"-tupleByAddingObject:", ^{ + qck_context(@"RACOneTuple", ^{ + qck_it(@"should produce a RACTwoTuple", ^{ + RACTwoTuple *tuple = [RACTuplePack(@"foo") tupleByAddingObject:@"buzz"]; + expect(tuple).to(beAnInstanceOf(RACTwoTuple.class)); + }); + }); + + qck_context(@"RACTwoTuple", ^{ + qck_it(@"should produce a RACThreeTuple", ^{ + RACThreeTuple *tuple = [RACTuplePack(@"foo", @"bar") tupleByAddingObject:@"buzz"]; + expect(tuple).to(beAnInstanceOf(RACThreeTuple.class)); + }); + }); + + qck_context(@"RACThreeTuple", ^{ + qck_it(@"should produce a RACFourTuple", ^{ + RACFourTuple *tuple = [RACTuplePack(@"foo", @"bar", @"buzz") tupleByAddingObject:@"fizz"]; + expect(tuple).to(beAnInstanceOf(RACFourTuple.class)); + }); + }); + + qck_context(@"RACFourTuple", ^{ + qck_it(@"should produce a RACFiveTuple", ^{ + RACFiveTuple *tuple = [RACTuplePack(@"foo", @"bar", @"buzz", @"fizz") tupleByAddingObject:@"bizz"]; + expect(tuple).to(beAnInstanceOf(RACFiveTuple.class)); + }); + }); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/UIActionSheetRACSupportSpec.m b/ReactiveObjCTests/UIActionSheetRACSupportSpec.m index d8a32b3f7..709e59c5d 100644 --- a/ReactiveObjCTests/UIActionSheetRACSupportSpec.m +++ b/ReactiveObjCTests/UIActionSheetRACSupportSpec.m @@ -16,24 +16,24 @@ QuickSpecBegin(UIActionSheetRACSupportSpec) qck_describe(@"-rac_buttonClickedSignal", ^{ - __block UIActionSheet *actionSheet; - - qck_beforeEach(^{ - actionSheet = [[UIActionSheet alloc] init]; - [actionSheet addButtonWithTitle:@"Button 0"]; - [actionSheet addButtonWithTitle:@"Button 1"]; - expect(actionSheet).notTo(beNil()); - }); - - qck_it(@"should send the index of the clicked button", ^{ - __block NSNumber *index = nil; - [actionSheet.rac_buttonClickedSignal subscribeNext:^(NSNumber *i) { - index = i; - }]; - - [actionSheet.delegate actionSheet:actionSheet clickedButtonAtIndex:1]; - expect(index).to(equal(@1)); - }); + __block UIActionSheet *actionSheet; + + qck_beforeEach(^{ + actionSheet = [[UIActionSheet alloc] init]; + [actionSheet addButtonWithTitle:@"Button 0"]; + [actionSheet addButtonWithTitle:@"Button 1"]; + expect(actionSheet).notTo(beNil()); + }); + + qck_it(@"should send the index of the clicked button", ^{ + __block NSNumber *index = nil; + [actionSheet.rac_buttonClickedSignal subscribeNext:^(NSNumber *i) { + index = i; + }]; + + [actionSheet.delegate actionSheet:actionSheet clickedButtonAtIndex:1]; + expect(index).to(equal(@1)); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/UIAlertViewRACSupportSpec.m b/ReactiveObjCTests/UIAlertViewRACSupportSpec.m index 6321094aa..9f5abe49e 100644 --- a/ReactiveObjCTests/UIAlertViewRACSupportSpec.m +++ b/ReactiveObjCTests/UIAlertViewRACSupportSpec.m @@ -16,32 +16,32 @@ QuickSpecBegin(UIAlertViewRACSupportSpec) qck_describe(@"UIAlertView", ^{ - __block UIAlertView *alertView; - - qck_beforeEach(^{ - alertView = [[UIAlertView alloc] initWithFrame:CGRectZero]; - expect(alertView).notTo(beNil()); - }); - - qck_it(@"sends the index of the clicked button to the buttonClickedSignal when a button is clicked", ^{ - __block NSInteger index = -1; - [alertView.rac_buttonClickedSignal subscribeNext:^(NSNumber *sentIndex) { - index = sentIndex.integerValue; - }]; - - [alertView.delegate alertView:alertView clickedButtonAtIndex:2]; - expect(@(index)).to(equal(@2)); - }); - - qck_it(@"sends the index of the appropriate button to the willDismissSignal when dismissed programatically", ^{ - __block NSInteger index = -1; - [alertView.rac_willDismissSignal subscribeNext:^(NSNumber *sentIndex) { - index = sentIndex.integerValue; - }]; - - [alertView.delegate alertView:alertView willDismissWithButtonIndex:2]; - expect(@(index)).to(equal(@2)); - }); + __block UIAlertView *alertView; + + qck_beforeEach(^{ + alertView = [[UIAlertView alloc] initWithFrame:CGRectZero]; + expect(alertView).notTo(beNil()); + }); + + qck_it(@"sends the index of the clicked button to the buttonClickedSignal when a button is clicked", ^{ + __block NSInteger index = -1; + [alertView.rac_buttonClickedSignal subscribeNext:^(NSNumber *sentIndex) { + index = sentIndex.integerValue; + }]; + + [alertView.delegate alertView:alertView clickedButtonAtIndex:2]; + expect(@(index)).to(equal(@2)); + }); + + qck_it(@"sends the index of the appropriate button to the willDismissSignal when dismissed programatically", ^{ + __block NSInteger index = -1; + [alertView.rac_willDismissSignal subscribeNext:^(NSNumber *sentIndex) { + index = sentIndex.integerValue; + }]; + + [alertView.delegate alertView:alertView willDismissWithButtonIndex:2]; + expect(@(index)).to(equal(@2)); + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/UIBarButtonItemRACSupportSpec.m b/ReactiveObjCTests/UIBarButtonItemRACSupportSpec.m index 7b5394089..cb40ee8a2 100644 --- a/ReactiveObjCTests/UIBarButtonItemRACSupportSpec.m +++ b/ReactiveObjCTests/UIBarButtonItemRACSupportSpec.m @@ -18,26 +18,26 @@ QuickSpecBegin(UIBarButtonItemRACSupportSpec) qck_describe(@"UIBarButtonItem", ^{ - __block UIBarButtonItem *button; - - qck_beforeEach(^{ - button = [[UIBarButtonItem alloc] init]; - expect(button).notTo(beNil()); - }); - - qck_itBehavesLike(RACControlCommandExamples, ^{ - return @{ - RACControlCommandExampleControl: button, - RACControlCommandExampleActivateBlock: ^(UIBarButtonItem *button) { - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[button.target methodSignatureForSelector:button.action]]; - invocation.selector = button.action; - - id target = button.target; - [invocation setArgument:&target atIndex:2]; - [invocation invokeWithTarget:target]; - } - }; - }); + __block UIBarButtonItem *button; + + qck_beforeEach(^{ + button = [[UIBarButtonItem alloc] init]; + expect(button).notTo(beNil()); + }); + + qck_itBehavesLike(RACControlCommandExamples, ^{ + return @{ + RACControlCommandExampleControl: button, + RACControlCommandExampleActivateBlock: ^(UIBarButtonItem *button) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[button.target methodSignatureForSelector:button.action]]; + invocation.selector = button.action; + + id target = button.target; + [invocation setArgument:&target atIndex:2]; + [invocation invokeWithTarget:target]; + } + }; + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/UIButtonRACSupportSpec.m b/ReactiveObjCTests/UIButtonRACSupportSpec.m index ba86798ef..2d7fa74a8 100644 --- a/ReactiveObjCTests/UIButtonRACSupportSpec.m +++ b/ReactiveObjCTests/UIButtonRACSupportSpec.m @@ -19,24 +19,24 @@ QuickSpecBegin(UIButtonRACSupportSpec) qck_describe(@"UIButton", ^{ - __block UIButton *button; - - qck_beforeEach(^{ - button = [RACTestUIButton button]; - expect(button).notTo(beNil()); - }); + __block UIButton *button; + + qck_beforeEach(^{ + button = [RACTestUIButton button]; + expect(button).notTo(beNil()); + }); - qck_itBehavesLike(RACControlCommandExamples, ^{ - return @{ - RACControlCommandExampleControl: button, - RACControlCommandExampleActivateBlock: ^(UIButton *button) { - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Warc-performSelector-leaks" - [button sendActionsForControlEvents:UIControlEventTouchUpInside]; - #pragma clang diagnostic pop - } - }; - }); + qck_itBehavesLike(RACControlCommandExamples, ^{ + return @{ + RACControlCommandExampleControl: button, + RACControlCommandExampleActivateBlock: ^(UIButton *button) { + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [button sendActionsForControlEvents:UIControlEventTouchUpInside]; + #pragma clang diagnostic pop + } + }; + }); }); QuickSpecEnd diff --git a/ReactiveObjCTests/UIImagePickerControllerRACSupportSpec.m b/ReactiveObjCTests/UIImagePickerControllerRACSupportSpec.m index 9ecc1536b..9a375522c 100644 --- a/ReactiveObjCTests/UIImagePickerControllerRACSupportSpec.m +++ b/ReactiveObjCTests/UIImagePickerControllerRACSupportSpec.m @@ -15,40 +15,40 @@ QuickSpecBegin(UIImagePickerControllerRACSupportSpec) qck_describe(@"UIImagePickerController", ^{ - __block UIImagePickerController *imagePicker; - - qck_beforeEach(^{ - imagePicker = [[UIImagePickerController alloc] init]; - expect(imagePicker).notTo(beNil()); - }); - - qck_it(@"sends the user info dictionary after confirmation", ^{ - __block NSDictionary *selectedImageUserInfo = nil; - [imagePicker.rac_imageSelectedSignal subscribeNext:^(NSDictionary *userInfo) { - selectedImageUserInfo = userInfo; - }]; - - NSDictionary *info = @{ - UIImagePickerControllerMediaType: @"public.image", - UIImagePickerControllerMediaMetadata: @{} - }; - [imagePicker.delegate imagePickerController:imagePicker didFinishPickingMediaWithInfo:info]; - expect(selectedImageUserInfo).to(equal(info)); - }); - - qck_it(@"cancels image picking process", ^{ - __block BOOL didSend = NO; - __block BOOL didComplete = NO; - [imagePicker.rac_imageSelectedSignal subscribeNext:^(NSDictionary *userInfo) { - didSend = YES; - } completed:^{ - didComplete = YES; - }]; - - [imagePicker.delegate imagePickerControllerDidCancel:imagePicker]; - expect(@(didSend)).to(beFalsy()); - expect(@(didComplete)).to(beTruthy()); - }); + __block UIImagePickerController *imagePicker; + + qck_beforeEach(^{ + imagePicker = [[UIImagePickerController alloc] init]; + expect(imagePicker).notTo(beNil()); + }); + + qck_it(@"sends the user info dictionary after confirmation", ^{ + __block NSDictionary *selectedImageUserInfo = nil; + [imagePicker.rac_imageSelectedSignal subscribeNext:^(NSDictionary *userInfo) { + selectedImageUserInfo = userInfo; + }]; + + NSDictionary *info = @{ + UIImagePickerControllerMediaType: @"public.image", + UIImagePickerControllerMediaMetadata: @{} + }; + [imagePicker.delegate imagePickerController:imagePicker didFinishPickingMediaWithInfo:info]; + expect(selectedImageUserInfo).to(equal(info)); + }); + + qck_it(@"cancels image picking process", ^{ + __block BOOL didSend = NO; + __block BOOL didComplete = NO; + [imagePicker.rac_imageSelectedSignal subscribeNext:^(NSDictionary *userInfo) { + didSend = YES; + } completed:^{ + didComplete = YES; + }]; + + [imagePicker.delegate imagePickerControllerDidCancel:imagePicker]; + expect(@(didSend)).to(beFalsy()); + expect(@(didComplete)).to(beTruthy()); + }); }); QuickSpecEnd