| // | |
| // RACSubscriberExamples.m | |
| // ReactiveCocoa | |
| // | |
| // Created by Justin Spahr-Summers on 2012-11-27. | |
| // Copyright (c) 2012 GitHub, Inc. All rights reserved. | |
| // | |
| #import "RACSubscriberExamples.h" | |
| #import "NSObject+RACDeallocating.h" | |
| #import "RACCompoundDisposable.h" | |
| #import "RACDisposable.h" | |
| #import "RACSubject.h" | |
| #import "RACSubscriber.h" | |
| NSString * const RACSubscriberExamples = @"RACSubscriberExamples"; | |
| NSString * const RACSubscriberExampleSubscriber = @"RACSubscriberExampleSubscriber"; | |
| NSString * const RACSubscriberExampleValuesReceivedBlock = @"RACSubscriberExampleValuesReceivedBlock"; | |
| NSString * const RACSubscriberExampleErrorReceivedBlock = @"RACSubscriberExampleErrorReceivedBlock"; | |
| NSString * const RACSubscriberExampleSuccessBlock = @"RACSubscriberExampleSuccessBlock"; | |
| SharedExampleGroupsBegin(RACSubscriberExamples) | |
| sharedExamplesFor(RACSubscriberExamples, ^(NSDictionary *data) { | |
| __block NSArray * (^valuesReceived)(void); | |
| __block NSError * (^errorReceived)(void); | |
| __block BOOL (^success)(void); | |
| __block id<RACSubscriber> subscriber; | |
| beforeEach(^{ | |
| valuesReceived = data[RACSubscriberExampleValuesReceivedBlock]; | |
| errorReceived = data[RACSubscriberExampleErrorReceivedBlock]; | |
| success = data[RACSubscriberExampleSuccessBlock]; | |
| subscriber = data[RACSubscriberExampleSubscriber]; | |
| expect(subscriber).notTo.beNil(); | |
| }); | |
| it(@"should accept a nil error", ^{ | |
| [subscriber sendError:nil]; | |
| expect(success()).to.beFalsy(); | |
| expect(errorReceived()).to.beNil(); | |
| expect(valuesReceived()).to.equal(@[]); | |
| }); | |
| describe(@"with values", ^{ | |
| __block NSSet *values; | |
| beforeEach(^{ | |
| NSMutableSet *mutableValues = [NSMutableSet set]; | |
| for (NSUInteger i = 0; i < 20; i++) { | |
| [mutableValues addObject:@(i)]; | |
| } | |
| values = [mutableValues copy]; | |
| }); | |
| 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); | |
| }); | |
| }); | |
| describe(@"multiple subscriptions", ^{ | |
| __block RACSubject *first; | |
| __block RACSubject *second; | |
| beforeEach(^{ | |
| first = [RACSubject subject]; | |
| [first subscribe:subscriber]; | |
| second = [RACSubject subject]; | |
| [second subscribe:subscriber]; | |
| }); | |
| 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); | |
| }); | |
| 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); | |
| }); | |
| 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); | |
| }); | |
| it(@"should dispose of all current subscriptions upon termination", ^{ | |
| __block BOOL firstDisposed = NO; | |
| RACSignal *firstDisposableSignal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { | |
| return [RACDisposable disposableWithBlock:^{ | |
| firstDisposed = YES; | |
| }]; | |
| }]; | |
| __block BOOL secondDisposed = NO; | |
| RACSignal *secondDisposableSignal = [RACSignal createSignal:^(id<RACSubscriber> 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(); | |
| }); | |
| it(@"should dispose of future subscriptions upon termination", ^{ | |
| __block BOOL disposed = NO; | |
| RACSignal *disposableSignal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { | |
| return [RACDisposable disposableWithBlock:^{ | |
| disposed = YES; | |
| }]; | |
| }]; | |
| [first sendCompleted]; | |
| expect(disposed).to.beFalsy(); | |
| [disposableSignal subscribe:subscriber]; | |
| expect(disposed).to.beTruthy(); | |
| }); | |
| }); | |
| describe(@"memory management", ^{ | |
| 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(); | |
| }); | |
| }); | |
| }); | |
| SharedExampleGroupsEnd |