プロトコル


メソッドをクラス間で共有する

プロトコルという用語は、比較的ネットワーク用語として利用されていますが、Objective-C に限っては、複数のクラスで実装される同一の名前のメソッドを共有するための、メソッドの宣言のことを指します。 Java 言語や C# 言語ではインタフェースと呼ばれる概念に近い存在です。

プロトコルが利用されるのは、プログラムの設計上の問題で、クラスが特定のメソッドを実装していることを保障するための手段として使われます。 例えば、特定のライブラリ仕様に基づいたクラスは指定したプロトコルに準拠させなければならない、といった関係を築くことができます。 プロトコルが提供するのは宣言されたメソッドだけで、メソッドの実装がどのような処理を実行するかは自由です。

プロトコルを宣言するには @protocol コンパイラディレクティブを用います。 この中で、プロトコルが規約として指定するメソッドを宣言します。

@protocol プロトコル名 <親プロトコル1 , ...>
プロトコル本体
...
@end

プロトコルは、クラスの継承関係のように親プロトコルを指定して継承することができます。 プロトコルの継承については後ほど詳しく解説しますが、プロトコルの継承は単純に親プロトコルが定めるメソッドに、新しいメソッドを追加する意味しか持ちません。 プロトコル名は、クラス名などと同様に C 言語の命名規則に基づいてプロ言るを識別するための名前を指定します。 プロトコル本体では、メソッドの宣言だけを行うことができます。

宣言したプロトコルは、クラスに採用することができます。 プロトコルが指定されているクラスは、そのプロトコルを採用していると呼ぶことができます。 プロトコルを採用しているクラスは、そのプロトコルで宣言されているメソッドを必ず実装しなければなりません。 因みに、プロトコルで宣言されているメソッドを、プロトコルを採用するクラスの宣言部で再度宣言する必要はありません。 プロトコルをクラスに採用させるには、次のようにクラスを宣言します。

@interface クラス名 : スーパークラス名 <プロトコル1, ...>

プロトコルはスーパークラスの指定の直後に < > でプロトコル名を囲んで指定します。 スーパークラスは 1 つしか指定することができませんが、プロトコルはカンマ , で区切って複数のプロトコルを採用させることができます。 プロトコルを採用する場合、必ずプロトコルで宣言されているメソッドを @implementation 部で実装しなければなりません。 つまり、ぷろとこるを採用するということは、そのクラスがプロトコルで宣言されているメソッドの実装を保障するということなのです。

#import <stdio.h>
#import <objc/Object.h>

@protocol ClassNameToString
- (id) ToString;
@end

@interface A : Object 
{
	char *name;
}
- (id) init;
- (id) free;
@end

@interface B : Object 
@end

@implementation A
- (id) init {
	[super init];
	name = (char *)malloc(255);
	sprintf(name , "%s . A@%d" , __FILE__ , self);
	return self;
}
- (id) free {
	free(name);
	return [super free];
}
- (id) ToString { return (id) name; }
@end
@implementation B
- (id) ToString { return (id)"This is Object of B Class"; }
@end

int main() {
	id objA = [A new];
	id objB = [B new];
	printf("objA = %s\n" , [objA ToString]);
	printf("objB = %s\n" , [objB ToString]);
	[objA free];
	[objB free];

	return 0;
}

このプログラムは、クラスの名前を表す文字列を返す ToString メソッドを宣言する ClassNameToString プロトコルを宣言しています。 このプロトコルを採用する A クラスと B クラスは、必ず ToString メソッドを実装しなければなりません。 A クラスと B クラスの継承関係では何の繋がりもありませんが、プロトコルを採用することで、これらのクラスに ToString メソッドが実装されていることを確実に保障できます。

実践では、クラスの実装に依存しない完全に抽象化された型としてプロトコルが利用されるほかに、ポインタを使わずにメソッドをコールバックさせる方法としても利用されます。 特に GUI 環境のイベント処理などには、このプロトコルが使われるでしょう。

