NSAutorelease#addObject:

以前作った TestPool クラスでは、自分が生成した自動解放プールでなければ autorelease されたオブジェクトを知ることができなかった。そこでランタイムAPI の class_replaceMethod を使って、addObject メソッドの中でログを出力するようにしてみた。

static IMP originalImp;

id TEST_addObjectImp(id self, SEL _cmd, id anObject)
{
  NSLog(@"replaced imp was called\n");
  NSLog(@"%s#%s\n", object_getClassName(self), sel_getName(_cmd));
  NSLog(@"arg = class %s: %@\n", object_getClassName(anObject), anObject);
  return originalImp(self, _cmd, anObject);
}

BOOL TEST_replaceMethod(Class replaceClass)
{
  originalImp = class_replaceMethod([NSAutoreleasePool class], @selector(addObject:),
                                    (IMP)TEST_addObjectImp, "v@:@");
  NSLog(@"replace '%s#addObject:' with 'TEST_addObjectImp'\n",
        object_getClassName(replaceClass));
  return originalImp != NULL;
}

int main (int argc, const char * argv[])
{
  NSAutoreleasePool * pool;
  id anObject;
  BOOL succ;

  succ = TEST_replaceMethod([NSAutoreleasePool class]);
    // --> replace 'NSAutoreleasePool#addObject:' with 'TEST_addObjectImp'
  pool = [[NSAutoreleasePool alloc] init];
  NSLog(@"replace result: %s\n", succ ? "YES" : "NO");
    // --> replace result: YES
  [NSString stringWithUTF8String:"<autorelease>"];
    // 交換したメソッドが呼び出されない
  [[[NSObject alloc] init] autorelease];
    // 交換したメソッドが呼び出されない
  anObject = [[NSObject alloc] init];
  [pool addObject:anObject];
    // --> replaced imp was called
    //     NSAutoreleasePool#addObject:
    //     arg = class NSObject: <NSObject: 0x1065c0>
    // addObject:を直接指定するとさすがに呼び出される
  [pool drain];
  return 0;
}

いったい何がおこっているのだろうか。

ここで NSAutoreleasePool を導出してサブクラス TestAutoreleasePool をつくって、そのインスタンスを作るとなぜかリプレースした addObject: が呼び出される。

@interface TestAutoreleasePool : NSAutoreleasePool
  // 何も継承しない
@end

@implementation TestAutoreleasePool
@end

...

  TEST_replaceMethod([NSAutoreleasePool class]);
    // --> replace 'NSAutoreleasePool#addObject:' with 'TEST_addObjectImp'
  pool = [[TestAutoreleasePool alloc] init];
  [NSString stringWithUTF8String:"<autorelease>"];
    // --> replaced imp was called
    // --> TESTAutoreleasePool#addObject:
    // --> arg = class NSCFString(<autorelease>)
  [[[NSObject alloc] init] autorelease];
    // --> replaced imp was called
    // --> TESTAutoreleasePool#addObject:
    // --> arg = class NSObject(<NSObject: 0x1065c0>)
...