RACKVOProxySpec.m 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. //
  2. // RACKVOProxySpec.m
  3. // ReactiveCocoa
  4. //
  5. // Created by Richard Speyer on 4/24/14.
  6. // Copyright (c) 2014 GitHub, Inc. All rights reserved.
  7. //
  8. #import <Quick/Quick.h>
  9. #import <Nimble/Nimble.h>
  10. #import "RACKVOProxy.h"
  11. #import "NSObject+RACKVOWrapper.h"
  12. #import "NSObject+RACPropertySubscribing.h"
  13. #import "RACSerialDisposable.h"
  14. #import "RACSignal+Operations.h"
  15. #import "RACScheduler.h"
  16. #import "RACSubject.h"
  17. @interface TestObject : NSObject {
  18. volatile int _testInt;
  19. }
  20. @property (assign, atomic) int testInt;
  21. @end
  22. @implementation TestObject
  23. - (int)testInt {
  24. return _testInt;
  25. }
  26. // Use manual KVO notifications to avoid any possible race conditions within the
  27. // automatic KVO implementation.
  28. - (void)setTestInt:(int)value {
  29. [self willChangeValueForKey:@keypath(self.testInt)];
  30. _testInt = value;
  31. [self didChangeValueForKey:@keypath(self.testInt)];
  32. }
  33. + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
  34. return NO;
  35. }
  36. @end
  37. QuickSpecBegin(RACKVOProxySpec)
  38. qck_describe(@"RACKVOProxy", ^{
  39. __block TestObject *testObject;
  40. __block dispatch_queue_t concurrentQueue;
  41. qck_beforeEach(^{
  42. testObject = [[TestObject alloc] init];
  43. concurrentQueue = dispatch_queue_create("org.reactivecocoa.ReactiveCocoa.RACKVOProxySpec.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
  44. });
  45. qck_afterEach(^{
  46. dispatch_barrier_sync(concurrentQueue, ^{
  47. testObject = nil;
  48. });
  49. });
  50. qck_describe(@"basic", ^{
  51. qck_it(@"should handle multiple observations on the same value", ^{
  52. __block int observedValue1 = 0;
  53. __block int observedValue2 = 0;
  54. [[[RACObserve(testObject, testInt)
  55. skip:1]
  56. take:1]
  57. subscribeNext:^(NSNumber *wrappedInt) {
  58. observedValue1 = wrappedInt.intValue;
  59. }];
  60. [[[RACObserve(testObject, testInt)
  61. skip:1]
  62. take:1]
  63. subscribeNext:^(NSNumber *wrappedInt) {
  64. observedValue2 = wrappedInt.intValue;
  65. }];
  66. testObject.testInt = 2;
  67. expect(@(observedValue1)).toEventually(equal(@2));
  68. expect(@(observedValue2)).toEventually(equal(@2));
  69. });
  70. qck_it(@"can remove individual observation", ^{
  71. __block int observedValue1 = 0;
  72. __block int observedValue2 = 0;
  73. RACDisposable *disposable1 = [RACObserve(testObject, testInt) subscribeNext:^(NSNumber *wrappedInt) {
  74. observedValue1 = wrappedInt.intValue;
  75. }];
  76. [RACObserve(testObject, testInt) subscribeNext:^(NSNumber *wrappedInt) {
  77. observedValue2 = wrappedInt.intValue;
  78. }];
  79. testObject.testInt = 2;
  80. expect(@(observedValue1)).toEventually(equal(@2));
  81. expect(@(observedValue2)).toEventually(equal(@2));
  82. [disposable1 dispose];
  83. testObject.testInt = 3;
  84. expect(@(observedValue2)).toEventually(equal(@3));
  85. expect(@(observedValue1)).to(equal(@2));
  86. });
  87. });
  88. qck_describe(@"async", ^{
  89. qck_it(@"should handle changes being made on another queue", ^{
  90. __block int observedValue = 0;
  91. [[[RACObserve(testObject, testInt)
  92. skip:1]
  93. take:1]
  94. subscribeNext:^(NSNumber *wrappedInt) {
  95. observedValue = wrappedInt.intValue;
  96. }];
  97. dispatch_async(concurrentQueue, ^{
  98. testObject.testInt = 2;
  99. });
  100. dispatch_barrier_sync(concurrentQueue, ^{});
  101. expect(@(observedValue)).toEventually(equal(@2));
  102. });
  103. qck_it(@"should handle changes being made on another queue using deliverOn", ^{
  104. __block int observedValue = 0;
  105. [[[[RACObserve(testObject, testInt)
  106. skip:1]
  107. take:1]
  108. deliverOn:[RACScheduler mainThreadScheduler]]
  109. subscribeNext:^(NSNumber *wrappedInt) {
  110. observedValue = wrappedInt.intValue;
  111. }];
  112. dispatch_async(concurrentQueue, ^{
  113. testObject.testInt = 2;
  114. });
  115. dispatch_barrier_sync(concurrentQueue, ^{});
  116. expect(@(observedValue)).toEventually(equal(@2));
  117. });
  118. qck_it(@"async disposal of target", ^{
  119. __block int observedValue;
  120. [[RACObserve(testObject, testInt)
  121. deliverOn:RACScheduler.mainThreadScheduler]
  122. subscribeNext:^(NSNumber *wrappedInt) {
  123. observedValue = wrappedInt.intValue;
  124. }];
  125. dispatch_async(concurrentQueue, ^{
  126. testObject.testInt = 2;
  127. testObject = nil;
  128. });
  129. dispatch_barrier_sync(concurrentQueue, ^{});
  130. expect(@(observedValue)).toEventually(equal(@2));
  131. });
  132. });
  133. qck_describe(@"stress", ^{
  134. static const size_t numIterations = 5000;
  135. __block dispatch_queue_t iterationQueue;
  136. beforeEach(^{
  137. iterationQueue = dispatch_queue_create("org.reactivecocoa.ReactiveCocoa.RACKVOProxySpec.iterationQueue", DISPATCH_QUEUE_CONCURRENT);
  138. });
  139. // ReactiveCocoa/ReactiveCocoa#1122
  140. qck_it(@"async disposal of observer", ^{
  141. RACSerialDisposable *disposable = [[RACSerialDisposable alloc] init];
  142. dispatch_apply(numIterations, iterationQueue, ^(size_t index) {
  143. RACDisposable *newDisposable = [RACObserve(testObject, testInt) subscribeCompleted:^{}];
  144. [[disposable swapInDisposable:newDisposable] dispose];
  145. dispatch_async(concurrentQueue, ^{
  146. testObject.testInt = (int)index;
  147. });
  148. });
  149. dispatch_barrier_sync(iterationQueue, ^{
  150. [disposable dispose];
  151. });
  152. });
  153. qck_it(@"async disposal of signal with in-flight changes", ^{
  154. RACSubject *teardown = [RACSubject subject];
  155. RACSignal *isEvenSignal = [[[[RACObserve(testObject, testInt)
  156. map:^(NSNumber *wrappedInt) {
  157. return @((wrappedInt.intValue % 2) == 0);
  158. }]
  159. deliverOn:RACScheduler.mainThreadScheduler]
  160. takeUntil:teardown]
  161. replayLast];
  162. dispatch_apply(numIterations, iterationQueue, ^(size_t index) {
  163. testObject.testInt = (int)index;
  164. });
  165. dispatch_barrier_async(iterationQueue, ^{
  166. [teardown sendNext:nil];
  167. });
  168. expect(@([isEvenSignal asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy());
  169. });
  170. });
  171. });
  172. QuickSpecEnd