blog

C++開発者がObjective-C言語のコア構文を素早く学ぶ

この記事では、Objective-C を取り上げ、この言語の中核となる構文について説明します。このセクションでは、具体的な構文のいくつかを詳しく説明します。予想されるように、定義とクラスがカバーされて...

Jul 13, 2025 · 9 min. read
シェア

この記事ではObjective-Cを取り上げ、この言語の中核となる構文について説明します。このセクションでは、具体的な構文のいくつかを詳しく説明します。ご想像の通り、定義とクラスが関係しています。

クラスは特別ではありません

Smalltalkでは、クラスはいくつかの特徴を持ったオブジェクトです。Objective-Cでも同じです。クラスはオブジェクトであり、オブジェクトはメッセージに応答します。Objective-CもC++も、オブジェクトの割り当てと初期化を分離しています。

C++では、オブジェクトはnew操作によって割り当てられます。Objective-Cでは、このような操作は、クラスに割り当てメッセージを送信し、malloc()または同等のものを呼び出すことによって行われます。

C++の初期化は、クラスと同じ名前の関数を呼び出すことで行われます。Objective-Cでは、初期化メソッドとその他のメソッドを区別していませんが、慣習上、デフォルトの初期化メソッドはinitialisationです。

インスタンスが応答するメソッドを宣言する場合、宣言は通常"-"で始まり、"+"はクラスメソッドとして使われます。ドキュメントではこれらのメッセージに接頭辞を付けるのが一般的なので、allocがクラスに渡され、initがインスタンスに渡されることを意味するために、+allocと-initという言い方もできます。

Objective-Cのクラスは、他のオブジェクト指向言語と同様、オブジェクト・ファクトリです。ほとんどのクラスは +alloc を独自に実装する必要はなく、親クラスを継承します。ほとんどの Objective-C プログラムでは、+alloc メソッドは +allocWithZone: を呼び出します。を呼び出すと、NSZone が引数として渡されます。Objective-CがNeXTstepで8MBのRAMと25MHZのCPUしかないマシン用のデバイス・ドライバとGUIを実装するために使われていた1880年代を振り返ると、NSZoneは最適化のために重要でした。同時に、Objective-Cプログラマからは多かれ少なかれ無視されていました。

多くの素晴らしい特徴のひとつは、オブジェクト生成のセマンティクスはライブラリによって定義され、言語はクラス群ではないという考え方です。オブジェクトに -init メッセージを渡すと、初期化されたオブジェクトが返されます。これはメッセージを送ったオブジェクトかもしれませんが、そうである必要はありません。これは他の初期化手続きと同じです。あるパブリック・クラスの特別なサブクラスが、異なるデータに対してより効率的である可能性は十分にあります。

この機能を実装する一般的な方法はisa-swizzlingと呼ばれます。先に述べたように、Objective-CのオブジェクトはCの構造体であり、これらの構造体の1つの要素***はクラスへのポインタです。この要素は他のインスタンス変数と同じようにアクセス可能です。新しい値を代入することで、実行時にオブジェクトのクラスを変更できます。もちろん、オブジェクトのクラス設定をメモリ上で別のレイアウトにすると、その設定が大きく間違ってしまうことがあります。

しかし、親クラスによってレイアウトを定義し、サブセットのコレクションによって振る舞いを定義することができます。たとえば、このテクニックは正規化文字列クラスで使用されており、異なるテキスト文字セットや静的なものなど、さまざまなインスタンスを持っています。

クラスはオブジェクトですから、オブジェクトのように操作することができます。例えば、コレクションに入れることができます。私は、クラスの異なるインスタンスによって処理される必要のある入力イベントが多数ある場合に、この形式を使用します。イベント名をクラス名にマッピングしたカタログを作成し、入力イベントごとにオブジェクトをインスタンス化する必要があります。これをライブラリの中で行えば、コードの利用者が独自のハンドルを簡単に登録できるようになります。

型とポインタ

Objective-Cは、スタック上でオブジェクトを定義することを公然とは認めていません。スタック上でオブジェクトを定義することは十分可能ですが、メモリ管理に関する前提が崩れるため、少々難しいのです。 その結果、Objective-Cのオブジェクトはすべてポインタになります。いくつかの型はObjective-Cで定義されています。これらはCの型としてヘッダで定義されています。

