RACKVOWrapperSpec.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. //
  2. // RACKVOWrapperSpec.m
  3. // ReactiveCocoa
  4. //
  5. // Created by Justin Spahr-Summers on 2012-08-07.
  6. // Copyright (c) 2012 GitHub, Inc. All rights reserved.
  7. //
  8. #import <Quick/Quick.h>
  9. #import <Nimble/Nimble.h>
  10. #import "NSObject+RACKVOWrapper.h"
  11. #import <ReactiveCocoa/EXTKeyPathCoding.h>
  12. #import "NSObject+RACDeallocating.h"
  13. #import "RACCompoundDisposable.h"
  14. #import "RACDisposable.h"
  15. #import "RACKVOTrampoline.h"
  16. #import "RACTestObject.h"
  17. @interface RACTestOperation : NSOperation
  18. @end
  19. // The name of the examples.
  20. static NSString * const RACKVOWrapperExamples = @"RACKVOWrapperExamples";
  21. // A block that returns an object to observe in the examples.
  22. static NSString * const RACKVOWrapperExamplesTargetBlock = @"RACKVOWrapperExamplesTargetBlock";
  23. // The key path to observe in the examples.
  24. //
  25. // The key path must have at least one weak property in it.
  26. static NSString * const RACKVOWrapperExamplesKeyPath = @"RACKVOWrapperExamplesKeyPath";
  27. // A block that changes the value of a weak property in the observed key path.
  28. // The block is passed the object the example is observing and the new value the
  29. // weak property should be changed to(
  30. static NSString * const RACKVOWrapperExamplesChangeBlock = @"RACKVOWrapperExamplesChangeBlock";
  31. // A block that returns a valid value for the weak property changed by
  32. // RACKVOWrapperExamplesChangeBlock. The value must deallocate
  33. // normally.
  34. static NSString * const RACKVOWrapperExamplesValueBlock = @"RACKVOWrapperExamplesValueBlock";
  35. // Whether RACKVOWrapperExamplesChangeBlock changes the value
  36. // of the last key path component in the key path directly.
  37. static NSString * const RACKVOWrapperExamplesChangesValueDirectly = @"RACKVOWrapperExamplesChangesValueDirectly";
  38. // The name of the examples.
  39. static NSString * const RACKVOWrapperCollectionExamples = @"RACKVOWrapperCollectionExamples";
  40. // A block that returns an object to observe in the examples.
  41. static NSString * const RACKVOWrapperCollectionExamplesTargetBlock = @"RACKVOWrapperCollectionExamplesTargetBlock";
  42. // The key path to observe in the examples.
  43. //
  44. // Must identify a property of type NSOrderedSet.
  45. static NSString * const RACKVOWrapperCollectionExamplesKeyPath = @"RACKVOWrapperCollectionExamplesKeyPath";
  46. QuickConfigurationBegin(RACKVOWrapperExampleGroups)
  47. + (void)configure:(Configuration *)configuration {
  48. sharedExamples(RACKVOWrapperExamples, ^(QCKDSLSharedExampleContext exampleContext) {
  49. __block NSObject *target = nil;
  50. __block NSString *keyPath = nil;
  51. __block void (^changeBlock)(NSObject *, id) = nil;
  52. __block id (^valueBlock)(void) = nil;
  53. __block BOOL changesValueDirectly = NO;
  54. __block NSUInteger priorCallCount = 0;
  55. __block NSUInteger posteriorCallCount = 0;
  56. __block BOOL priorTriggeredByLastKeyPathComponent = NO;
  57. __block BOOL posteriorTriggeredByLastKeyPathComponent = NO;
  58. __block BOOL posteriorTriggeredByDeallocation = NO;
  59. __block void (^callbackBlock)(id, NSDictionary *, BOOL, BOOL) = nil;
  60. qck_beforeEach(^{
  61. NSObject * (^targetBlock)(void) = exampleContext()[RACKVOWrapperExamplesTargetBlock];
  62. target = targetBlock();
  63. keyPath = exampleContext()[RACKVOWrapperExamplesKeyPath];
  64. changeBlock = exampleContext()[RACKVOWrapperExamplesChangeBlock];
  65. valueBlock = exampleContext()[RACKVOWrapperExamplesValueBlock];
  66. changesValueDirectly = [exampleContext()[RACKVOWrapperExamplesChangesValueDirectly] boolValue];
  67. priorCallCount = 0;
  68. posteriorCallCount = 0;
  69. callbackBlock = [^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  70. if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) {
  71. priorTriggeredByLastKeyPathComponent = affectedOnlyLastComponent;
  72. ++priorCallCount;
  73. return;
  74. }
  75. posteriorTriggeredByLastKeyPathComponent = affectedOnlyLastComponent;
  76. posteriorTriggeredByDeallocation = causedByDealloc;
  77. ++posteriorCallCount;
  78. } copy];
  79. });
  80. qck_afterEach(^{
  81. target = nil;
  82. keyPath = nil;
  83. changeBlock = nil;
  84. valueBlock = nil;
  85. changesValueDirectly = NO;
  86. callbackBlock = nil;
  87. });
  88. qck_it(@"should not call the callback block on add if called without NSKeyValueObservingOptionInitial", ^{
  89. [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock];
  90. expect(@(priorCallCount)).to(equal(@0));
  91. expect(@(posteriorCallCount)).to(equal(@0));
  92. });
  93. qck_it(@"should call the callback block on add if called with NSKeyValueObservingOptionInitial", ^{
  94. [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior | NSKeyValueObservingOptionInitial observer:nil block:callbackBlock];
  95. expect(@(priorCallCount)).to(equal(@0));
  96. expect(@(posteriorCallCount)).to(equal(@1));
  97. });
  98. qck_it(@"should call the callback block twice per change, once prior and once posterior", ^{
  99. [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock];
  100. priorCallCount = 0;
  101. posteriorCallCount = 0;
  102. id value1 = valueBlock();
  103. changeBlock(target, value1);
  104. expect(@(priorCallCount)).to(equal(@1));
  105. expect(@(posteriorCallCount)).to(equal(@1));
  106. expect(@(priorTriggeredByLastKeyPathComponent)).to(equal(@(changesValueDirectly)));
  107. expect(@(posteriorTriggeredByLastKeyPathComponent)).to(equal(@(changesValueDirectly)));
  108. expect(@(posteriorTriggeredByDeallocation)).to(beFalsy());
  109. id value2 = valueBlock();
  110. changeBlock(target, value2);
  111. expect(@(priorCallCount)).to(equal(@2));
  112. expect(@(posteriorCallCount)).to(equal(@2));
  113. expect(@(priorTriggeredByLastKeyPathComponent)).to(equal(@(changesValueDirectly)));
  114. expect(@(posteriorTriggeredByLastKeyPathComponent)).to(equal(@(changesValueDirectly)));
  115. expect(@(posteriorTriggeredByDeallocation)).to(beFalsy());
  116. });
  117. qck_it(@"should call the callback block with NSKeyValueChangeNotificationIsPriorKey set before the value is changed, and not set after the value is changed", ^{
  118. __block BOOL priorCalled = NO;
  119. __block BOOL posteriorCalled = NO;
  120. __block id priorValue = nil;
  121. __block id posteriorValue = nil;
  122. id value1 = valueBlock();
  123. changeBlock(target, value1);
  124. id oldValue = [target valueForKeyPath:keyPath];
  125. [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  126. if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) {
  127. priorCalled = YES;
  128. priorValue = value;
  129. expect(@(posteriorCalled)).to(beFalsy());
  130. return;
  131. }
  132. posteriorCalled = YES;
  133. posteriorValue = value;
  134. expect(@(priorCalled)).to(beTruthy());
  135. }];
  136. id value2 = valueBlock();
  137. changeBlock(target, value2);
  138. id newValue = [target valueForKeyPath:keyPath];
  139. expect(@(priorCalled)).to(beTruthy());
  140. expect(priorValue).to(equal(oldValue));
  141. expect(@(posteriorCalled)).to(beTruthy());
  142. expect(posteriorValue).to(equal(newValue));
  143. });
  144. qck_it(@"should not call the callback block after it's been disposed", ^{
  145. RACDisposable *disposable = [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock];
  146. priorCallCount = 0;
  147. posteriorCallCount = 0;
  148. [disposable dispose];
  149. expect(@(priorCallCount)).to(equal(@0));
  150. expect(@(posteriorCallCount)).to(equal(@0));
  151. id value = valueBlock();
  152. changeBlock(target, value);
  153. expect(@(priorCallCount)).to(equal(@0));
  154. expect(@(posteriorCallCount)).to(equal(@0));
  155. });
  156. qck_it(@"should call the callback block only once with NSKeyValueChangeNotificationIsPriorKey not set when the value is deallocated", ^{
  157. __block BOOL valueDidDealloc = NO;
  158. [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock];
  159. @autoreleasepool {
  160. NSObject *value __attribute__((objc_precise_lifetime)) = valueBlock();
  161. [value.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  162. valueDidDealloc = YES;
  163. }]];
  164. changeBlock(target, value);
  165. priorCallCount = 0;
  166. posteriorCallCount = 0;
  167. }
  168. expect(@(valueDidDealloc)).to(beTruthy());
  169. expect(@(priorCallCount)).to(equal(@0));
  170. expect(@(posteriorCallCount)).to(equal(@1));
  171. expect(@(posteriorTriggeredByDeallocation)).to(beTruthy());
  172. });
  173. });
  174. qck_sharedExamples(RACKVOWrapperCollectionExamples, ^(QCKDSLSharedExampleContext exampleContext) {
  175. __block NSObject *target = nil;
  176. __block NSString *keyPath = nil;
  177. __block NSMutableOrderedSet *mutableKeyPathProxy = nil;
  178. __block void (^callbackBlock)(id, NSDictionary *, BOOL, BOOL) = nil;
  179. __block id priorValue = nil;
  180. __block id posteriorValue = nil;
  181. __block NSDictionary *priorChange = nil;
  182. __block NSDictionary *posteriorChange = nil;
  183. qck_beforeEach(^{
  184. NSObject * (^targetBlock)(void) = exampleContext()[RACKVOWrapperCollectionExamplesTargetBlock];
  185. target = targetBlock();
  186. keyPath = exampleContext()[RACKVOWrapperCollectionExamplesKeyPath];
  187. callbackBlock = [^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  188. if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) {
  189. priorValue = value;
  190. priorChange = change;
  191. return;
  192. }
  193. posteriorValue = value;
  194. posteriorChange = change;
  195. } copy];
  196. [target setValue:[NSOrderedSet orderedSetWithObject:@0] forKeyPath:keyPath];
  197. [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior observer:nil block:callbackBlock];
  198. mutableKeyPathProxy = [target mutableOrderedSetValueForKeyPath:keyPath];
  199. });
  200. qck_afterEach(^{
  201. target = nil;
  202. keyPath = nil;
  203. callbackBlock = nil;
  204. priorValue = nil;
  205. priorChange = nil;
  206. posteriorValue = nil;
  207. posteriorChange = nil;
  208. });
  209. qck_it(@"should support inserting elements into ordered collections", ^{
  210. [mutableKeyPathProxy insertObject:@1 atIndex:0];
  211. expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]]));
  212. expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:(@[ @1, @0 ])]));
  213. expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeInsertion)));
  214. expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeInsertion)));
  215. expect(priorChange[NSKeyValueChangeOldKey]).to(beNil());
  216. expect(posteriorChange[NSKeyValueChangeNewKey]).to(equal(@[ @1 ]));
  217. expect(priorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0]));
  218. expect(posteriorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0]));
  219. });
  220. qck_it(@"should support removing elements from ordered collections", ^{
  221. [mutableKeyPathProxy removeObjectAtIndex:0];
  222. expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]]));
  223. expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:@[]]));
  224. expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeRemoval)));
  225. expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeRemoval)));
  226. expect(priorChange[NSKeyValueChangeOldKey]).to(equal(@[ @0 ]));
  227. expect(posteriorChange[NSKeyValueChangeNewKey]).to(beNil());
  228. expect(priorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0]));
  229. expect(posteriorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0]));
  230. });
  231. qck_it(@"should support replacing elements in ordered collections", ^{
  232. [mutableKeyPathProxy replaceObjectAtIndex:0 withObject:@1];
  233. expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]]));
  234. expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @1 ]]));
  235. expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeReplacement)));
  236. expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeReplacement)));
  237. expect(priorChange[NSKeyValueChangeOldKey]).to(equal(@[ @0 ]));
  238. expect(posteriorChange[NSKeyValueChangeNewKey]).to(equal(@[ @1 ]));
  239. expect(priorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0]));
  240. expect(posteriorChange[NSKeyValueChangeIndexesKey]).to(equal([NSIndexSet indexSetWithIndex:0]));
  241. });
  242. qck_it(@"should support adding elements to unordered collections", ^{
  243. [mutableKeyPathProxy unionOrderedSet:[NSOrderedSet orderedSetWithObject:@1]];
  244. expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]]));
  245. expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:(@[ @0, @1 ])]));
  246. expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeInsertion)));
  247. expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeInsertion)));
  248. expect(priorChange[NSKeyValueChangeOldKey]).to(beNil());
  249. expect(posteriorChange[NSKeyValueChangeNewKey]).to(equal(@[ @1 ]));
  250. });
  251. qck_it(@"should support removing elements from unordered collections", ^{
  252. [mutableKeyPathProxy minusOrderedSet:[NSOrderedSet orderedSetWithObject:@0]];
  253. expect(priorValue).to(equal([NSOrderedSet orderedSetWithArray:@[ @0 ]]));
  254. expect(posteriorValue).to(equal([NSOrderedSet orderedSetWithArray:@[]]));
  255. expect(priorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeRemoval)));
  256. expect(posteriorChange[NSKeyValueChangeKindKey]).to(equal(@(NSKeyValueChangeRemoval)));
  257. expect(priorChange[NSKeyValueChangeOldKey]).to(equal(@[ @0 ]));
  258. expect(posteriorChange[NSKeyValueChangeNewKey]).to(beNil());
  259. });
  260. });
  261. }
  262. QuickConfigurationEnd
  263. QuickSpecBegin(RACKVOWrapperSpec)
  264. qck_describe(@"-rac_observeKeyPath:options:observer:block:", ^{
  265. qck_describe(@"on simple keys", ^{
  266. NSObject * (^targetBlock)(void) = ^{
  267. return [[RACTestObject alloc] init];
  268. };
  269. void (^changeBlock)(RACTestObject *, id) = ^(RACTestObject *target, id value) {
  270. target.weakTestObjectValue = value;
  271. };
  272. id (^valueBlock)(void) = ^{
  273. return [[RACTestObject alloc] init];
  274. };
  275. qck_itBehavesLike(RACKVOWrapperExamples, ^{
  276. return @{
  277. RACKVOWrapperExamplesTargetBlock: targetBlock,
  278. RACKVOWrapperExamplesKeyPath: @keypath(RACTestObject.new, weakTestObjectValue),
  279. RACKVOWrapperExamplesChangeBlock: changeBlock,
  280. RACKVOWrapperExamplesValueBlock: valueBlock,
  281. RACKVOWrapperExamplesChangesValueDirectly: @YES
  282. };
  283. });
  284. qck_itBehavesLike(RACKVOWrapperCollectionExamples, ^{
  285. return @{
  286. RACKVOWrapperCollectionExamplesTargetBlock: targetBlock,
  287. RACKVOWrapperCollectionExamplesKeyPath: @keypath(RACTestObject.new, orderedSetValue)
  288. };
  289. });
  290. });
  291. qck_describe(@"on composite key paths'", ^{
  292. qck_describe(@"last key path components", ^{
  293. NSObject *(^targetBlock)(void) = ^{
  294. RACTestObject *object = [[RACTestObject alloc] init];
  295. object.strongTestObjectValue = [[RACTestObject alloc] init];
  296. return object;
  297. };
  298. void (^changeBlock)(RACTestObject *, id) = ^(RACTestObject *target, id value) {
  299. target.strongTestObjectValue.weakTestObjectValue = value;
  300. };
  301. id (^valueBlock)(void) = ^{
  302. return [[RACTestObject alloc] init];
  303. };
  304. qck_itBehavesLike(RACKVOWrapperExamples, ^{
  305. return @{
  306. RACKVOWrapperExamplesTargetBlock: targetBlock,
  307. RACKVOWrapperExamplesKeyPath: @keypath(RACTestObject.new, strongTestObjectValue.weakTestObjectValue),
  308. RACKVOWrapperExamplesChangeBlock: changeBlock,
  309. RACKVOWrapperExamplesValueBlock: valueBlock,
  310. RACKVOWrapperExamplesChangesValueDirectly: @YES
  311. };
  312. });
  313. qck_itBehavesLike(RACKVOWrapperCollectionExamples, ^{
  314. return @{
  315. RACKVOWrapperCollectionExamplesTargetBlock: targetBlock,
  316. RACKVOWrapperCollectionExamplesKeyPath: @keypath(RACTestObject.new, strongTestObjectValue.orderedSetValue)
  317. };
  318. });
  319. });
  320. qck_describe(@"intermediate key path components", ^{
  321. NSObject *(^targetBlock)(void) = ^{
  322. return [[RACTestObject alloc] init];
  323. };
  324. void (^changeBlock)(RACTestObject *, id) = ^(RACTestObject *target, id value) {
  325. target.weakTestObjectValue = value;
  326. };
  327. id (^valueBlock)(void) = ^{
  328. RACTestObject *object = [[RACTestObject alloc] init];
  329. object.strongTestObjectValue = [[RACTestObject alloc] init];
  330. return object;
  331. };
  332. qck_itBehavesLike(RACKVOWrapperExamples, ^{
  333. return @{
  334. RACKVOWrapperExamplesTargetBlock: targetBlock,
  335. RACKVOWrapperExamplesKeyPath: @keypath([[RACTestObject alloc] init], weakTestObjectValue.strongTestObjectValue),
  336. RACKVOWrapperExamplesChangeBlock: changeBlock,
  337. RACKVOWrapperExamplesValueBlock: valueBlock,
  338. RACKVOWrapperExamplesChangesValueDirectly: @NO
  339. };
  340. });
  341. });
  342. qck_it(@"should not notice deallocation of the object returned by a dynamic final property", ^{
  343. RACTestObject *object = [[RACTestObject alloc] init];
  344. __block id lastValue = nil;
  345. @autoreleasepool {
  346. [object rac_observeKeyPath:@keypath(object.dynamicObjectProperty) options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  347. lastValue = value;
  348. }];
  349. expect(lastValue).to(beAKindOf(RACTestObject.class));
  350. }
  351. expect(lastValue).to(beAKindOf(RACTestObject.class));
  352. });
  353. qck_it(@"should not notice deallocation of the object returned by a dynamic intermediate property", ^{
  354. RACTestObject *object = [[RACTestObject alloc] init];
  355. __block id lastValue = nil;
  356. @autoreleasepool {
  357. [object rac_observeKeyPath:@keypath(object.dynamicObjectProperty.integerValue) options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  358. lastValue = value;
  359. }];
  360. expect(lastValue).to(equal(@42));
  361. }
  362. expect(lastValue).to(equal(@42));
  363. });
  364. qck_it(@"should not notice deallocation of the object returned by a dynamic method", ^{
  365. RACTestObject *object = [[RACTestObject alloc] init];
  366. __block id lastValue = nil;
  367. @autoreleasepool {
  368. [object rac_observeKeyPath:@keypath(object.dynamicObjectMethod) options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  369. lastValue = value;
  370. }];
  371. expect(lastValue).to(beAKindOf(RACTestObject.class));
  372. }
  373. expect(lastValue).to(beAKindOf(RACTestObject.class));
  374. });
  375. });
  376. qck_it(@"should not call the callback block when the value is the observer", ^{
  377. __block BOOL observerDisposed = NO;
  378. __block BOOL observerDeallocationTriggeredChange = NO;
  379. __block BOOL targetDisposed = NO;
  380. __block BOOL targetDeallocationTriggeredChange = NO;
  381. @autoreleasepool {
  382. RACTestObject *observer __attribute__((objc_precise_lifetime)) = [RACTestObject new];
  383. [observer.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  384. observerDisposed = YES;
  385. }]];
  386. RACTestObject *target __attribute__((objc_precise_lifetime)) = [RACTestObject new];
  387. [target.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  388. targetDisposed = YES;
  389. }]];
  390. observer.weakTestObjectValue = observer;
  391. target.weakTestObjectValue = target;
  392. // These observations can only result in dealloc triggered callbacks.
  393. [observer rac_observeKeyPath:@keypath(target.weakTestObjectValue) options:0 observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  394. observerDeallocationTriggeredChange = YES;
  395. }];
  396. [target rac_observeKeyPath:@keypath(target.weakTestObjectValue) options:0 observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  397. targetDeallocationTriggeredChange = YES;
  398. }];
  399. }
  400. expect(@(observerDisposed)).to(beTruthy());
  401. expect(@(observerDeallocationTriggeredChange)).to(beFalsy());
  402. expect(@(targetDisposed)).to(beTruthy());
  403. expect(@(targetDeallocationTriggeredChange)).to(beTruthy());
  404. });
  405. qck_it(@"should call the callback block for deallocation of the initial value of a single-key key path", ^{
  406. RACTestObject *target = [RACTestObject new];
  407. __block BOOL objectDisposed = NO;
  408. __block BOOL objectDeallocationTriggeredChange = NO;
  409. @autoreleasepool {
  410. RACTestObject *object __attribute__((objc_precise_lifetime)) = [RACTestObject new];
  411. target.weakTestObjectValue = object;
  412. [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  413. objectDisposed = YES;
  414. }]];
  415. [target rac_observeKeyPath:@keypath(target.weakTestObjectValue) options:0 observer:target block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  416. objectDeallocationTriggeredChange = YES;
  417. }];
  418. }
  419. expect(@(objectDisposed)).to(beTruthy());
  420. expect(@(objectDeallocationTriggeredChange)).to(beTruthy());
  421. });
  422. qck_it(@"should call the callback block for deallocation of an object conforming to protocol property", ^{
  423. RACTestObject *target = [RACTestObject new];
  424. __block BOOL objectDisposed = NO;
  425. __block BOOL objectDeallocationTriggeredChange = NO;
  426. @autoreleasepool {
  427. RACTestObject *object __attribute__((objc_precise_lifetime)) = [RACTestObject new];
  428. target.weakObjectWithProtocol = object;
  429. [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  430. objectDisposed = YES;
  431. }]];
  432. [target rac_observeKeyPath:@keypath(target.weakObjectWithProtocol) options:0 observer:target block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  433. objectDeallocationTriggeredChange = YES;
  434. }];
  435. }
  436. expect(@(objectDisposed)).to(beTruthy());
  437. expect(@(objectDeallocationTriggeredChange)).to(beTruthy());
  438. });
  439. });
  440. qck_describe(@"rac_addObserver:forKeyPath:options:block:", ^{
  441. qck_it(@"should add and remove an observer", ^{
  442. NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{}];
  443. expect(operation).notTo(beNil());
  444. __block BOOL notified = NO;
  445. RACDisposable *disposable = [operation rac_observeKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  446. expect([change objectForKey:NSKeyValueChangeNewKey]).to(equal(@YES));
  447. expect(@(notified)).to(beFalsy());
  448. notified = YES;
  449. }];
  450. expect(disposable).notTo(beNil());
  451. [operation start];
  452. [operation waitUntilFinished];
  453. expect(@(notified)).toEventually(beTruthy());
  454. });
  455. qck_it(@"should accept a nil observer", ^{
  456. NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{}];
  457. RACDisposable *disposable = [operation rac_observeKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {}];
  458. expect(disposable).notTo(beNil());
  459. });
  460. qck_context(@"automatically stops KVO on subclasses when the target deallocates", ^{
  461. void (^testKVOOnSubclass)(Class targetClass, id observer) = ^(Class targetClass, id observer) {
  462. __weak id weakTarget = nil;
  463. __weak id identifier = nil;
  464. @autoreleasepool {
  465. // Create an observable target that we control the memory management of.
  466. CFTypeRef target = CFBridgingRetain([[targetClass alloc] init]);
  467. expect((__bridge id)target).notTo(beNil());
  468. weakTarget = (__bridge id)target;
  469. expect(weakTarget).notTo(beNil());
  470. identifier = [(__bridge id)target rac_observeKeyPath:@"isFinished" options:0 observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {}];
  471. expect(identifier).notTo(beNil());
  472. CFRelease(target);
  473. }
  474. expect(weakTarget).to(beNil());
  475. expect(identifier).to(beNil());
  476. };
  477. qck_it(@"stops KVO on NSObject subclasses", ^{
  478. testKVOOnSubclass(NSOperation.class, self);
  479. });
  480. qck_it(@"stops KVO on subclasses of already-swizzled classes", ^{
  481. testKVOOnSubclass(RACTestOperation.class, self);
  482. });
  483. qck_it(@"stops KVO on NSObject subclasses even with a nil observer", ^{
  484. testKVOOnSubclass(NSOperation.class, nil);
  485. });
  486. qck_it(@"stops KVO on subclasses of already-swizzled classes even with a nil observer", ^{
  487. testKVOOnSubclass(RACTestOperation.class, nil);
  488. });
  489. });
  490. qck_it(@"should automatically stop KVO when the observer deallocates", ^{
  491. __weak id weakObserver = nil;
  492. __weak id weakIdentifier = nil;
  493. NSOperation *operation = [[NSOperation alloc] init];
  494. @autoreleasepool {
  495. // Create an observer that we control the memory management of.
  496. CFTypeRef observer = CFBridgingRetain([[NSOperation alloc] init]);
  497. expect((__bridge id)observer).notTo(beNil());
  498. weakObserver = (__bridge id)observer;
  499. expect(weakObserver).notTo(beNil());
  500. id identifier = [operation rac_observeKeyPath:@"isFinished" options:0 observer:(__bridge id)observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {}];
  501. expect(identifier).notTo(beNil());
  502. weakIdentifier = identifier;
  503. expect(weakIdentifier).notTo(beNil());
  504. CFRelease(observer);
  505. }
  506. expect(weakObserver).to(beNil());
  507. expect(weakIdentifier).to(beNil());
  508. });
  509. qck_it(@"should stop KVO when the observer is disposed", ^{
  510. NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  511. __block NSString *name = nil;
  512. RACDisposable *disposable = [queue rac_observeKeyPath:@"name" options:0 observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  513. name = queue.name;
  514. }];
  515. queue.name = @"1";
  516. expect(name).to(equal(@"1"));
  517. [disposable dispose];
  518. queue.name = @"2";
  519. expect(name).to(equal(@"1"));
  520. });
  521. qck_it(@"should distinguish between observers being disposed", ^{
  522. NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  523. __block NSString *name1 = nil;
  524. __block NSString *name2 = nil;
  525. RACDisposable *disposable = [queue rac_observeKeyPath:@"name" options:0 observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  526. name1 = queue.name;
  527. }];
  528. [queue rac_observeKeyPath:@"name" options:0 observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  529. name2 = queue.name;
  530. }];
  531. queue.name = @"1";
  532. expect(name1).to(equal(@"1"));
  533. expect(name2).to(equal(@"1"));
  534. [disposable dispose];
  535. queue.name = @"2";
  536. expect(name1).to(equal(@"1"));
  537. expect(name2).to(equal(@"2"));
  538. });
  539. });
  540. QuickSpecEnd
  541. @implementation RACTestOperation
  542. @end