メソッド


アクセッサメソッド

クラスの宣言と定義を正しく行い、Object ルートクラスの alloc メソッドを呼び出すと、クラスのインスタンスが生成されます。 クラスの定義は構造体の宣言のようなもので、インスタンスの生成するということは、構造体型の変数を定義して構造体を表現するために必要なメモリを割り当てる行為に似ています。 ただし、クラスはメンバ変数とそれを処理する専用の関数を関連付けている点で、構造体と大きく異なります。

前回で、クラスを宣言し、メソッドを定義してそれを呼び出すということを行いました。 前回のメソッドは引数や戻り値をやり取りしない極めて単純なものでしたが、この場ではより複雑なメソッドを実現するために、メソッドの詳細を解説します。 特に、メソッドは関数とは異なり、設計論の概念からインスタンスやクラスの役割に関連付けられなければなりません。

例えば、2次元座標の点を表す Point クラスを作成するとします。 この場合、Point クラスは座標 X と Y を提供しなければなりません。 Point クラスは int 型のインスタンス変数 x と y を宣言し、これらの値を制御するメソッド郡を提供する必要があります。

原則として、クラスのインスタンス変数にはクラスの外部からはアクセスすることができません。 なぜならば、変数が自由に変更されてしまうと、変数の意味や入出力するデータの仕様が変更された場合など、保守性やコード全体の柔軟性が低下してしまうためです。 そこで、Objective-C では変数にアクセスするためにメソッドを使います。 Point クラスの基本的な宣言は、次のようになるでしょう。

@interface Point : Object
{
	int x, y;
}
- (void)setPoint:(int)ptx:(int)pty;
- (int)getX;
- (int)getY;
@end

このクラスの宣言では、座標を保存するための変数 x と y を宣言し、さらにこの変数にクラスの外部からアクセスするためのメソッドを宣言しています。 setPoint メソッドは、x と y の値を変更するためのメソッドで、getX と getY メソッドは、それぞれの変数を取得するためのメソッドです。 このようなメソッドを、特にアクセッサメソッドと呼びます。 また、このようにインスタンスの性質に関する外部に提供するための情報を、特にプロパティと呼ぶこともあります。

引数を受け取るメソッドは、引数型を ( ) の中に指定し、その次に仮引数の名前を指定します。 このときの ( ) はキャスト式と同様で、型を指定しない場合は戻り値と同様に id 型がデフォルトとされています。 複数の引数がある場合、さらにコロン : に続けて指定します。 より具体的には、メソッドの宣言は次のようになります。

- (戻り値型)メソッド名 : (引数型) 変数名 ラベル : ....

複数の引数を受け取るメソッドは、変数名の後にラベルを指定することがあります。 比較的、Objective-C の世界ではラベルを指定することが習慣となっていますが、このラベルとコロンはメソッド名の一部として認識されるのです。

ラベルとコロンが名前の一部として認識されているため、Objective-C では同じ戻り値、引数、メソッド名を持つメソッドを区別して呼び出すことができるのです。 例えば (void)setPoint:(int):(int) というメソッドと (void)setPoint:(int) label:(int) メソッドは異なるメソッドとして宣言、定義することができます。

引数付きのメソッドを呼び出すには、メッセージ式でメソッド名に続いて引数に渡す値を指定します。 ラベルを指定していない前者の setPoint の場合 [obj setPoint:x:y] という形で呼び出すのに対し、ラベル付きのメソッドは [obj setPoint:x label:y] という形で呼び出します。 引数の数やラベルさえ異なれば、メソッド名が同じでも区別して呼び出すことができるという仕組みです。 特定の機能を提供するメソッドを、様々な型に対応して提供したい場合にラベルが必要となります。 setPoint メソッドを、int 型以外の整数にも対応させたい場合などは、重要でしょう。

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

@interface Point : Object
{
	int x, y;
}
- (void)setPoint:(int)ptx int:(int)pty;
- (int)getX;
- (int)getY;
@end

