blob: a81d89c7a3084cd763f8ef67fd9f6cdc6038a6ce [file] [log] [blame]
//
// NSObject+RACDeallocating.m
// ReactiveCocoa
//
// Created by Kazuo Koga on 2013/03/15.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "NSObject+RACDeallocating.h"
#import "RACCompoundDisposable.h"
#import "RACDisposable.h"
#import "RACReplaySubject.h"
#import <objc/message.h>
#import <objc/runtime.h>
static const void *RACObjectCompoundDisposable = &RACObjectCompoundDisposable;
static NSMutableSet *swizzledClasses() {
static dispatch_once_t onceToken;
static NSMutableSet *swizzledClasses = nil;
dispatch_once(&onceToken, ^{
swizzledClasses = [[NSMutableSet alloc] init];
});
return swizzledClasses;
}
static void swizzleDeallocIfNeeded(Class classToSwizzle) {
@synchronized (swizzledClasses()) {
NSString *className = NSStringFromClass(classToSwizzle);
if ([swizzledClasses() containsObject:className]) return;
SEL deallocSelector = sel_registerName("dealloc");
__block void (*originalDealloc)(__unsafe_unretained id, SEL) = NULL;
id newDealloc = ^(__unsafe_unretained id self) {
RACCompoundDisposable *compoundDisposable = objc_getAssociatedObject(self, RACObjectCompoundDisposable);
[compoundDisposable dispose];
if (originalDealloc == NULL) {
struct objc_super superInfo = {
.receiver = self,
.super_class = class_getSuperclass(classToSwizzle)
};
void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper;
msgSend(&superInfo, deallocSelector);
} else {
originalDealloc(self, deallocSelector);
}
};
IMP newDeallocIMP = imp_implementationWithBlock(newDealloc);
if (!class_addMethod(classToSwizzle, deallocSelector, newDeallocIMP, "v@:")) {
// The class already contains a method implementation.
Method deallocMethod = class_getInstanceMethod(classToSwizzle, deallocSelector);
// We need to store original implementation before setting new implementation
// in case method is called at the time of setting.
originalDealloc = (__typeof__(originalDealloc))method_getImplementation(deallocMethod);
// We need to store original implementation again, in case it just changed.
originalDealloc = (__typeof__(originalDealloc))method_setImplementation(deallocMethod, newDeallocIMP);
}
[swizzledClasses() addObject:className];
}
}
@implementation NSObject (RACDeallocating)
- (RACSignal *)rac_willDeallocSignal {
RACSignal *signal = objc_getAssociatedObject(self, _cmd);
if (signal != nil) return signal;
RACReplaySubject *subject = [RACReplaySubject subject];
[self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
[subject sendCompleted];
}]];
objc_setAssociatedObject(self, _cmd, subject, OBJC_ASSOCIATION_RETAIN);
return subject;
}
- (RACCompoundDisposable *)rac_deallocDisposable {
@synchronized (self) {
RACCompoundDisposable *compoundDisposable = objc_getAssociatedObject(self, RACObjectCompoundDisposable);
if (compoundDisposable != nil) return compoundDisposable;
swizzleDeallocIfNeeded(self.class);
compoundDisposable = [RACCompoundDisposable compoundDisposable];
objc_setAssociatedObject(self, RACObjectCompoundDisposable, compoundDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return compoundDisposable;
}
}
@end
@implementation NSObject (RACDeallocatingDeprecated)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
- (RACSignal *)rac_didDeallocSignal {
RACSubject *subject = [RACSubject subject];
RACScopedDisposable *disposable = [[RACDisposable
disposableWithBlock:^{
[subject sendCompleted];
}]
asScopedDisposable];
objc_setAssociatedObject(self, (__bridge void *)disposable, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return subject;
}
- (void)rac_addDeallocDisposable:(RACDisposable *)disposable {
[self.rac_deallocDisposable addDisposable:disposable];
}
#pragma clang diagnostic pop
@end