BBAsyncTask その2

さて、前回はBBAsyncTaskProtocolを定義したので今回はその実装を書いてみる。

ポイントは、実装されるかどうか判らない前処理と後処理をどのように呼び出すのか。プロトコルとして制約しているのは-executeInBackgoundだけであり、このメソッドが呼ばれたことを処理の実行前に検知して前処理と後処理を挿入する必要がある訳だ。
また、非同期処理をどのように実装するかもポイントになるだろう。

BBAsyncTaskProtocol.h
@protocol BBAsyncTaskProtocol <NSObject>
@required
 - (id)executeInBackgound:(id)param; 
@optional
 - (void)preExecute:(id)param;
 - (void)postExecute:(id)executeResult;
@end

用意したプロトコルBBAsyncTaskProtocolは必須と任意のメソッドがあったが、使う側は必須だけを実装することが求められる。しかし任意のメソッドを実装した場合、それがプロトコル側からコールバックよろしく呼ばれる仕組みが必要になる。


この仕組みを提供するためにはCocoaで多用されている以下のパターンのどちらかを使うのが良いだろう。

・デリゲート
・プロキシ

デリゲートは対象となる処理の委譲先を設定できる口を用意しておき、実際の処理は予めセットされたデリゲート対象のオブジェクトに任せる。プロキシは外部には処理を委譲せず、内部でメソッド呼び出しを捕捉して処理を制御する。

Cocoaではデリゲートが使われることが多いが、後々考えていることもあるので敢えてプロキシで実装することにする。

プロキシの実装

以前にも書いたがCocoa/FoundationにはNSProxyクラスが既にあり、これを継承したクラスを作ることで対象のオフジェクトのメッセージ(メソッド呼び出し)を全て捕捉することができる。

BBDynamicProxy.h
#import <Foundation/Foundation.h>
@interface BBDynamicProxy : NSProxy
@property (strong, nonatomic) id targetObject;

@end
BBDynamicProxy.m
#import "BBDynamicProxy.h"

@implementation BBDynamicProxy
@synthesize targetObject;
- (void) dealloc
{
    targetObject = nil;
}
- (NSString *)description 
{
    return [targetObject description];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
    if ( targetObject )
    {   
        [invocation setTarget:targetObject];
        [invocation invoke];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return ( targetObject ) 
       ? [targetObject methodSignatureForSelector:sel]
       : [super methodSignatureForSelector:sel];
}
@end

このプロキシはtagetObjectにセットしたオブジェクトの全てのメッセージを捕捉することが出来るので、結果としてtagetObjectと同等のオブジェクトとして扱うことできる。

BBAsyncTaskクラスは初期化時に内部クラスである_MethodProxyのインスタンスを生成してこれを返すことで、メソッドに割り込み済みのオブジェクトをサービスする。

BBAsyncTask.h
#import "BBAsyncTaskProtocol.h"

@interface BBAsyncTask : NSObject<BBAsyncTaskProtocol>
{
    _MethodProxy* _proxy;
}
@end
BBAsyncTask.m

#import "BBAsyncTask.h"

@interface _MethodProxy : BBDynamicProxy
- (id)initWithTarget:(id)target;
@end
@implementation _MethodProxy
- (id)initWithTarget:(id<BBTaskProtocol>)target
{
    BOOL conform = [target conformsToProtocol:@protocol(BBTaskProtocol)];
    NSAssert( conform, @"targetObject must be conforms BBTaskProcol"); 
    self.targetObject = target;
    return self;
}
〜

@implementation BBAsyncTask
- (id)init
{
    self = [super init];
    _proxy = [[_MethodProxy alloc] initWithTarget:self];
    return (BBAsyncTask*)_proxy;
}

}
〜
@end

これでBBAsyncTaskクラスは全てのメッセージを捕捉できるようになった。
次回はプロキシ内部の実装を見ていこう。