Tron Thomas
Tron Thomas

Reputation: 1651

Detected autorelease objects

I using Test Driven Development in Objective-C for iOS and Mac OS X development, and I want to be able to write tests that can verify that objects I create with class factory methods return autorelease objects.

How can someone write a test that verifies a provided object is autorelease?

Upvotes: 1

Views: 490

Answers (3)

Jon Reid
Jon Reid

Reputation: 20980

I commend your dedication to TDD. But memory management is an area where you simply have to follow well-established conventions: "When returning an object, it needs to take care of its own lifetime." My unit tests catch me when I accidentally over-release something, but they won't catch a leak. For that, I rely first on Analyze, then on running the Leaks instrument.

Upvotes: 0

Carl Veazey
Carl Veazey

Reputation: 18363

In some cases, you can infer whether an object was placed in an autorelease pool. The idea is declaring a pointer to an object, instantiating it within an @autoreleasepool block, and then verifying that it had dealloc called after the end of the block.

Through whatever combination of swizzling or overriding dealloc you choose, you must first provide a way to verify that dealloc has been called. I wrote an NSObject category with the following interface and implementation, that provides a deallocationDelegate property that will receive a message of handleDeallocation: when the object is deallocated.

@interface NSObject (FunTimes)

@property (nonatomic, assign) id deallocationDelegate;

@end

@implementation NSObject (FunTimes)

+ (void)load
{
    Class klass = [NSObject class];
    SEL originalSelector = @selector(dealloc);
    Method originalMethod = class_getInstanceMethod(klass, originalSelector);
    SEL replacementSelector = @selector(funDealloc);
    Method replacementMethod = class_getInstanceMethod(klass, replacementSelector);
    if(class_addMethod(klass, originalSelector, method_getImplementation(replacementMethod), method_getTypeEncoding(replacementMethod)))
    {
        class_replaceMethod(klass, replacementSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }
    else
    {
        method_exchangeImplementations(originalMethod, replacementMethod);
    }
}

- (void)funDealloc
{
    if (self.deallocationDelegate)
        [self.deallocationDelegate performSelector:@selector(handleDeallocation:) withObject:self];

    [self funDealloc];
}

static char myKey;

- (void)setDeallocationDelegate:(id)deallocationDelegate
{
    objc_setAssociatedObject(self, &myKey, deallocationDelegate, OBJC_ASSOCIATION_ASSIGN);
}

- (id)deallocationDelegate
{
    return objc_getAssociatedObject(self, &myKey);
}

@end

I ran some test code in my application delegate just to see if it works. I declared an NSMutableArray instance designed to hold NSValue instances derived from the pointers of objects calling -handleDeallocation, which I implement as shown:

- (void)handleDeallocation:(id)toDie
{
    NSValue *pointerValue = [NSValue valueWithPointer:toDie];
    [self.deallocatedPointerValues addObject:pointerValue];
}

Now, here's a snippet of what I ran. SomeClass is an NSObject subclass with no additional properties or methods.

self.deallocatedPointerValues = [NSMutableArray array];

SomeClass *arsc = nil;
@autoreleasepool {
    arsc = [[[SomeClass alloc] init] autorelease];
    arsc.deallocationDelegate = self;
    NSValue *prePointerValue = [NSValue valueWithPointer:arsc];
    BOOL preDeallocated = [self.deallocatedPointerValues containsObject:prePointerValue];
    NSLog(@"PreDeallocated should be no is %d",preDeallocated);
}

NSValue *postPointerValue = [NSValue valueWithPointer:arsc];
BOOL postDeallocated = [self.deallocatedPointerValues containsObject:postPointerValue];
NSLog(@"Post deallocated should be yes is %d",postDeallocated);

In this case, it can be verified that the object pointed to by arsc (which stands for auto released SomeClass) has been deallocated due to ending the @autoreleasepool block.

There are several significant limitations to the this approach. One, this cannot work when other messages of retain may be sent to your object that is returned from your factory method. Also, and this should go without saying, swizzling dealloc should only be done in experimental settings, and I think some would argue that it shouldn't be swizzled in testing (obviously it shouldn't be swizzled in production!). Finally, and more significantly, this doesn't work well with Foundation objects such as NSString that have been optimized in ways that it's not always clear whether you are creating a new instance or not. So this is most appropriate, if at all, for your own custom objects.

As a final word, I don't think it's practical to do this really. I felt it was more work than it was worth and is so narrowly applicable as the make spending time learning instruments better to be a far better investment when it comes to memory management. And, of course, with ARC's ascendency, this approach is archaic from the start. Regardless, if you do have need to write such tests, and can work around the limitations here, feel free to adapt this code. I'd be curious to see how it pans out in an actual testing environment.

Upvotes: 0

bbum
bbum

Reputation: 162722

In short, you can't. There is no way to know the autorelease state of an object.

Upvotes: 2

Related Questions