I’ve noticed that in Objective-C, doing anything in -finalize beyond freeing non-GC’ed memory is frowned upon. The concrete reasons for this I’ve come across are that:
a) you risk resurrecting the finalized object by sending something a reference to self.
b) you don’t know whether the object you’re messaging is also being finalized.
Are there any other reasons? And what in particular should be avoided? I’m wondering in particular about allocating new objects, locking the gc thread, using gcd to dispatch a block, and sending a singleton object a performSelectorOnMainThread: message.
To provide a bit of context, I’m writing a class which wraps a type from a C API that makes modifications to a global state when allocated or deallocated. I’m tempted to do something like this:
@interface MyWrapper : NSObject
{
C_API_Type* data;
}
@end
static const* NSString GlobalLock = @"GlobalLock";
@implementation MyWrapper
- (id)init
{
if((self = [super init]))
{
@synchronized( GlobalLock )
{
data = C_API_Allocate();
}
}
return self;
}
- (void) finalize
{
@synchronized( GlobalLock )
{
C_API_Deallocate(data);
}
[super finalize];
}
@end
which avoids both of those issues, but alarm bells are still going off. I may just have to approach what I’m trying to do differently, but if so, what’s wrong with this pattern?
The general rule is actually “don’t do heavy lifting in either
dealloc(manual retain-release) orfinalize“.The problem in both cases is exactly that the object graph local to the object being finalized or deallocated is necessarily in an undefined state or you have to enforce a specific order on object destruction, which is a horrible thing to do.
The “no heavy lifting” is a simple rule for expressing that particular detail;
free()ing malloc memory is pretty safe because it won’t touch the object graph. But touching other objects is bad and will lead to fragility and/or crashes.(When porting Xcode from non-GC to GC, one of the single most nasty source of problems was order dependencies — both known and unknown — that were in the
deallocmethods. By moving to a more formal “invalidation” pattern, where dependencies could be explicitly expressed and enforced, that fragility was eliminated.)