前の章でオブジェクトに対してメソッドを呼び出す様子を見ました。しかしメソッドを呼ぶ対象となるオブジェクトは、次のような Objective-C の式として書いていました。

NSString *str = @"Hello";

これさえあれば str に対するメソッドを C の API を通して呼ぶことができたわけですが、しかしこの書き方は NSString 型のオブジェクトしか作れないし、その上コードを書く時点で内容が決まっている必要があります。つまりこのままでは、コードを書く時点で必要なオブジェクトを作っておかないとメソッドも呼ばなくなってしまいます。

しかし、コードを書く時点でどんなクラスのオブジェクトが必要になるか分からない時はどうすればいいでしょうか。コードを書く時点でどのクラスが必要になるか分からないという状況は、普段はあまりないかもしれません。しかし例えば、次のような状況を考えてみましょう。ネットワーク越しにサーバと通信するクライアントプログラムがあるとします。そしてそのクライアントは、サーバから送られて来たデータに基づいて動的にオブジェクトを生成し、メモリに保持します。その場合、ランタイム API を使わずに書くと次のようなコードになるかもしれません:

id ret;
if ([type compare: @"NSString"]) {
   ret = [NSString stringWithUTF8String: params[0]];
} else if ([type compare: @"NSNumber"]) {
   ret = [NSNumber numberWithDouble: [params[0] doubleValue]];
}

type にはデータの型、params には必要なパラメータの配列が入っていると想像してください。

Objective-C のランタイム API を使うと、実行時に決めたクラスの新しいオブジェクトを生成し、それに対してメソッドを呼ぶ事ができます。

そこでこの章では、ランタイム API を使って、C のコードで任意のクラスのオブジェクトを作る方法を紹介します。

オブジェクトの生成

Objective-C におけるオブジェクトとは、Objective-C のやり方でメソッドを呼び出すことができるデータ型だと言えます。そしてオブジェクトとはクラスのインスタンスでもあります。

Objective-C では、クラスのインスタンスを生成するには次のように書きます:

NSString *str = [NSString stringWithUTF8String: "hello"];

Objective-C のリファレンスでは、NSString はクラスで、stringWithUTF8String:クラスメソッドとして扱われています。

これをオブジェクトに対するメソッド呼び出しの書き方にあてはめると、NSString という名前のオブジェクトにの stringWithUTF8String: というメソッドを呼んでいるように見えます。Objective-C で推奨されているコーディングスタイルでは、クラスの名前は大文字で始め、オブジェクトの名前は小文字で始めるという決まりになっています。したがって、このコードを見れば NSString はクラス名であることがわかりますが、実際にはそのような命名規則は紳士協定でしかないので、決まりを破ることもできます。つまり、NSString という名前のオブジェクトを定義することもできてしまいます。

そうなると、コードを見ただけでは、クラスに対するクラスメソッドの呼び出しと、オブジェクトに対するメソッドの呼び出しは見分けがつかないわけです。しかし実は、ランタイム API のレベルではこれらの間には大きな違いはありません。それを以下で見ていきましょう。

同じ内容の動作をランタイム API で書くと次のようになります:

id cls = objc_getClass("NSString");
SEL sel = sel_registerName("stringWithUTF8String:");
NSString *str = objc_msgSend(cls, sel, "hello");

1 行目にでてくる objc_getClass という関数は次のようにプロトタイプ宣言されています:

id objc_getClass(const char *name);

クラス名を C の文字列として受け取って、それに対応するクラスオブジェクトを返します。クラスオブジェクトとはクラスを表すオブジェクトです。この関数は実行時にクラスを探して、与えられた名前に該当するクラスが見つかればそれを返します。見つからなければ nil を返します。戻り値の id という型についてはこの後で説明します。

実行時にクラスを探すということは、コンパイルの時点、あるいはコードを書いている時点では、そのような名前のクラスが実際に存在するかどうか知らなくても良いということです。また、手元にそのクラスを宣言したヘッダファイルがなくても、実行時にそのクラスにアクセスできるということです。

