I need to perform an action in the dealloc method of a category. I've tried swizzling but that doesn't work (nor is it a great idea).
In case anyone asks, the answer is no, I can't use a subclass, this is specifically for a category.
I want to perform an action on delay using [NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:]
or [self performSelector:withObject:afterDelay:]
and cancel it on dealloc.
The first issue is that NSTimer
retains the target, which I don't want. [self performSelector:withObject:afterDelay:]
doesn't retain, but I need to be able to call [NSObject cancelPreviousPerformRequestsWithTarget:selector:object:]
in the dealloc
method or we get a crash.
Any suggestions how to do this on a category?
I still think it would be better to subclass your class and not mess with the runtime, but if you are definitely sure you need to do it in a category, I have an option in mind for you. It still messes with the runtime, but is safer than swizzling I think.
Consider writing a helper class, say calling it DeallocHook
which can be attached to any NSObject
and perform an action when this NSObject
gets deallocated. Then you can do something like this:
// Instead of directly messing with your class -dealloc method, attach
// the hook to your instance and do the cleanup in the callback
[DeallocHook attachTo: yourObject
callback: ^{ [NSObject cancelPrevious... /* your code here */ ]; }];
You can implement the DeallocHook
using objc_setAssociatedObject
:
@interface DeallocHook : NSObject
@property (copy, nonatomic) dispatch_block_t callback;
+ (id) attachTo: (id) target callback: (dispatch_block_t) block;
@end
Implementation would be something like this:
#import "DeallocHook.h"
#import <objc/runtime.h>
// Address of a static global var can be used as a key
static void *kDeallocHookAssociation = &kDeallocHookAssociation;
@implementation DeallocHook
+ (id) attachTo: (id) target callback: (dispatch_block_t) block
{
DeallocHook *hook = [[DeallocHook alloc] initWithCallback: block];
// The trick is that associations are released when your target
// object gets deallocated, so our DeallocHook object will get
// deallocated right after your object
objc_setAssociatedObject(target, kDeallocHookAssociation, hook, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return hook;
}
- (id) initWithCallback: (dispatch_block_t) block
{
self = [super init];
if (self != nil)
{
// Here we just copy the callback for later
self.callback = block;
}
return self;
}
- (void) dealloc
{
// And we place our callback within the -dealloc method
// of your helper class.
if (self.callback != nil)
dispatch_async(dispatch_get_main_queue(), self.callback);
}
@end
See Apple's documentation on Objective-C runtime for more info about the associative references (although I'd say the docs are not very detailed regarding this subject).
I've not tested this thoroughly, but it seemed to work. Just thought I'd give you another direction to look into.
I just stumbled on a solution to this that I haven't seen before, and seems to work...
I have a category that--as one often does--needs some state variables, so I use objc_setAssociatedObject
, like this:
Memento *m = [[[Memento alloc] init] autorelease];
objc_setAssociatedObject(self, kMementoTagKey, m, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
And, I needed to know when the instances my category extending were being dealloc
ed. In my case it's because I set observers on self
, and have to remove those observers at some point, otherwise I get the NSKVODeallocateBreak
leak warnings, which could lead to bad stuff.
Suddenly it dawned on me, since my associated objects were being retain
ed (because of using OBJC_ASSOCIATION_RETAIN_NONATOMIC
), they must be being release
d also, and therefore being dealloc
ed...in fact I had implemented a dealloc
method in the simple storage class I had created for storing my state values. And, I postulated: my associated objects must be dealloced before my category's instances are! So, I can have my associated objects notify their owners when they realize they are being dealloc
ed! Since I already had my retained associated objects, I just had to add an owner
property (which is not specified as retain
!), set the owner, and then call some method on the owner in the associated object's dealloc
method.
Here's a modified part of my category's .m file, with the relevant bits:
#import <objc/runtime.h> // So we can use objc_setAssociatedObject, etc.
#import "TargetClass+Category.h"
@interface TargetClass_CategoryMemento : NSObject
{
GLfloat *_coef;
}
@property (nonatomic) GLfloat *coef;
@property (nonatomic, assign) id owner;
@end
@implementation TargetClass_CategoryMemento
-(id)init {
if (self=[super init]) {
_coef = (GLfloat *)malloc(sizeof(GLfloat) * 15);
}
return self;
};
-(void)dealloc {
free(_coef);
if (_owner != nil
&& [_owner respondsToSelector:@selector(associatedObjectReportsDealloc)]) {
[_owner associatedObjectReportsDealloc];
}
[super dealloc];
}
@end
@implementation TargetClass (Category)
static NSString *kMementoTagKey = @"TargetClass+Category_MementoTagKey";
-(TargetClass_CategoryMemento *)TargetClass_CategoryGetMemento
{
TargetClass_CategoryMemento *m = objc_getAssociatedObject(self, kMementoTagKey);
if (m) {
return m;
}
// else
m = [[[TargetClass_CategoryMemento alloc] init] autorelease];
m.owner = self; // so we can let the owner know when we dealloc!
objc_setAssociatedObject(self, kMementoTagKey, m, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return m;
}
-(void) doStuff
{
CCSprite_BlurableMemento *m = [self CCSprite_BlurableGetMemento];
// do stuff you needed a category for, and store state values in m
}
-(void) associatedObjectReportsDealloc
{
NSLog(@"My associated object is being dealloced!");
// do stuff you need to do when your category instances are dealloced!
}
@end
The pattern here I learned somewhere (probably on S.O.) uses a factory method to get or create a memento object. Now it sets the owner on the memento, and the memento's dealloc
method calls back to let the owner know it's being dealloc
ed
CAVEATS:
OBJC_ASSOCIATION_RETAIN_NONATOMIC
, or it won't be retained and released for you automatically.dealloc
ed under other circumstances than the owner being dealloc
ed...but you can probably train one object or the other to ignore that event.owner
property can't be declared as retain
, or you'll truly create a strong reference loop and neither object will ever qualify to be dealloc
ed!OBJC_ASSOCIATION_RETAIN_NONATOMIC
associated objects are necessarily release
d before the owner is completely dealloc
ed, but it seems to happen that way and almost must be the case, intuitively at least.associatedObjectReportsDealloc
will be called before or after the TargetClass
's dealloc method--this could be important! If it runs afterwards, if you try to access member objects of the TargetClass
you will crash! And my guess is that it's afterwards.This is a little messy, because you're double-linking your objects, which requires you to be very careful to keep those references straight. But, it doesn't involve swizzling, or other interference with the runtime--this just relies on a certain behavior of the runtime. Seems like a handy solution if you already have an associated object. In some cases it might be worth creating one just to catch your own dealloc
s!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With