NSObjectRACPropertySubscribingExamples.m 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. //
  2. // NSObjectRACPropertySubscribingExamples.m
  3. // ReactiveCocoa
  4. //
  5. // Created by Josh Vera on 4/10/13.
  6. // Copyright (c) 2013 GitHub, Inc. All rights reserved.
  7. //
  8. #import <Quick/Quick.h>
  9. #import <Nimble/Nimble.h>
  10. #import "RACTestObject.h"
  11. #import "NSObjectRACPropertySubscribingExamples.h"
  12. #import <ReactiveCocoa/EXTScope.h>
  13. #import "NSObject+RACDeallocating.h"
  14. #import "NSObject+RACPropertySubscribing.h"
  15. #import "RACCompoundDisposable.h"
  16. #import "RACDisposable.h"
  17. #import "RACSignal.h"
  18. NSString * const RACPropertySubscribingExamples = @"RACPropertySubscribingExamples";
  19. NSString * const RACPropertySubscribingExamplesSetupBlock = @"RACPropertySubscribingExamplesSetupBlock";
  20. QuickConfigurationBegin(NSObjectRACPropertySubscribingExamples)
  21. + (void)configure:(Configuration *)configuration {
  22. sharedExamples(RACPropertySubscribingExamples, ^(QCKDSLSharedExampleContext exampleContext) {
  23. __block RACSignal *(^signalBlock)(RACTestObject *object, NSString *keyPath, id observer);
  24. qck_beforeEach(^{
  25. signalBlock = exampleContext()[RACPropertySubscribingExamplesSetupBlock];
  26. });
  27. qck_it(@"should send the current value once on subscription", ^{
  28. RACTestObject *object = [[RACTestObject alloc] init];
  29. RACSignal *signal = signalBlock(object, @keypath(object, objectValue), self);
  30. NSMutableArray *values = [NSMutableArray array];
  31. object.objectValue = @0;
  32. [signal subscribeNext:^(id x) {
  33. [values addObject:x];
  34. }];
  35. expect(values).to(equal((@[ @0 ])));
  36. });
  37. qck_it(@"should send the new value when it changes", ^{
  38. RACTestObject *object = [[RACTestObject alloc] init];
  39. RACSignal *signal = signalBlock(object, @keypath(object, objectValue), self);
  40. NSMutableArray *values = [NSMutableArray array];
  41. object.objectValue = @0;
  42. [signal subscribeNext:^(id x) {
  43. [values addObject:x];
  44. }];
  45. expect(values).to(equal((@[ @0 ])));
  46. object.objectValue = @1;
  47. expect(values).to(equal((@[ @0, @1 ])));
  48. });
  49. qck_it(@"should stop observing when disposed", ^{
  50. RACTestObject *object = [[RACTestObject alloc] init];
  51. RACSignal *signal = signalBlock(object, @keypath(object, objectValue), self);
  52. NSMutableArray *values = [NSMutableArray array];
  53. object.objectValue = @0;
  54. RACDisposable *disposable = [signal subscribeNext:^(id x) {
  55. [values addObject:x];
  56. }];
  57. object.objectValue = @1;
  58. NSArray *expected = @[ @0, @1 ];
  59. expect(values).to(equal(expected));
  60. [disposable dispose];
  61. object.objectValue = @2;
  62. expect(values).to(equal(expected));
  63. });
  64. qck_it(@"shouldn't send any more values after the observer is gone", ^{
  65. __block BOOL observerDealloced = NO;
  66. RACTestObject *object = [[RACTestObject alloc] init];
  67. NSMutableArray *values = [NSMutableArray array];
  68. @autoreleasepool {
  69. RACTestObject *observer __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init];
  70. [observer.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  71. observerDealloced = YES;
  72. }]];
  73. RACSignal *signal = signalBlock(object, @keypath(object, objectValue), observer);
  74. object.objectValue = @1;
  75. [signal subscribeNext:^(id x) {
  76. [values addObject:x];
  77. }];
  78. }
  79. expect(@(observerDealloced)).to(beTruthy());
  80. NSArray *expected = @[ @1 ];
  81. expect(values).to(equal(expected));
  82. object.objectValue = @2;
  83. expect(values).to(equal(expected));
  84. });
  85. qck_it(@"shouldn't keep either object alive unnaturally long", ^{
  86. __block BOOL objectDealloced = NO;
  87. __block BOOL scopeObjectDealloced = NO;
  88. @autoreleasepool {
  89. RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init];
  90. [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  91. objectDealloced = YES;
  92. }]];
  93. RACTestObject *scopeObject __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init];
  94. [scopeObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  95. scopeObjectDealloced = YES;
  96. }]];
  97. RACSignal *signal = signalBlock(object, @keypath(object, objectValue), scopeObject);
  98. [signal subscribeNext:^(id _) {
  99. }];
  100. }
  101. expect(@(objectDealloced)).to(beTruthy());
  102. expect(@(scopeObjectDealloced)).to(beTruthy());
  103. });
  104. qck_it(@"shouldn't keep the signal alive past the lifetime of the object", ^{
  105. __block BOOL objectDealloced = NO;
  106. __block BOOL signalDealloced = NO;
  107. @autoreleasepool {
  108. RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init];
  109. [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  110. objectDealloced = YES;
  111. }]];
  112. RACSignal *signal = [signalBlock(object, @keypath(object, objectValue), self) map:^(id value) {
  113. return value;
  114. }];
  115. [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  116. signalDealloced = YES;
  117. }]];
  118. [signal subscribeNext:^(id _) {
  119. }];
  120. }
  121. expect(@(signalDealloced)).toEventually(beTruthy());
  122. expect(@(objectDealloced)).to(beTruthy());
  123. });
  124. qck_it(@"shouldn't crash when the value is changed on a different queue", ^{
  125. __block id value;
  126. @autoreleasepool {
  127. RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init];
  128. RACSignal *signal = signalBlock(object, @keypath(object, objectValue), self);
  129. [signal subscribeNext:^(id x) {
  130. value = x;
  131. }];
  132. NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  133. [queue addOperationWithBlock:^{
  134. object.objectValue = @1;
  135. }];
  136. [queue waitUntilAllOperationsAreFinished];
  137. }
  138. expect(value).toEventually(equal(@1));
  139. });
  140. qck_describe(@"mutating collections", ^{
  141. __block RACTestObject *object;
  142. __block NSMutableOrderedSet *lastValue;
  143. __block NSMutableOrderedSet *proxySet;
  144. qck_beforeEach(^{
  145. object = [[RACTestObject alloc] init];
  146. object.objectValue = [NSMutableOrderedSet orderedSetWithObject:@1];
  147. NSString *keyPath = @keypath(object, objectValue);
  148. [signalBlock(object, keyPath, self) subscribeNext:^(NSMutableOrderedSet *x) {
  149. lastValue = x;
  150. }];
  151. proxySet = [object mutableOrderedSetValueForKey:keyPath];
  152. });
  153. qck_it(@"sends the newest object when inserting values into an observed object", ^{
  154. NSMutableOrderedSet *expected = [NSMutableOrderedSet orderedSetWithObjects: @1, @2, nil];
  155. [proxySet addObject:@2];
  156. expect(lastValue).to(equal(expected));
  157. });
  158. qck_it(@"sends the newest object when removing values in an observed object", ^{
  159. NSMutableOrderedSet *expected = [NSMutableOrderedSet orderedSet];
  160. [proxySet removeAllObjects];
  161. expect(lastValue).to(equal(expected));
  162. });
  163. qck_it(@"sends the newest object when replacing values in an observed object", ^{
  164. NSMutableOrderedSet *expected = [NSMutableOrderedSet orderedSetWithObjects: @2, nil];
  165. [proxySet replaceObjectAtIndex:0 withObject:@2];
  166. expect(lastValue).to(equal(expected));
  167. });
  168. });
  169. });
  170. }
  171. QuickConfigurationEnd