プロトコルは型として独立していませんが、型宣言で、型名の直後に < > でプロトコルを指定することで、変数が指定したプロトコルを採用していることを明示的に宣言することができます。

型名 <プロトコル名> 変数名 ...

これは、関数やメソッドの引数宣言でも同様に指定することができます。

#import <stdio.h>
#import <objc/Object.h>

@protocol InstanceListener
- (void) InstanceFree:(id)object;
@end

@interface Test : Object
{
	id <InstanceListener> listener;
}
- (id) init;
- (id) free;
- (void)SetInstanceListener:(id <InstanceListener>)l;
- (id <InstanceListener>)GetInstanceListener;
@end

@implementation Test
- (id) init {
	[super init];
	listener = NULL;
	return self;
}
- (id) free {
	if (listener) [listener InstanceFree:self];
	return [super free];
}
- (void)SetInstanceListener:(id <InstanceListener>)l {
	listener = l;
}
- (id <InstanceListener>)GetInstanceListener {
	return listener;
}
@end

@interface WriteInstanceFree : Object <InstanceListener>
@end

@implementation WriteInstanceFree
- (void) InstanceFree:(id)object {
	printf("%X:インスタンスが解放されました\n" , object);
}
@end

int main() {
	id obj1 = [Test new] , obj2 = [Test new];
	id <InstanceListener> listener = [WriteInstanceFree new];
	[obj1 SetInstanceListener:listener];
	[obj2 SetInstanceListener:listener];
	[obj1 free];
	[obj2 free];

	return 0;
}

このプログラムでは、インスタンスが解放されたタイミングで呼び出されるコールバック専門の InstanceFree メソッドを宣言する InstanceListener プロトコルを宣言しています。 Test クラスでは、このプロトコルを採用するインスタンスを SetInstanceListener メソッドから設定することができ、Test クラスのインスタンスが解放される直前にプロトコルのメソッドを呼び出します。 GUI のイベント処理はこれと同様の原理で処理されるでしょう。

変数を宣言するとき、型名の直後にプロトコルを指定すると、そのオブジェクトが指定したプロトコルを採用していなければならないということを明示します。 そのため、Test クラスの listener インスタンス変数は、確実に目的のメソッドをコールバックすることができるのです。


プロトコルの継承

プロトコルはクラスのように継承することができます。 プロトコルを継承は、クラスの継承とは性質が異なり、実際には基本となるプロトコルに新しいメソッド宣言を追加するだけです。 また、スーパークラスは必ず 1 つしか指定することができませんでしたが、プロトコルは複数のプロトコルを継承させることができます。

#import <stdio.h>
#import <objc/Object.h>

@protocol ProtocolA
- (void) MethodA;
@end

@protocol ProtocolB
- (void) MethodB;
@end

@protocol ProtocolC <ProtocolA>
- (void) MethodC;
@end

@interface Test : Object <ProtocolB, ProtocolC>
@end

@implementation Test
- (void) MethodA { printf("This is MethodA\n"); }
- (void) MethodB { printf("This is MethodB\n"); }
- (void) MethodC { printf("This is MethodC\n"); }
@end

int main() {
	id obj = [Test new];
	[obj MethodA];
	[obj MethodB];
	[obj MethodC];
	[obj free];
	return 0;
}

このプログラムの ProtocolC プロトコルは、ProtocolA プロトコルを継承しています。 そのため、ProtocolC では、MethodA と MethodC が宣言されていると考えることができます。

また、Test クラスは、ProtocolB と ProtocolC を同時に採用している点にも注目してください。 宣言しか保有していないプロトコルは、メソッド名が衝突してもプログラムのメソッド検索には影響がないため可能なのです。 ただし、異なる意図のメソッドがプロトコルの継承関係の中で衝突した場合は注意する必要があるでしょう。



前のページへ戻る次のページへ