連載
» 2008年11月10日 00時00分 UPDATE

Cocoaの素、Objective-Cを知ろう(3):Objective-Cのクラス定義を理解しよう (2/3)

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

メソッド定義とメッセージ式

 いままでのサンプルでも見てきたように、メソッドの定義とその呼び出し方法は、Objective-Cの大きな特徴の一つとなっています。最初のうちは戸惑うことも多いと思いますので、ここで基本的なルールを整理しておきましょう。

 メソッド名や戻り値、引数などの情報を、クラスの宣言部と実装部に記述し、実装側では{と}で囲まれたブロック内に、具体的な処理内容を記述します。ごく簡単な例を示すと以下のようになります。

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

/* クラスの宣言部 */
@interface MyClass : NSObject {
}
- (void)myMethod;
@end

/* クラスの実装部 */
@implementation MyClass
- (void)myMethod {
    // メソッドの処理を記述
    printf("Hello World.\n");
}
@end

/* メソッドの実行例 */
int main(void) {
    id test = [[MyClass alloc] init];
    [test myMethod];
}
main.m

 上記は単純なメソッドなので分かりやすいのですが、戻り値や引数がある場合はもっと複雑になります。以下にいくつかのパターンを、メソッドの宣言と実行の部分に着目して整理してみましょう(「↓メソッドの実行」の部分は、"test"が当該クラスのインスタンス(実体)を表す変数であると想定して読んでください)。

a. 戻り値も引数もないメソッド

    - (void)myMethod1;

    ↓メソッドの実行例
    [test myMethod1];

b. 戻り値があって引数がないメソッド

    - (NSString *)myMethod2;

    ↓メソッドの実行例
    NSString *result = [test myMethod2];

c. 引数が1つあって戻り値がないメソッド

    - (void)myMethod3:(NSString *)argString;

    ↓メソッドの実行例
    [test myMethod3:@"test"];

d. 引数が1つと戻り値があるメソッド

    - (NSString *)myMethod4:(NSString *)argString;

    ↓メソッドの実行例
    NSString *result = [test myMethod4:@"test"];

e. 引数が2つと戻り値があるメソッド

    - (NSString *)myMethod5:(NSString *)argString
                  myInt:(int)argInt;

    ↓メソッドの実行例
    NSString *result = [test myMethod5:@"test" myInt:5];

 aは特に迷うところはないでしょう。bでは、戻り値(ここではNSString型)を受け取っています。

 cでは、メッセージ式のメソッド名の後に、「:(コロン)」に続けて引数を指定しています。これはメソッドの定義側の記述と対応していることが分かります。dは、bとcが分かれば特に迷うところはないはずです。

 最も特徴的なのは、eでしょう。第2引数に注目してください。int型のargIntという引数を受け取っていることは分かると思います。では、:(コロン)の前に記述されている「myInt」は何を表しているのでしょうか。

 Objective-Cでは、メソッドの引数の1つ1つに、メッセージ式で利用するためのキーワード(メッセージキーワード)を指定することができます。メッセージキーワードは、上記のmyIntのように:(コロン)の前に記述します。そして、それらすべてのキーワードをつないだものが、そのメソッドのメソッド名ということになります。

 上記eのメソッド名は、「myMethod5:myInt:」です。メソッド名には:(コロン)も含まれるため、aのメソッド名は「myMethod1」、cのメソッド名は「myMethod3:」ということになります。

 eの「メソッドの実行例」の部分を見てください。

    [test myMethod5:@"test" myInt:5];

 第1引数に「myMethod5」、第2引数に「myInt」というメッセージキーワードが指定されており、それぞれの引数が何を意味しているのかが明確になっています。上記の例は機械的なネーミングなので実感しにくいかもしれませんが、例えば以下のようなメソッドではどうでしょう。

    - (void)setWidth:(int)argWidth
            height:(int)argHeight
            depth:(int)argDepth;

    ↓メソッドの実行例
    [test setWidth:5 height:6 depth:7];

 これだと、メソッド実行のメッセージ式を見ただけで、何のために引数を渡しているのかがよく分かります。

 これがほかの一般的なプログラミング言語の場合ですと、

    public void setSize(int w, int h, int d) {
        // ....
    }

    // メソッドの実行
    test.setSize(5, 6, 7);

 のようになるところでしょうか。これだと、メソッドの実行側だけを見ると、それぞれの引数が何を意味しているのかは分かりませんね。

 このように、Objective-Cのメソッド定義の構文は、メッセージ式の記述が明解になるように考えられています。自分でメソッドを定義するときにも、メッセージ式でどのように表現されるかをイメージしながらネーミングするといいかもしれません。