@implementation Point
- (void)setPoint:(int)ptx int:(int)pty {
	x = ptx;
	y = pty;
}
- (int)getX {
	return x;
}
- (int)getY {
	return y;
}
@end

int main() {
	id point1 , point2;
	point1 = [Point alloc];
	point2 = [Point alloc];

	[point1 setPoint:16 int:32];
	[point2 setPoint:256 int:128];

	printf("point1:X=%d, Y=%d\n", [point1 getX] , [point1 getY]);
	printf("point2:X=%d, Y=%d\n", [point2 getX] , [point2 getY]);
	return 0;
}

このプログラムの Point クラスは、2 つの整数型引数を受け取る setPoint:int: メソッドと、設定されている座標を返す getX、getY メソッドを宣言しています。 setPoint メソッドの宣言は setPoint(int)ptx:(int)pty でも問題ありませんが、将来 setPoint を拡張することを考えた場合は、ラベルを指定するべきでしょう。

main() メソッドでは alloc メソッドを用いて Point クラスのインスタンスを 2 つ生成してます。 point1 変数と point2 変数が指すインスタンスは異なっているため、それぞれのインスタンスに設定した値は、個別のメモリに保存されていることが、出力結果から確認することができます。


暗黙の self

メソッドのスコープ範囲内には、仮引数や宣言された変数以外に、暗黙の変数としてselfが定義されています。 self 変数は id 型で、常にメソッドを呼び出したインスタンスを参照しています。 つまり、メソッドを実行しているオブジェクト自信を表す変数が self なのです。

self はメソッド以外の場所では使えず、常にメソッドん実行コード部分でのみ暗黙低に存在しています。 self を利用することによって、オブジェクトのインスタンス変数にオブジェクトから参照することができます。 これは、メソッドの仮引数の変数名がインスタンス変数名を隠蔽してしまったときなどに利用することができます。 アクセッサメソッドでは、特に重要な存在となるでしょう。

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

@interface Point : Object
{
	int x, y;
}
- (void)setPoint:(int)ptx int:(int)pty;
- (int)getX;
- (int)getY;
@end

@implementation Point
- (void)setPoint:(int)x int:(int)y {
	self->x = x;
	self->y = y;
}
- (int)getX {
	return x;
}
- (int)getY {
	return y;
}
@end

int main() {
	id point1 , point2;
	point1 = [Point alloc];
	point2 = [Point alloc];

	[point1 setPoint:32 int:64];
	[point2 setPoint:256 int:128];

	printf("point1:X=%d, Y=%d\n", [point1 getX] , [point1 getY]);
	printf("point2:X=%d, Y=%d\n", [point2 getX] , [point2 getY]);
	return 0;
}

このプログラムの setPoint:int: メソッドは、仮引数で宣言した名前がインスタンス変数 x と y に衝突しています。 これ自体は問題ではありませんが、このメソッドのスコープ範囲ではインスタンス変数が隠蔽されてしまいます。 そこで、インスタンス変数にアクセスする手段として、現在のメソッドを実行しているオブジェクトを参照する self 変数を使っています。 クラスの外部からインスタンス変数に参照することはできませんが、メソッドはクラスの内部なので、オブジェクトから直接インスタンス変数にアクセスすることができるのです。 プログラムでは self->x という形で、インスタンス変数に渡されたデータを保存しています。

また、メソッドから同じインスタンスの異なるメソッドを呼び出す場合にも self が必要になります。 いくつかのメソッドが、機能の一部を共有している設計などでは、同一クラスの他のメソッドを呼び出すことが必要になることがあります。

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

@interface Test : Object
- (void)methodA;
- (void)methodB;
@end

@implementation Test
- (void)methodA {
	printf("method A\n");
	[self methodB];
}
- (void)methodB {
	printf("method B\n");
}
@end

int main() {
	[[Test alloc] methodA];
	return 0;
}

このプログラムの Test クラスでは、methodA メソッドから methodB メソッドを self オブジェクトを使って呼び出しています。 self は methodA を実行しているインスタンスなので、同一インスタンスの methodB を呼び出しているということになります。



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