連載
» 2009年03月06日 00時00分 公開

Cocoaの素、Objective-Cを知ろう(6):コードをもっとオブジェクティブに (2/4)

[竹下肯己,株式会社 qnote]

プロトコル

 前回、配列をループ処理する高速列挙の解説のところで、プロトコルについて少し触れました。あるプロトコルを採用することで、クラスのAPIを統一させ、そのクラスを利用する側にも、一定の規格にのっとっていることを示すことができます。

 こういった仕組みは、ほかの多くのオブジェクト指向言語ではインターフェイスと呼ばれています。Objective-Cではクラスの宣言が「@interface」ですから、最初は少し紛らわしく感じるかもしれません。ですが、プロトコルは約束事、規約などの意味があるので、クラスを一定の約束事に沿って作成するという意味では分かりやすい名称ですね。

 ごく単純な、プロトコルの利用例を見てみましょう。

#import <Foundation/Foundation.h>
#import <stdio.h>

@protocol Zukei                         // 図形プロトコル
- (void) draw;                          // 「draw」メソッドは必須
@end

@interface Maru : NSObject <Zukei>      // 採用
@end
@implementation Maru
- (void) draw { printf("○\n"); }       // 実装
@end

@interface Sankaku : NSObject <Zukei>   // 採用
@end
@implementation Sankaku
- (void) draw { printf("△\n"); }       // 実装
@end

@interface Sikaku : NSObject <Zukei>    // 採用
@end
@implementation Sikaku                  // 実装していない
@end                                    // ので警告が出る


int main(void) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    id maru = [[Maru alloc] init];
    [maru draw];

    id sankaku = [[Sankaku alloc] init];
    [sankaku draw];

    [pool drain];
    return 0;
}
main.m

 プロトコルは、「@protocol」というコンパイラディレクティブを利用して定義します。上記の例では、まず「Zukei(図形)」というプロトコルを用意しています。このプロトコルでは、「drawメソッドを実装してください」という約束事が決められています。

 プロトコルで規定できるのはここまでです。プロトコルは@implementationのような実装部を持ちませんので、メソッドの具体的な実装を記述することはできません。

 3種類の図形を表すクラス(Maru、Sankaku、Sikaku)が、Zukeiプロトコルを採用しています。必須メソッドであるdrawメソッドを、クラスごとにふさわしい内容で実装します。

 唯一、Sikakuクラスだけが、drawメソッドを実装していないため、コンパイラから警告が発せられてしまいます(ほかの多くの言語では、実装を義務付けられているメソッドが実装されていなかった場合、コンパイルエラーとなります。Objective-Cでは警告のみです)。

 上記のようなクラス構造の場合、前述の継承の仕組みで表現することもできます。Zukeiをスーパークラスとして定義するのです。図形を表すクラス群の結び付きが強く、完全に共通となる機能がたくさんある場合などは、継承がふさわしいといえます。逆に、クラスの機能や目的などにはばらつきがあるけれど、一部の作りを統一させておきたい場合などには、プロトコルの仕組みが便利です。

 また、プロトコルで規定されるメソッド群は、採用先のクラスで実装が必須になるものとそうでないものに分けることができます。プロトコルのメソッド宣言の部分に、「@optional」というコンパイラディレクティブを指定すると、そのメソッドの実装は必須ではなくなります。

 @optionalが指定されていない場合、または明示的に「@required」が指定されている場合には、そのメソッドは実装が必須となります。例えば以下のように記述します。

@protocol Zukei
    @required    // ←これはデフォルトなので省略できます
    - (void) draw;
    - (void) paint;

    @optional
    - (void) move;
    - (void) resize;
@end

プロトコル同士の継承と多重採用

 あるプロトコルが別のプロトコルを採用することもできます。この場合、もちろんプロトコル自身は何も実装せず、採用したプロトコルの約束事を引き継ぐかたちになるので、プロトコルの継承と呼ばれることもあります。

 また、プロトコルは複数採用することができます。この場合、

@interface TestCls : NSObject <ProtocolA, ProtocolB>

のように、カンマで区切って指定します。

 クラスは、直接的に採用したプロトコルだけでなく、そのプロトコルが継承しているすべてのプロトコルの約束事を守らなければなりません。以下に、プロトコルの採用関係を少しだけ複雑にした例を見てみましょう。

#import <Foundation/Foundation.h>
#import <stdio.h>

@protocol ProtocolA
- (void) testMethod1;
@end

@protocol ProtocolB
- (void) testMethod2;
@end

@protocol ProtocolC <ProtocolA, ProtocolB>
- (void) testMethod3:(int)arg;
@end

@protocol ProtocolX
- (void) testMethod3:(int)arg;
@end

@protocol ProtocolY
- (void) testMethod3:(NSString *)arg;
@end

@interface TestCls : NSObject <ProtocolC, ProtocolX>
@end
@implementation TestCls
- (void) testMethod1 { printf("testMethod1\n"); }
- (void) testMethod2 { printf("testMethod2\n"); }
- (void) testMethod3:(int)arg { printf("testMethod3 arg:%d\n", arg); }
@end


int main(void) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    id obj = [[TestCls alloc] init];
    [obj testMethod1];
    [obj testMethod2];
    [obj testMethod3:7];

    [pool drain];
    return 0;
}
main.m

 上記の例で、TestClsは ProtocolC と ProtocolX の2つのプロトコルを採用しています。ProtocolC は ProtocolA と ProtocolB を採用(継承)しているため、最終的にTestClsはtestMethod1、testMethod2、testMethod3: という3つのメソッドを実装しなければなりません。

 ProtocolC と ProtocolX では同じメソッドが宣言されていますが、これは問題ありません。プロトコルはメソッドの実装内容は問わないので、メソッド名や引数の型、戻り値の型が一致するメソッドが実装されていればいいのです。

 ただし、上記のTestClsが、ProtocolC と ProtocolX のほかに ProtocolY も同時に採用しようとすると、問題が生じます。ProtocolYの「testMethod3:」は、引数の型が、ProtocolC や ProtocolX の「testMethod3:」とは違っているからです。

 Objective-Cでは、メソッド名が同じで引数の型や戻り値の型だけが違うメソッドは、重複と見なされるので定義できません。従って、TestClsは、ProtocolC/ProtocolX と ProtocolY の約束事を同時に守ることができません。

 Objective-Cでは、引数に合わせてメソッド名も変更するのが一般的です。前回までの解説にも登場したインスタンス生成メソッド群のように、「xxxWithInt」と「xxxWithString」などと命名すれば、重複も避けられますし、利用者側にも明示的で親切になります。クラスやプロトコルの設計時にはこのあたりにも注意しておきましょう。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。