NSAutoreleasePool 再び

あれid:masakih:20110113:1294933491を書いたあと、例外以外でも大丈夫なんだろうかと思ったのでやってみたよ!

- (void)test01
{
    NSAutoreleasePool *pool01 = [[NSAutoreleasePool alloc] init];
    
    for(int i = 0; i < 100; i++) {
        NSAutoreleasePool *pool02 = [[NSAutoreleasePool alloc] init];
        Hoge *hoge = [[[Hoge alloc] init] autorelease];
        if(i>3) break;
        [pool02 release];  // 途中 break対策されてません!
    }
    
    NSLog(@"Before pool01 release");
    [pool01 release];
    NSLog(@"After pool01 released");
}

結果

2011-01-20 19:57:07.618 TestAutoreleasePool[30389:a0f] Initialized -> 0x1001c8680
2011-01-20 19:57:07.619 TestAutoreleasePool[30389:a0f] Deallocate -> 0x1001c8680
2011-01-20 19:57:07.619 TestAutoreleasePool[30389:a0f] Initialized -> 0x1001c89a0
2011-01-20 19:57:07.620 TestAutoreleasePool[30389:a0f] Deallocate -> 0x1001c89a0
2011-01-20 19:57:07.621 TestAutoreleasePool[30389:a0f] Initialized -> 0x1001c8d10
2011-01-20 19:57:07.622 TestAutoreleasePool[30389:a0f] Deallocate -> 0x1001c8d10
2011-01-20 19:57:07.623 TestAutoreleasePool[30389:a0f] Initialized -> 0x1001c9040
2011-01-20 19:57:07.623 TestAutoreleasePool[30389:a0f] Deallocate -> 0x1001c9040
2011-01-20 19:57:07.624 TestAutoreleasePool[30389:a0f] Initialized -> 0x1001c9370
2011-01-20 19:57:07.628 TestAutoreleasePool[30389:a0f] Before pool01 release
2011-01-20 19:57:07.628 TestAutoreleasePool[30389:a0f] Deallocate -> 0x1001c9370
2011-01-20 19:57:07.629 TestAutoreleasePool[30389:a0f] After pool01 released

途中Breakがあっても大丈夫です!


さて、ここからが問題。じゃあ、NSAutoreleasePool自体はどうなのさ?

残念ながらまだi368/X86_64のニーモニックとか全然分からないのでobjcランタイムで分かる事だけ。

まず、NSAutoreleasePoolのメソッドを乗っ取ります。

@implementation NSAutoreleasePool (hoge)
+ (void)load
{
    if(self == [NSAutoreleasePool class]) {
        Method method01 = class_getClassMethod(self, @selector(allocWithZone:));
        Method method02 = class_getClassMethod(self, @selector(allocWithZoneA:));
        method_exchangeImplementations(method01, method02);
        
        method01 = class_getInstanceMethod(self, @selector(init));
        method02 = class_getInstanceMethod(self, @selector(initA));
        method_exchangeImplementations(method01, method02);
        
        method01 = class_getInstanceMethod(self, @selector(release));
        method02 = class_getInstanceMethod(self, @selector(releaseA));
        method_exchangeImplementations(method01, method02);
        
        method01 = class_getInstanceMethod(self, @selector(drain));
        method02 = class_getInstanceMethod(self, @selector(drainA));
        method_exchangeImplementations(method01, method02);
        
        method01 = class_getInstanceMethod(self, @selector(dealloc));
        method02 = class_getInstanceMethod(self, @selector(deallocA));
        method_exchangeImplementations(method01, method02);
    }
}

+ (id)allocWithZoneA:(NSZone *)zone
{
    id res = [self allocWithZoneA:zone];
    fprintf(stderr, "Allocated NSAutoreleasePool <%p>\n", res);
    return res;
}
- (id)initA
{
    [self initA];
    fprintf(stderr, "Initialized NSAutoreleasePool <%p>\n",self);
    return self;
}
- (void)releaseA
{
    fprintf(stderr, "Release NSAutoreleasePool <%p>\n", self);
    [self releaseA];
}
- (void)drainA
{
    fprintf(stderr, "Drain NSAutoreleasePool <%p>\n", self);
    [self drainA];
}
- (void)deallocA
{
    fprintf(stderr, "Deallocated NSAutoreleasePool <%p>\n", self);
    [self deallocA];
}
@end

その上で、上のコードを実行しました。さらに、このプロジェクトはCocoaアプリケーションテンプレートを使用したので、勝手にメニューとウインドウがついてます。これをグリグリ動かしました。さらにウインドウにボタンを付けて-test01メソッドを何度か実行してみました。出力が長くなるので以下のコード(awk)で見やすくしました。

NF!=3 {
    next
}
/Alloca/{
    alloc[$3]++
}
/Init/ {
    init[$3]++
}
/Release/ {
    release[$3]++
}
/Drain/ {
    release[$3]++
}
/Dealloc/ {
    dealloc[$3]++
}
END {
    printf("Pointer----\tAlloc\tInit\tRelease/Drain\tDealloc\n")
    for(p in alloc) {
        printf("%s\t%5d\t%4d\t%13d\t%7d\n", p, alloc[p], init[p], release[p], dealloc[p])
    }
}

その結果。

Pointer----    Alloc    Init    Release/Drain    Dealloc
<0x10017bfe0>        5       5                4          0
<0x1006986d0>       15      15               15          0
<0x100180b40>        5       5                4          0
<0x10017e0a0>        5       5                4          0
<0x10068b650>        8       8                7          0
<0x1006991e0>      142     142              141          0
<0x114b6b3c0>        5       5                4          0
<0x100699210>        5       5                4          0

なんと、全然deallocされてません。
あれ?
しかも、同じインスタンスを使い回してる?

うーーん。なぞです。

ただし、アクティビティモニタで見る限りリークが発生しているので、releaseしていないNSAutoreleasePool自体はリークしているようです。

てことで、とりあえずは、確保したNSAutoreleasePoolはちゃんと解放しましょう!