Objective-Cで最も一般的な3つの型は、id、Class、SELです。idはObjective-Cオブジェクトへのポインターで、C言語のvoid*に相当します。

id には任意のメッセージを渡すことができますが、サポートされていない場合は実行時例外が返されます。

クラスはObjective-Cのクラスへのポインターです。クラスはオブジェクトなので、メッセージを受け取ることもできます。クラス名は型であり、変更できません。識別子 NSObject は NSObject インスタンスの型ですが、メッセージのレシーバーとしても使用できます。クラスは以下のようにして取得できます:

[NSObject class]; 

class メッセージを NSObject クラスに送信し、そのクラスを表すクラス構造体へのポインタを返します。

本連載の第2回で紹介したように、[FS:PAGE]を見直す際に非常に便利です。

3 番目のタイプである SEL は、セレクタを表します。セレクタは、メソッド名を表す抽象です。セレクタは、コンパイル時に @selector() で直接作成するか、実行時に C 文字列でランタイム・ライブラリ関数を呼び出すか、Objective-C 文字列にセレクタを与える OpenStep NSSelectorFromString() 関数を使用して作成できます。このテクニックを使用すると、名前でメソッドを呼び出すことができます。C では、dlsym() などを使用してこれを実行できますが、C++ では大きく異なります。Objective-Cでは以下のようになります:

[object perfomSelector:@selector(doSomething)]; 

これは以下と同じです:

[object doSomething]; 

明らかに、****は2つのメッセージを送信しているようなものなので、2番目のフォーマットの方が若干速いです。後で、セレクタを使った処理の詳細を見てみましょう。

C++はidと同じ型を持っていません。なぜなら、オブジェクトは常に型付け可能だからです。Objective-Cでは、型システムの選択肢があります。以下のどちらも有効です:

id object = @”a string”;  
  
NSString *string = @”a string”; 

定数文字列は実際には NSString のサブクラスである NSConstantString クラスのインスタンスです。これを NSString* に参照することで、コンパイル時にメッセージの型チェックを行い、パブリック・インスタンス変数を格納することができます。なお、この設定は次のようにして変更できます:

NSArray *array = (NSArray*)string; 

配列にメッセージを送信する場合、コンパイラは NSArray が受信できるメッセージをチェックします。オブジェクトは文字列なので、これはあまり役に立ちません。NSArray と NSString の実装を持つメッセージを送信する場合はうまくいくかもしれません。NSString が実装していないメッセージを送信すると、例外がスローされます。

Objective-CとC++の違いを強調するのは奇妙に思えるかもしれませんが、Objective-Cには型-値構文があり、C++には型-変数構文があります。Objective-C では、オブジェクトの型はオブジェクト専用のプロパティです。C++ では、型は変数の型に依存します。

C++では、親クラスへのポインターを定義する変数にオブジェクトへのポインターを代入する場合、2つのポインターは同じ値を持つとは限りません。

クラスの定義

Objective-Cのクラス定義には、インターフェイスと実装部分があります。C++と似ている部分もありますが、両者は微妙に混在しています。

Objective-Cのインターフェイスはビットだけを定義し、明示的にパブリックである必要があります。実装上の理由から、ほとんどの実装ではプライベートなインスタンス変数も含まれます。Appleの64ビット・ランタイムのような最近の実装では、この制限がないものもあります。

Objective-Cオブジェクトのインターフェースは以下の通りです:

@interface AnObject : NSObject  
  
{  
  
@private  
  
int integerivar  
  
@public  
  
id anotherObject;  
  
}  
  
+ (id) aClassMethod;  
  
- (id) anInstanceMethod:(NSString*)aString with:(id)anObject  
  
@end 

行目***には3つの部分があります。識別子 AnObject は新しいクラスの名前です。コロンの後の名前は NSObject です。括弧内の名前は、クラスを通して実装されるプロトコル(Javaのインターフェイスに似ています)です。

C++のインスタンス変数が修飾子にアクセスできるのと同様に、C++とは異なり、これらの修飾子はCの識別子との衝突を避けるために@が先頭に付きます。

Objective-Cは多重継承をサポートしていないので、親クラスは1つだけです。そのため、オブジェクトの***部分のレイアウトは常に親クラスのインスタンスのレイアウトと一致します。これは、クラスのインスタンス変数を変更すると、そのすべての子クラスを再コンパイルする必要があることを意味します。新しいランタイムでは、この修飾は不要になり、インスタンス変数へのアクセスに少し費用がかかるようになりました。この決定のもう1つの影響は、Objective-Cの他の機能の1つです。

