blob: b5e80b27ec0761375c8259ef8945f43af9ac1cdb [file] [log] [blame]
//
// NSObject+RACDeallocating.m
// ReactiveCocoa
//
// Created by Kazuo Koga on 2013/03/15.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACTestObject.h"
#import "NSObject+RACDeallocating.h"
#import "RACCompoundDisposable.h"
#import "RACDisposable.h"
#import "RACSignal+Operations.h"
#import <objc/runtime.h>
@interface RACDeallocSwizzlingTestClass : NSObject
@end
@implementation RACDeallocSwizzlingTestClass
- (void)dealloc {
// Provide an empty implementation just so we can swizzle it.
}
@end
@interface RACDeallocSwizzlingTestSubclass : RACDeallocSwizzlingTestClass
@end
@implementation RACDeallocSwizzlingTestSubclass
@end
SpecBegin(NSObjectRACDeallocatingSpec)
describe(@"-dealloc swizzling", ^{
SEL selector = NSSelectorFromString(@"dealloc");
it(@"should not invoke superclass -dealloc method twice", ^{
__block NSUInteger superclassDeallocatedCount = 0;
__block BOOL subclassDeallocated = NO;
@autoreleasepool {
RACDeallocSwizzlingTestSubclass *object __attribute__((objc_precise_lifetime)) = [[RACDeallocSwizzlingTestSubclass alloc] init];
Method oldDeallocMethod = class_getInstanceMethod(RACDeallocSwizzlingTestClass.class, selector);
void (*oldDealloc)(id, SEL) = (__typeof__(oldDealloc))method_getImplementation(oldDeallocMethod);
id newDealloc = ^(__unsafe_unretained id self) {
superclassDeallocatedCount++;
oldDealloc(self, selector);
};
class_replaceMethod(RACDeallocSwizzlingTestClass.class, selector, imp_implementationWithBlock(newDealloc), method_getTypeEncoding(oldDeallocMethod));
[object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
subclassDeallocated = YES;
}]];
expect(subclassDeallocated).to.beFalsy();
expect(superclassDeallocatedCount).to.equal(0);
}
expect(subclassDeallocated).to.beTruthy();
expect(superclassDeallocatedCount).to.equal(1);
});
it(@"should invoke superclass -dealloc method swizzled in after the subclass", ^{
__block BOOL superclassDeallocated = NO;
__block BOOL subclassDeallocated = NO;
@autoreleasepool {
RACDeallocSwizzlingTestSubclass *object __attribute__((objc_precise_lifetime)) = [[RACDeallocSwizzlingTestSubclass alloc] init];
[object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
subclassDeallocated = YES;
}]];
Method oldDeallocMethod = class_getInstanceMethod(RACDeallocSwizzlingTestClass.class, selector);
void (*oldDealloc)(id, SEL) = (__typeof__(oldDealloc))method_getImplementation(oldDeallocMethod);
id newDealloc = ^(__unsafe_unretained id self) {
superclassDeallocated = YES;
oldDealloc(self, selector);
};
class_replaceMethod(RACDeallocSwizzlingTestClass.class, selector, imp_implementationWithBlock(newDealloc), method_getTypeEncoding(oldDeallocMethod));
expect(subclassDeallocated).to.beFalsy();
expect(superclassDeallocated).to.beFalsy();
}
expect(subclassDeallocated).to.beTruthy();
expect(superclassDeallocated).to.beTruthy();
});
});
describe(@"-rac_deallocDisposable", ^{
it(@"should dispose of the disposable when it is dealloc'd", ^{
__block BOOL wasDisposed = NO;
@autoreleasepool {
NSObject *object __attribute__((objc_precise_lifetime)) = [[NSObject alloc] init];
[object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
wasDisposed = YES;
}]];
expect(wasDisposed).to.beFalsy();
}
expect(wasDisposed).to.beTruthy();
});
it(@"should be able to use the object during disposal", ^{
@autoreleasepool {
RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init];
@autoreleasepool {
object.objectValue = [@"foo" mutableCopy];
}
__unsafe_unretained RACTestObject *weakObject = object;
[object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
expect(weakObject.objectValue).to.equal(@"foo");
}]];
}
});
});
describe(@"-rac_willDeallocSignal", ^{
it(@"should complete on dealloc", ^{
__block BOOL completed = NO;
@autoreleasepool {
[[[[RACTestObject alloc] init] rac_willDeallocSignal] subscribeCompleted:^{
completed = YES;
}];
}
expect(completed).to.beTruthy();
});
it(@"should not send anything", ^{
__block BOOL valueReceived = NO;
__block BOOL completed = NO;
@autoreleasepool {
[[[[RACTestObject alloc] init] rac_willDeallocSignal] subscribeNext:^(id x) {
valueReceived = YES;
} completed:^{
completed = YES;
}];
}
expect(valueReceived).to.beFalsy();
expect(completed).to.beTruthy();
});
it(@"should complete upon subscription if already deallocated", ^{
__block BOOL deallocated = NO;
RACSignal *signal;
@autoreleasepool {
RACTestObject *object = [[RACTestObject alloc] init];
signal = [object rac_willDeallocSignal];
[signal subscribeCompleted:^{
deallocated = YES;
}];
}
expect(deallocated).to.beTruthy();
expect([signal waitUntilCompleted:NULL]).to.beTruthy();
});
it(@"should complete before the object is invalid", ^{
__block NSString *objectValue;
@autoreleasepool {
RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init];
@autoreleasepool {
object.objectValue = [@"foo" mutableCopy];
}
__unsafe_unretained RACTestObject *weakObject = object;
[[object rac_willDeallocSignal] subscribeCompleted:^{
objectValue = [weakObject.objectValue copy];
}];
}
expect(objectValue).to.equal(@"foo");
});
});
SpecEnd