NSObjectRACDeallocatingSpec.m 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. //
  2. // NSObject+RACDeallocating.m
  3. // ReactiveCocoa
  4. //
  5. // Created by Kazuo Koga on 2013/03/15.
  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 "NSObject+RACDeallocating.h"
  12. #import "RACCompoundDisposable.h"
  13. #import "RACDisposable.h"
  14. #import "RACSignal+Operations.h"
  15. #import <objc/runtime.h>
  16. @interface RACDeallocSwizzlingTestClass : NSObject
  17. @end
  18. @implementation RACDeallocSwizzlingTestClass
  19. - (void)dealloc {
  20. // Provide an empty implementation just so we can swizzle it.
  21. }
  22. @end
  23. @interface RACDeallocSwizzlingTestSubclass : RACDeallocSwizzlingTestClass
  24. @end
  25. @implementation RACDeallocSwizzlingTestSubclass
  26. @end
  27. QuickSpecBegin(NSObjectRACDeallocatingSpec)
  28. qck_describe(@"-dealloc swizzling", ^{
  29. SEL selector = NSSelectorFromString(@"dealloc");
  30. qck_it(@"should not invoke superclass -dealloc method twice", ^{
  31. __block NSUInteger superclassDeallocatedCount = 0;
  32. __block BOOL subclassDeallocated = NO;
  33. @autoreleasepool {
  34. RACDeallocSwizzlingTestSubclass *object __attribute__((objc_precise_lifetime)) = [[RACDeallocSwizzlingTestSubclass alloc] init];
  35. Method oldDeallocMethod = class_getInstanceMethod(RACDeallocSwizzlingTestClass.class, selector);
  36. void (*oldDealloc)(id, SEL) = (__typeof__(oldDealloc))method_getImplementation(oldDeallocMethod);
  37. id newDealloc = ^(__unsafe_unretained id self) {
  38. superclassDeallocatedCount++;
  39. oldDealloc(self, selector);
  40. };
  41. class_replaceMethod(RACDeallocSwizzlingTestClass.class, selector, imp_implementationWithBlock(newDealloc), method_getTypeEncoding(oldDeallocMethod));
  42. [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  43. subclassDeallocated = YES;
  44. }]];
  45. expect(@(subclassDeallocated)).to(beFalsy());
  46. expect(@(superclassDeallocatedCount)).to(equal(@0));
  47. }
  48. expect(@(subclassDeallocated)).to(beTruthy());
  49. expect(@(superclassDeallocatedCount)).to(equal(@1));
  50. });
  51. qck_it(@"should invoke superclass -dealloc method swizzled in after the subclass", ^{
  52. __block BOOL superclassDeallocated = NO;
  53. __block BOOL subclassDeallocated = NO;
  54. @autoreleasepool {
  55. RACDeallocSwizzlingTestSubclass *object __attribute__((objc_precise_lifetime)) = [[RACDeallocSwizzlingTestSubclass alloc] init];
  56. [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  57. subclassDeallocated = YES;
  58. }]];
  59. Method oldDeallocMethod = class_getInstanceMethod(RACDeallocSwizzlingTestClass.class, selector);
  60. void (*oldDealloc)(id, SEL) = (__typeof__(oldDealloc))method_getImplementation(oldDeallocMethod);
  61. id newDealloc = ^(__unsafe_unretained id self) {
  62. superclassDeallocated = YES;
  63. oldDealloc(self, selector);
  64. };
  65. class_replaceMethod(RACDeallocSwizzlingTestClass.class, selector, imp_implementationWithBlock(newDealloc), method_getTypeEncoding(oldDeallocMethod));
  66. expect(@(subclassDeallocated)).to(beFalsy());
  67. expect(@(superclassDeallocated)).to(beFalsy());
  68. }
  69. expect(@(subclassDeallocated)).to(beTruthy());
  70. expect(@(superclassDeallocated)).to(beTruthy());
  71. });
  72. });
  73. qck_describe(@"-rac_deallocDisposable", ^{
  74. qck_it(@"should dispose of the disposable when it is dealloc'd", ^{
  75. __block BOOL wasDisposed = NO;
  76. @autoreleasepool {
  77. NSObject *object __attribute__((objc_precise_lifetime)) = [[NSObject alloc] init];
  78. [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  79. wasDisposed = YES;
  80. }]];
  81. expect(@(wasDisposed)).to(beFalsy());
  82. }
  83. expect(@(wasDisposed)).to(beTruthy());
  84. });
  85. qck_it(@"should be able to use the object during disposal", ^{
  86. @autoreleasepool {
  87. RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init];
  88. @autoreleasepool {
  89. object.objectValue = [@"foo" mutableCopy];
  90. }
  91. __unsafe_unretained RACTestObject *weakObject = object;
  92. [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  93. expect(weakObject.objectValue).to(equal(@"foo"));
  94. }]];
  95. }
  96. });
  97. });
  98. qck_describe(@"-rac_willDeallocSignal", ^{
  99. qck_it(@"should complete on dealloc", ^{
  100. __block BOOL completed = NO;
  101. @autoreleasepool {
  102. [[[[RACTestObject alloc] init] rac_willDeallocSignal] subscribeCompleted:^{
  103. completed = YES;
  104. }];
  105. }
  106. expect(@(completed)).to(beTruthy());
  107. });
  108. qck_it(@"should not send anything", ^{
  109. __block BOOL valueReceived = NO;
  110. __block BOOL completed = NO;
  111. @autoreleasepool {
  112. [[[[RACTestObject alloc] init] rac_willDeallocSignal] subscribeNext:^(id x) {
  113. valueReceived = YES;
  114. } completed:^{
  115. completed = YES;
  116. }];
  117. }
  118. expect(@(valueReceived)).to(beFalsy());
  119. expect(@(completed)).to(beTruthy());
  120. });
  121. qck_it(@"should complete upon subscription if already deallocated", ^{
  122. __block BOOL deallocated = NO;
  123. RACSignal *signal;
  124. @autoreleasepool {
  125. RACTestObject *object = [[RACTestObject alloc] init];
  126. signal = [object rac_willDeallocSignal];
  127. [signal subscribeCompleted:^{
  128. deallocated = YES;
  129. }];
  130. }
  131. expect(@(deallocated)).to(beTruthy());
  132. expect(@([signal waitUntilCompleted:NULL])).to(beTruthy());
  133. });
  134. qck_it(@"should complete before the object is invalid", ^{
  135. __block NSString *objectValue;
  136. @autoreleasepool {
  137. RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init];
  138. @autoreleasepool {
  139. object.objectValue = [@"foo" mutableCopy];
  140. }
  141. __unsafe_unretained RACTestObject *weakObject = object;
  142. [[object rac_willDeallocSignal] subscribeCompleted:^{
  143. objectValue = [weakObject.objectValue copy];
  144. }];
  145. }
  146. expect(objectValue).to(equal(@"foo"));
  147. });
  148. });
  149. QuickSpecEnd