poseAsClass and method_exchangeImplementations

昨日の続き。クラスのポージングを使ってみた。

@interface TESTAutoreleasePool : NSAutoreleasePool
@end

@implementation TESTAutoreleasePool
+(void)load
{
  if (self != [TESTAutoreleasePool class])
    return;
  NSLog(@"%s pose as NSAutoreleasePool\n", class_getName(self));
  [self poseAsClass:[NSAutoreleasePool class]];
}

-(void)addObject:(id)anObject
{
  NSLog(@"posing method was called\n");
  NSLog(@"method = %s#%s\n", object_getClassName(self), sel_getName(_cmd));
  NSLog(@"arg = class %s (%@)\n", object_getClassName(anObject), anObject);
  [super addObject:anObject];
}
@end

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

    // --> TESTAutoreleasePool pose as NSAutoreleasePool
  pool = [[NSAutoreleasePool alloc] init];
  [NSString stringWithUTF8String:"<autorelease>"];  // 何も表示されない
  [[[NSObject alloc] init] autorelease];            // 何も表示されない
  anObject = [[NSObject alloc] init];
  [pool addObject:anObject];
    // --> posing method was called
    //     method = NSAutoreleasePool#addObject:
    //     arg = class NSObject(<NSObject: 0x1068f0>)
  [pool drain];
  return 0;
}

残念ながらポージングでも NSAutoreleasePool#addObject: を乗っ取ることはできなかった。

しかしポージングは Objective-C では Deprecated である。そのかわり method_exchangeImplementations を使うことができる。

@interface NSAutoreleasePool (TESTAutoreleasePool)
-(void)TEST_addObject:(id)anObject;
@end

@implementation NSAutoreleasePool (TESTAutoreleasePool)
+(void)load
{
  if (self == [NSAutoreleasePool class])
    {
      Method originalMethod = class_getInstanceMethod(self, @selector(addObject:));
      Method replaceMethod = class_getInstanceMethod(self, @selector(TEST_addObject:));
      method_exchangeImplementations(originalMethod, replaceMethod);
      NSLog(@"method %s was exchanged with %s\n",
      method_getName(originalMethod), method_getName(replaceMethod));
    }
  else
    {
      NSLog(@"load %s\n", class_getName(self));
    }
}

-(void)TEST_addObject:(id)anObject
{
  NSLog(@"exchanged method was called\n");
  NSLog(@"method = %s#%s\n", object_getClassName(self), sel_getName(_cmd));
  NSLog(@"arg = class %s(%@)\n", object_getClassName(anObject), anObject);
  [self TEST_addObject:anObject];
}
@end

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

    // --> method addObject: was exchanged with TEST_addObject:
  pool = [[NSAutoreleasePool alloc] init];
  [NSString stringWithUTF8String:"<autorelease>"];  // 何も表示されない
  [[[NSObject alloc] init] autorelease];            // 何も表示されない
  anObject = [[NSObject alloc] init];
  [pool addObject:anObject];
    // --> exchanged method was called
    //     TestAutoreleasePool[2008:10b] method = NSAutoreleasePool#addObject:
    //     arg = class NSObject(<NSObject: 0x106620>)
  [pool drain];
  return 0;
}

これでも addObject: を乗っ取れないかという感じである。想像するに NSObject の autorelease メソッドで何かやっているのではないか。自動解放プールのインスタンスが NSAutoreleasePool かそのサブクラスかによって、addObject: でメッセージを送るか独自の仕組みを使うか。さすがにそれは検証できないなあ。