インスタンスメソッドとクラスメソッド

 ここまで、メソッド名の先頭には無条件で「-(マイナス)」の記号を付加してきましたが、実はこの記号にも意味があります。

 先頭に-(マイナス)が付加されたメソッドは、クラスのインスタンスがあって初めて実行できるメソッドで、「インスタンスメソッド」と呼ばれます。通常、ほとんどのメソッドはインスタンスメソッドとして定義することになります。

 一方、先頭の記号が「+(プラス)」となるメソッドもあります。これは、クラスのインスタンスを生成せずに、クラスの型から直接実行することが可能な「クラスメソッド」と呼ばれるメソッドです。

 クラスメソッドはやや特殊な用途のために利用されることが多く、代表的な例としては、クラスのインスタンスを生成して返すメソッドなどが挙げられます。後述するクラスの初期化手続きで利用されるallocメソッドもクラスメソッドとなっています。

プロパティとアクセサ

 先ほど、クラスのインスタンス変数の有効範囲は、デフォルトでprotected(そのクラス自身またはそのクラスのサブクラスからしかアクセスできない)であり、メソッドを通してアクセスさせるのが一般的であると説明しました。このような、メソッドを通して公開されているprotected(またはprivate)なインスタンス変数のことを「プロパティ」と呼びます。また、プロパティにアクセスするためのメソッドを「アクセサ」といいます。

 アクセサを定義するときは、プロパティの値をセットするメソッド(セッターメソッド)と、プロパティの値を取得するメソッド(ゲッターメソッド)を、ネーミングルールに従って対で定義します。メソッド名は、プロパティ名が「myProp」だとすると、セッターメソッドは「setMyProp:」、ゲッターメソッドは「myProp」となります。例えば以下のような感じです。

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

/* クラスの宣言部 */
@interface MyClass : NSObject {
    // protectedなインスタンス変数
    int myProp;
}

// アクセサの宣言
- (int)myProp;
- (void)setMyProp:(int)value;

@end

/* クラスの実装部 */
@implementation MyClass

// アクセサの実装
- (int)myProp {
    return myProp;
}
- (void)setMyProp:(int)value {
    myProp = value;
}
@end

/* アクセサメソッドの実行例 */
int main(void) {
    MyClass *test = [[MyClass alloc] init];
    test.myProp = 5;
    int i = test.myProp;
    printf("%d \n", i);

    return 0;
}
main.m

 ゲッターメソッドは、多くの言語では「getMyProp」のように、プロパティ名の前にgetを付加する形式でネーミングされますが、Objective-Cでは、通常はプロパティ名と同じになります。

 セッターメソッドの実装は、実際にはメモリ管理を意識してもう少し複雑なロジックになる場合もあるのですが、そのあたりについては、連載の別の回で解説したいと思います。

 また、上記の「アクセサメソッドの実行例」の、インスタンス変数へのアクセス部分に注目してください。

test.myProp = 5;     // [test setMyProp:5];    と同じ
int i = test.myProp; // int i = [test myProp]; と同じ

 C言語の構造体のメンバへのアクセスのように、ドット演算子が使われています。このように書くと、まるでクラスのインスタンス変数へ直接アクセスしているように見えますが、実際には定義済みのアクセサメソッドが実行されています。

 このようなドット演算子によるアクセサメソッドの実行は、Objective-Cのバージョン2.0で導入されました。これにより、実行側のプログラムは非常に簡潔で分かりやすいものとなっています。

 ただし注意が必要なのは、ドット演算子でインスタンス変数へアクセスする場合は、主体となるオブジェクト変数(上記の例では"test"変数)はid型ではなく、そのアクセサメソッドを実装している具体的なクラス型である必要があるという点です。

 従って、上記の例のように最初からクラス型を明示して変数を生成するか、もしくは以下のように、アクセサの呼び出し時に具体的なクラス型にキャストしてあげる必要があります。

id test = [[MyClass alloc] init];
((MyClass *)test).myProp = 5;
int i = ((MyClass *)test).myProp;

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

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

メールマガジン登録

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