コードの後半をみると、クラスメソッドの呼び出しは、実際にはクラスを表すオブジェクトに対するメソッド呼び出しであることがわかります。

ただ、少し注意してほしいのは、Objective-C ランタイムでは、クラスは何かある別のクラスのインスタンスではない、ということです。クラスはあくまでもクラスであって、それそのものはオブジェクトではありません。ただ、そのクラスを表すオブジェクトが存在するというだけです。

id 型

上の例で id という型が出てきました。これは、Objective-C のオブジェクトならなんでもいいという型です。上の例ではクラスオブジェクトを代入していますが、他のクラスのオブジェクトを代入することもできます。

たとえば、次のように書いてもいいのです:

id str = objc_msgSend(cls, sel, "hello");

なお、NSString* などの型を明示する場合は * が必要ですが、id はそれ自体がポインタ型なので id* とする必要はありません。

前章で出て来た SEL と同様に id も値のように扱って良い型です。いやしかし実体はポインタなのだから、無効なポインタになる事もあると心配になるかもしれませんが、大丈夫です。id 型の変数にオブジェクトを代入するとオブジェクトのリファレンスカウンタが増えるので、ポインタの指すオブジェクトがいつの間にか解放されていて無効なポインタとなることはないことが保証されています。このような Objective-C ランタイムに特有のメモリ管理について、詳しくは 4 章で説明します。

プロパティ

インスタンスのプロパティとは、そのオブジェクトのメンバ変数のようなものです。たとえば、button というオブジェクトの title というプロパティに値を設定するには次のように書きます:

button.title = @"Push Me!";

これは、C の構造体のメンバ変数に値を代入するのによく似ています。しかし、Objective-C のオブジェクトは全てポインタ型だったことを思い出してください。C の場合は、構造体へのポインタを使ってメンバ変数にアクセスするには、. ではなく -> という演算子を使います。

また、プロパティの値を設定するには、次のようにメソッド呼び出しを使うこともできます。

[button setTitle: @"Push Me!"];

title というプロパティに対して setTitle: というセッタが定義されています。このように、プロパティ名の先頭の文字を大文字にして頭に set、お尻に : がついたメソッドが定義されるという約束になっています。

プロパティの値を読み出すにはゲッタを呼び出します。このゲッタはプロパティ名と同じ名前のメソッドです。つまり、Objective-C で次の 2 行は同じことをします。

NSString *title = button.title;
NSString *title = [button title];

ランタイム API でプロパティにアクセスするには、通常のメソッド呼び出しと同様にゲッタやセッタのメソッドを呼び出すだけです。

セッタを呼び出すには次のようにします:

NSString *title = @"Push Me!";
SEL sel = sel_registerName("setTitle:");
objc_msgSend(button, sel, title);

そしてゲッタを呼び出すには次のようにします:

SEL sel = sel_registerName("title");
NSString *title = objc_msgSend(button, sel);

NSObject

Objective-C のクラスはすべて NSObject というクラスの子クラスとして定義されています。これは言い換えると、NSObject クラスに定義されたメソッドは、あらゆるオブジェクトに対して呼び出すことができます。

アロケーションと初期化

上の例であげた NSString インスタンスの生成は、細かく分けるとメモリのアロケーションと初期化の 2 段階に分けられます。これを Objective-C で書くと次のように書けます:

NSString *str = [NSString alloc];
[str initWithUTF8String: "hello"];

ここで、1 行目は alloc というクラスメソッドが呼ばれ、その名が示す通り NSString オブジェクトのためにメモリがアロケートされます。そして 2 行目でオブジェクトを初期化します。なお、NSString クラスのオブジェクトは一旦初期化すると文字列の中身を変更することはできません。このように内容が変更できないことをイミュータブルと言ったりします。逆に変更できる場合はミュータブルといいます。ミュータブル版の NSString として NSMutableString というクラスもあります。