我在 iOS 和 Mac OS X 开发中使用 Objective-C 中的测试驱动开发,我希望能够编写测试来验证我使用类工厂方法创建的对象是否返回自动释放对象。
有人如何编写一个测试来验证提供的对象是自动释放的?
我在 iOS 和 Mac OS X 开发中使用 Objective-C 中的测试驱动开发,我希望能够编写测试来验证我使用类工厂方法创建的对象是否返回自动释放对象。
有人如何编写一个测试来验证提供的对象是自动释放的?
简而言之,你不能。无法知道对象的自动释放状态。
在某些情况下,您可以推断对象是否被放置在自动释放池中。这个想法是声明一个指向对象的指针,在一个@autoreleasepool
块中实例化它,然后验证它是否dealloc
在块结束后调用。
通过您选择的混合或覆盖的任何组合dealloc
,您必须首先提供一种方法来验证dealloc
已调用。我编写了一个NSObject
具有以下接口和实现的类别,它提供了一个deallocationDelegate
属性,该属性将接收有关handleDeallocation:
何时释放对象的消息。
@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
我在我的应用程序委托中运行了一些测试代码,只是为了看看它是否有效。我声明了一个实例,该NSMutableArray
实例旨在保存NSValue
从调用对象的指针派生的实例-handleDeallocation
,我将其实现如下所示:
- (void)handleDeallocation:(id)toDie
{
NSValue *pointerValue = [NSValue valueWithPointer:toDie];
[self.deallocatedPointerValues addObject:pointerValue];
}
现在,这是我运行的一个片段。SomeClass
是NSObject
没有附加属性或方法的子类。
self.deallocatedPointerValues = [NSMutableArray 数组];
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);
在这种情况下,可以验证由arsc
(代表自动释放的SomeClass)指向的对象由于结束@autoreleasepool
块而被释放。
这种方法有几个重大限制。retain
一,当其他消息可能发送到从您的工厂方法返回的对象时,这不起作用。此外,这不言而喻,swizzlingdealloc
应该只在实验环境中进行,我认为有些人会争辩说它不应该在测试中被 swizzled(显然它不应该在生产中被 swizzled!)。最后,更重要的是,这不适用于 Foundation 对象,例如NSString
那些已经优化的方式,您是否正在创建一个新实例并不总是很清楚。因此,如果有的话,这对于您自己的自定义对象是最合适的。
最后,我认为真正做到这一点并不实际。我觉得这比它值得做的工作更多,并且适用范围如此之小,因为在内存管理方面,花时间学习工具更好,成为更好的投资。而且,当然,由于 ARC 的优势,这种方法从一开始就是过时的。无论如何,如果您确实需要编写此类测试,并且可以解决这里的限制,请随意调整此代码。我很想知道它在实际测试环境中的表现如何。
我赞扬您对 TDD 的奉献。但是内存管理是一个你只需要遵循公认的约定的领域:“当返回一个对象时,它需要照顾自己的生命周期。” 当我不小心过度释放某些东西时,我的单元测试会抓住我,但它们不会发现泄漏。为此,我首先依靠分析,然后依靠运行泄漏仪器。