struct_AnObject  
  
{  
  @defs(AnObject);  
}; 

defは、この構造体が特定のオブジェクトのすべてのフィールドに挿入され、 struct_AnObjectとAnObjectクラスのインスタンスが同じメモリ構造を持つことを意味します。例として、このルールを使用してインスタンス変数への直接アクセスを許可することができます。一般的な使用法は、パフォーマンス上の理由から、C 関数で Objective-C オブジェクトを直接操作できるようにすることです。

先ほどもほのめかしたように、この機能に関連するもう1つのことは、スタック上にオブジェクトを作成する機能です。構造体とオブジェクトは[FS:PAGE]メモリ・レイアウトで同じ構造を持っているので、構造体を作成し、そのポインタを正しいクラスを指すように設定し、オブジェクト・ポインタを指すようにポインタをマップするだけです。その後、ポインタが境界の外に出ないように注意しなければなりませんが、オブジェクトとして使用することができます。

C++とは異なり、Objective-Cにはprivateメソッドもprotectedメソッドもありません。Objective-Cのオブジェクト上のメソッドはすべて、他のオブジェクトから呼び出すことができます。インターフェイスでメソッドを宣言しない場合、それは非公式にプライベートです。オブジェクトがこのメッセージに応答しないという実行時の警告が表示されますが、それでも呼び出すことはできます。

インターフェイスは、C言語のヘッダー宣言によく似ています。しかし、まだ実装が必要で、驚くことではありませんが、@implementationを使って定義することができます。

@implementation AnObject  
+ (id) aClassMethod  
{  
    ...  
}  
 
- (id) anInstanceMethod:(NSString*)aString with:(id)anObject  
{  
    ...  
}  
@end 

パラメータの型は括弧で囲まれていることに注意してください。これは、値が型にマッピングされることを示すために、C言語のマッピング構文を再利用したものです。マッピングの際にも、まったく同じルールが適用されます。つまり、互換性のないオブジェクト・ポインタ型の間でマッピングを行うと警告が出ます。

メモリ管理

NSObject から継承されたオブジェクトはすべて -retain と -release メッセージに応答します。オブジェクトへのポインタを保持したい場合は -retain メッセージを送ります。使い終わったら -release メッセージを送ります。

この設計には微妙な問題があります。通常、オブジェクトへのポインタを保持する必要はありませんが、解放もしたくありません。典型的な例は、オブジェクトを返すとき、呼び出し側はオブジェクトへのポインタを保持する必要がありますが、あなたはそれをしたくありません。

この問題を解決するのが NSAutoreleasePool クラスです。NSObject は、-retain と -release とともに、-autorelease メッセージにも応答します。これらのうちの 1 つを送信すると、現在存在する autorelease プールと共に登録されます。プール・オブジェクトがキャンセルされると、-autorelease メッセージを最初に受信した各オブジェクトに -release メッセージが送信されます。OpenStep アプリケーションでは、NSAutoreleasePool インスタンスがループの最初に作成され、最後に破棄されます。オブジェクトを自動解放するために独自のインスタンスを作成することもできます。

Objective-C では、定数パターンは不変クラスと変更可能なサブクラスを定義します。

NSStringを取得し、それを保存したい場合は、-retainメッセージを渡して、ポインタをコピーせずに保存することができます。その代わりに、NSString に +stringWithString: メッセージを渡すと、引数がミュータブルかどうかに関係なく、元のポインタをチェックして返します。

Objective-CはAppleとGNUの両方のランタイムでストアド・ラビッシュ・コレクションをサポートしており、-retainと-releaseの必要性を回避しています。言語追加は、既存のフレームワークでは必ずしもうまくサポートされておらず、細心の注意を払って使用する必要があります。

概要

さて、Objective-C言語の核心部分をざっと読み終えたところで、この要約のセクションではさらに高度なトピックをいくつか見ていきましょう。

Read next

チームワーク:良い技術リーダーと悪い技術リーダー?

ベン・ホロウィッツの記事「良いプロダクトマネージャー、悪いプロダクトマネージャー」にインスパイアされた、チームにおけるテクニカル・リーダーシップの簡単なガイドです。

Jul 13, 2025 · 5 min read