Objective-Cのクラスとメソッド

クラス定義と多態

C++とObjective-Cで、クラスを定義してみる。

C++はC言語の構造体 (struct) を拡張してクラスをつくり、インスタンスへのポインタでオブジェクトを指すことにした。Objective-Cは、オブジェクトを指すid型を導入した。

C++での例

次の例はC++で書いた多態 (polymorphism) の例。C++では派生クラス(サブクラス)Bar のメソッドを呼ぶためには、メソッドにvirtualを付けなければならない。

C++
[RAW]
  1. #include <stdio.h>
  2. struct Foo {
  3. int val;
  4. Foo(int v): val(v) {}
  5. virtual ~Foo() {}
  6. virtual void add(int a) { val += a; }
  7. int value() const { return val; }
  8. };
  9. struct Bar: public Foo {
  10. Bar(int v): Foo(v) {}
  11. virtual ~Bar() {}
  12. virtual void add(int a) { val -= a; }
  13. };
  14. int main() {
  15. Foo* p = new Bar(10);
  16. p->add(10);
  17. printf("%d\n", p->value());
  18. return 0;
  19. }

Objective-Cでの例

次に、Objective-Cで上記のC++での内容とほぼ同じものを書いてみる。

ヘッダファイルの拡張子は.h、実装は.mにしなければならない。

Objective-Cではクラス宣言とクラス定義が明確に分かれている。クラス宣言は、@interface 〜 @end のなかにインスタンス変数宣言、メソッド宣言を書く。Cのソースのなかでかなり浮いて見える。。

objc
[RAW]
  1. @interface クラス名: スーパークラス
  2. {
  3. インスタンス変数宣言
  4. }
  5. メソッド宣言
  6. @end

ルートクラスは、gccを使う場合は、Objectとなる。<objc/Object.h>で宣言されている。

メソッド宣言は、{ }の外で行う。宣言の書き方は、C++と、あるいはCの関数宣言ともずいぶん異なる。

- (戻り値の型) メソッド名 :(引数の型) 引数 ... ;

先頭の「-」はインスタンスメソッドをあらわす。クラスメソッドのときは「+」とする。戻り値の型、引数の型は( )で囲む。また、引数の前には「:」を付ける。型を省略したときはid型とみなされる。

次のソース(部分)は、クラスFooを宣言する。

objc
[RAW]
  1. #include <objc/Object.h>
  2. @interface Foo: Object {
  3. int val;
  4. }
  5. -init :(int) v;
  6. -add :(int) v;
  7. -(int) value;
  8. @end

クラスの実装は、@implementation 〜 @end のなかに書く。通常は、クラス宣言はヘッダファイルで、実装は.mファイルでする。

objc
[RAW]
  1. @implementation クラス名
  2. メソッド定義
  3. @end

メソッドの定義部は、普通のC言語の関数のように書けばいい。戻り値の型を省略したときはid型になるので、自分(インスタンス)自身を指すself を返すようにすればいい。

objc
[RAW]
  1. @implementation Foo
  2. -init :(int) v { val = v; return self; }
  3. -add :(int) v { val += v; return self; }
  4. -(int) value { return val; }
  5. @end

さきほどのC++と同様に、派生クラスを作ってみる。Objective-Cでは、メソッドを追加せずにオーバーライドするだけなら、@interface 〜 @end にメソッドを書く必要はない。@implementation で新しい定義を書くだけでいい。

objc
[RAW]
  1. @interface Bar: Foo {}
  2. @end
  3. @implementation Bar
  4. -add :(int) v { val -= v; return self; }
  5. @end

このようにして定義したクラスのインスタンスを生成し、メソッドを呼び出すには、次のように書く。C言語の関数呼び出しとえらく違いすぎて、ここでも浮いた感じになってしまうが。

[ レシーバ メソッド :引数 ... ]

インスタンスを生成するには、allocクラスメソッドを使う。

objc
[RAW]
  1. int main() {
  2. id obj = [[Bar alloc] init :20];
  3. [obj add :10];
  4. printf("%d\n", [obj value]);
  5. return 0;
  6. }

メソッド呼び出しの解決

(2003.10.03追加。)

Objective-Cでは、C++と違い、呼び出すメソッドの解決を動的に行う。

C++(あるいはJavaでも)では、複数のクラスのインスタンスを操作する場合には、インターフェイスクラスをひとつ作って、具象クラスはそこから派生させる。これは、C++ではコンパイル時にクラス、メソッド名の解決をするため。

Objective-Cでは、同じメソッドを持つオブジェクトであれば、どんなクラスのオブジェクトであっても同じように扱うことができる。(Duck Typing)

次のソースは、クラスFooとクラスBarで同じ名前のメソッドmethod_aを定義している。FooとBarのあいだには、共通の親クラスは(ルートクラスであるObject以外には)ない。関数pでは、Foo、Barのいずれのインスタンスを渡されても、正しくそれぞれのクラスのメソッドを呼び出すことができる。

また、Bazクラスにはmethod_aメソッドがない。しかし、コンパイル時にはエラーにならず、実行時に次のエラーを出してSEGVする。

error: Baz (instance)
Baz does not recognize method_a

このあたり、むしろRubyなどのスクリプト言語に近い印象がある。

 15| #include <objc/Object.h>
 16| 
 17| @interface Foo: Object {} -method_a; @end
 18| @implementation Foo
 19| -method_a { printf("Foo's method\n"); return self; }
 20| @end
 21| 
 22| @interface Bar: Object {} -method_a; @end
 23| @implementation Bar
 24| -method_a { printf("Bar's method\n"); return self; }
 25| @end
 26| 
 27| @interface Baz: Object {} @end
 28| @implementation Baz
 29| @end
 30| 
 31| void p(id obj) { [obj method_a]; }
 32| 
 33| int main() {
 34|     id obj = [Foo alloc]; p(obj);
 35|     obj = [Bar alloc]; p(obj);
 36|     obj = [Baz alloc]; p(obj);
 37|     return 0;
 38| }