私は、iOS および Mac OS X の開発に Objective-C でテスト駆動開発を使用しており、クラス ファクトリ メソッドで作成したオブジェクトが autorelease オブジェクトを返すことを検証できるテストを記述できるようにしたいと考えています。
提供されたオブジェクトが autorelease であることを検証するテストを作成するにはどうすればよいでしょうか?
私は、iOS および Mac OS X の開発に Objective-C でテスト駆動開発を使用しており、クラス ファクトリ メソッドで作成したオブジェクトが autorelease オブジェクトを返すことを検証できるテストを記述できるようにしたいと考えています。
提供されたオブジェクトが autorelease であることを検証するテストを作成するにはどうすればよいでしょうか?
要するに、できません。オブジェクトの自動解放状態を知る方法はありません。
TDDへのあなたの献身を称賛します。ただし、メモリ管理は、確立された規則に従う必要がある領域です。「オブジェクトを返すときは、オブジェクト自体の存続期間を管理する必要があります。」ユニットテストでは、誤って何かを過剰リリースしたときにキャッチされますが、リークはキャッチされません。そのために、私は最初に分析に依存し、次にリーク機器の実行に依存します。
場合によっては、オブジェクトが自動解放プールに配置されたかどうかを推測できます。アイデアは、オブジェクトへのポインターを宣言し、ブロック内でインスタンス化し、ブロックの終了後に呼び出された@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
1 つ目は、ファクトリ メソッドから返されたオブジェクトに他のメッセージが送信される可能性がある場合、これは機能しません。また、言うまでもなく、スウィズリングdealloc
は実験的な設定でのみ行うべきであり、テストではスウィズルすべきではないと主張する人もいると思います (明らかに、本番環境ではスウィズルすべきではありません!)。NSString
最後に、さらに重要なことに、これは、新しいインスタンスを作成しているかどうかが常に明確ではない方法で最適化されているような Foundation オブジェクトではうまく機能しません。したがって、これは、独自のカスタム オブジェクトに最も適しています。
最後に、これを実際に行うのは現実的ではないと思います。それは価値があるよりも多くの作業であり、メモリ管理に関しては、楽器の学習に時間を費やすことがはるかに優れた投資になるため、適用範囲が非常に狭いと感じました. そしてもちろん、ARC の台頭により、このアプローチは最初から時代遅れです。それでも、そのようなテストを作成する必要があり、ここでの制限を回避できる場合は、このコードを自由に変更してください。実際のテスト環境でどのように機能するかを知りたいです。