NSProxy

JavaC#でクラスのメッセージに対する振る舞いを動的に変更しようとした場合、必ずといって良い程お世話になるのがProxyクラスだが※、Objective-Cにもちゃんとある。
NSProxy Class Reference - Mac OS X Developer Library

NSProxy.h
@interface NSProxy <NSObject>
{
@public
    Class isa;
}
〜
@end

ヘッダはこのようにNSObjectクラスを継承せずにNSObjectプロトコルに準拠するだけになっている。実際にこのクラスをスーパクラスにして何かのクラスを書いて静的なインスタンスを生成してみれば、殆どのメソッドが実装されていないことが解るだろう。

NSProxyは他のクラスのプロキシとなるために用意されたクラスであり、プロキシに送信されたメッセージをターゲットに送ってやることが目的だからだ。(なので単独でで使うことはまずない)

Objective-Cではインスタンスのメソッドを呼び出そうとすると、そのメソッドが実装されているかどうか動的に検索する。見つからなかった場合は例外を発生させる。NSProxyにはターゲットが持つメソッドは実装されていないので、ほとんどの場合メソッド呼び出しは失敗することになるが、それを捕捉して何か処理を行った後にターゲットオブジェクトに改めて投げてやればいいのだ。

以上のことからNSProxyを使うことで他のクラスの全てのメソッドに割り込むため透過プロキシが簡単に実現できる。

DynamicProxyクラス (DynamicProxy.h, DynamicProxy.m)
#import <Foundation/Foundation.h>

@interface DynamicProxy : NSProxy
{
    NSObject *targetObject;
}
@property (strong, nonatomic) NSObject *targetObject;
@end

@implementation DynamicProxy
@synthesize targetObject;
- (NSString *)description 
{
    return [targetObject description];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
    if ( targetObject )
    {
        [invocation setTarget:targetObject];
        [invocation invoke];
    }
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    NSMethodSignature *result;
    if ( targetObject ) 
    {
        result = [targetObject methodSignatureForSelector:sel];
    }
    else
    {
        result = [super methodSignatureForSelector:sel];
    }
    
    return result;
}
@end

使い方としては、ターゲットのオブジェクトをプロキシのtargetObjectにセットして、後はtargetObjectと同じ型として使うだけである。

    Dto* dto = [[Dto alloc] init];
    id proxy = [DynamicProxy alloc];
    [proxy setTargetObject:dto];
    Dto* proxiedDto = (Dto*)proxy;

以降、proxiedDtoは送られたどんなメッセージでも全てプロキシの-forwardInvocation:メソッドを経由してターゲットオブジェクトに送信されるので、プロキシの同メソッドをオーバライドすることでターゲットオブジェクトの処理の前後に処理を挿入することが出来る。

- (void)forwardInvocation:(NSInvocation *)invocation
{
    if ( targetObject )
    {
        {
            /*メソッドの実行前に何か処理*/
        }
        [invocation setTarget:targetObject];
        [invocation invoke];
        {
            unsigned int length = [[invocation methodSignature] methodReturnLength];
            void *buffer = (void *)malloc(length);
            [invocation getReturnValue:&buffer];
            /*メソッドの実行後に何か処理*/
        }
    }
}

さて、面白くなってきたぞ。

C#の場合はSystem.Runtime.Remoting.Proxies.RealProxy、Javaの場合はjava.lang.reflect.Proxyを指す