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:
- Obviously, you have to have your associated object set with
OBJC_ASSOCIATION_RETAIN_NONATOMIC
, or it won't be retained and released for you automatically.
- This becomes trickier if your memento/state associated object gets
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.
- The
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!
- I don't know that it's documented that
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.
- I don't know if
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!