前回はProxyパターンを紹介したので、今回はCocoaでの実例の解説だ。Proxyパターンを使っているクラスと言えば、これはもう言わずもがなのNSProxyクラスである。このクラスはとても興味深いものなので、じっくり解説していこう。

スレッド間通信とProxyパターン

NSProxyクラスは、Cocoaのオブジェクトでスレッド間通信、プロセス間通信を行うときの入り口となるものである。

はじめにスレッド間通信およびプロセス間通信について、簡単に説明しておこう。現在のほとんどのOSでは、あるアプリケーション(プロセス)の中で、並行して同時に複数の処理を走らせることができる。この処理の単位をスレッドと呼ぶ。

複数のスレッドが動いているといっても、それらがバラバラに動いているのでは意味がない。お互いに協調して動作することで、きちんと処理を行うことができるのだ。そのために、異なるスレッド、あるいは異なるプロセスに存在するオブジェクトを呼び出したい。

しかし、それはなかなか難しい。まず、プロセスが違う場合は、異なるメモリ空間にあることになる。それでは、オブジェクトの参照をアドレスでもらっても、それを直接呼び出す訳にもいかない。異なるスレッド上にあるオブジェクトの場合は、アドレスの問題は考えなくてもいいが、その代わりにスレッドの動作に悪影響を与えないよう、スレッドの同期を気にしなくてはいけない。

こういったことを解決するために、Cocoaではスレッド間、プロセス間でのコネクションを提供している。コネクションが、上記の問題を吸収してくれるのだ。そして、コネクションを通した向こうのターゲットのオブジェクトを呼び出すために、こちらで用意しておくのがプロキシになる。このプロキシオブジェクトのおかげで、コネクションのことを気にせずに、直接ターゲットを呼び出すのと同じ感覚で使えることになる。

プロキシオブジェクトによってコネクションが隠蔽される

これがNSProxyが提供する、Proxyパターンとしての機能だ。GoF本の分類によれば、remote proxyということになる。

NSProxyクラス

そんなNSProxyだが、このクラスの定義はCocoaの中でも非常にユニークなものとなっている。

実は、NSProxyはルートクラスである。CocoaのルートクラスといえばNSObjectであるが、NSProxyはこのクラスを継承していない。

NSProxyのヘッダを見てみよう。次のような宣言になっている。

List 1. NSProxy.h

@interface NSProxy <NSObject> {
    Class   isa;
}

...

@end

NSObjectクラスは継承していないが、NSObjectプロトコルに準拠している。これはどういうことだろう?

NSObjectプロトコルでは、NSObjectが持っているメソッドのうち、Cocoaのオブジェクトとして必要な最低限のメソッドを定義している。たとえば、オブジェクトの同値性を調べるisEqual:メソッド、メモリ管理に使われるretain、relase、autoreleaseメソッドなどだ。これらを実装することで、NSObjectを継承していないながらも、通常のオブジェクトとして配列に入れたりすることができるようになる。

なぜNSObjectを継承していないかというと、NSProxyの仕事はターゲットとなるオブジェクトにメッセージを送信することのみだからだ。自分で処理をすることは、ほとんどない。

NSProxyは、すべてのクラスのProxyになる

さて、Proxyパターンが提供しなくてはいけない機能は、ターゲット(またはrealオブジェクト)へメッセージを転送することだ。NSProxyがどうやってこれを行っているか見てみよう。

前回Proxyパターンの実装例を紹介したが、そのときはProxyクラスとRealSubjectクラスとで同じメソッドを実装していた。Proxyクラスのそれぞれのメソッドで、対応するRealSubjectクラスのメソッドを呼び出していた。

NSProxyでも同じことをする必要があるのだろうか? ターゲットクラスが持っているメソッドを、わざわざ再実装しなくてはいけないのだろうか?

いや、そんな必要はない。実は、1つのNSProxyクラスで、すべてのターゲットクラスに対応できるのだ。

そのからくりは、Objective-Cのメッセージ送信機構の中にある。Objective-Cでは、メソッドを呼び出そうとすると、そのメソッドが実装されているかどうか動的に検索する。見つからなかった場合は、例外を発生させる。これを利用するのだ。NSProxyにはターゲットが持つメソッドは実装されていないので、ほとんどの場合メソッド呼び出しは失敗することになる。そこで、検索で使われるメソッドを上書きして、それをターゲットオブジェクトに投げてやればいいのだ。

上書きされるのは、forwardInvocation:とmethodForSignatureForSelector:のメソッドである。

List 2.

- (void)forwardInvocation:(NSInvocation*)anInvocation
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector

forwardInvocation:は、Objective-Cランタイムがメソッドの検索をしても、対象となるメソッドが見つからなかった場合に呼び出されるものだ。ここで、失敗したメソッドを他のオブジェクトに投げるチャンスが与えられる。methodSignatureForSelector:は、NSInvocationのためにNSMethodSignatureオブジェクトを作るものだ。

これらを使うとProxyパターンの動作は、次のように実現できる。Proxyクラスが、realObjectという名前のRealSubjectへの参照を持っているとする。上記のメソッドを次のように上書きしてやればいい。

List 3.

- (void)forwardInvocation:(NSInvocation*)anInvocation
{
    [anInvocation setTarget:realObject];
    [anInvocation invoke];
    return;
}

- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
    return [realObject methodSignatureForSelector:aSelector];
}

これで、Proxyに投げられたすべてのメソッドがRealSubjectクラスへと転送されることになる。Proxy側でRealSubjectのメソッドを実装する必要はまったくなくなる訳だ。

これが、Objective-Cのような動的な言語の利点を活かしたProxyパターンの実現方法だ。ランタイムの動作の肝となる部分を押さえて、非常にスマートに実現できている。

次回は、実際のスレッド間通信、プロセス間通信について解説しよう。

提供:毎日キャリアバン ク

毎日キャリアバンクではITエンジニア出身のキャリアコンサルタントで形成する IT専門のチームを編成し、キャリアに応じた専任コンサルタントがご相談を承り ます。キャリアチェンジから市場価値の可能性、ご収入などの相談から面接のア ドバイスまでお気軽にご相談ください。求人情報誌や転職情報サイトなどで一般 に公開されていないような「急募求人案件」も随時ご紹介が可能です。まずはご 登録ください!