Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Contact ADC

< Previous PageNext Page >

Objective-Cによるオブジェクト指向プログラミング

Cocoaは、そのパラダイムからメカニズム、イベント駆動型アーキテクチャにいたるまで、オブジェクト指向が全面に表れています。また、Cocoaの主要な開発言語であるObjective-Cも、ANSI Cを土台としているにもかかわらず、徹底したオブジェクト指向が貫かれています。Objective-Cでは、メッセージのディスパッチのランタイムサポートが提供され、新しいクラスを定義するための構文規則が規定されています。Objective-Cでも、C++やJavaなどのほかのオブジェクト指向言語に見られる抽象化やメカニズムのほとんどがサポートされています。たとえば、継承、カプセル化、再利用、ポリモーフィズムなどがサポートされています。

しかし、Objective-Cは、多くの場合いくつかの重要な点で、これらのほかのオブジェクト指向言語とは異なっています。たとえば、C++とは異なり、Objective-Cでは演算子のオーバーロード、テンプレート、または多重継承は許されていません。また、Javaの“ガーベジコレクション”のように、不要になったオブジェクトを自動的に解放するメカニズムはありません(ただし、同じ動作を実現するためのメカニズムや規則は備えています)。

Objective-Cにはこれらの機能は備わっていませんが、オブジェクト指向のプログラミング言語として、それらを補って余りある強みがあります。以降では、Objective-Cの持つ特別な機能と、Cocoa版のJavaプログラミング言語の概要について説明します。

参考資料: このセクションの内容は、そのほとんどがObjective-Cガイドの決定版である『The Objective-C Programming Language』の情報を要約したものです。Objective-Cの詳細や全般的な説明については、この文書を参照してください。

In this section:

Objective-Cの利点
Objective-Cの使用


Objective-Cの利点

プロシージャを主体としたプログラミングに慣れていて、オブジェクト指向の概念にまだ慣れていないプログラマは、当初はオブジェクトを「関数が関連付けられている構造体である」と捉えるとわかりやすいかもしれません。このような考え方は、特にランタイムの実装においては実際とそれほどかけ離れているわけではありません。

Objective-Cのオブジェクトはすべてデータ構造を隠しており、その1番目のメンバ(つまりインスタンス変数)を“isaポインタ”と呼びます(それ以外のメンバのほとんどは、オブジェクトのクラスおよびスーパークラスによって定義されます)。isaポインタは、その名(“is a”)が示すように、オブジェクトのクラスを指し示します。それ自身もオブジェクトであり(Figure 2-1を参照)、クラス定義に基づいてコンパイルされます。クラスオブジェクトはディスパッチテーブルを保持しています。このテーブルは、基本的にはクラスで実装している各メソッドへのポインタで構成されています。また、クラスのスーパークラスへのポインタも保持しています。スーパークラスも自身のディスパッチテーブルとスーパークラスポインタを持っています。このような参照のつながり(チェーン)を通じて、オブジェクトから、自身のクラスとすべてのスーパークラスのメソッド実装にアクセスすることができ、継承されたパブリックインスタンス変数および保護されたインスタンス変数にもアクセスできます。isaポインタは、メッセージのディスパッチのメカニズムと、Cocoaオブジェクトのダイナミズムにとってたいへん重要な存在です。


Figure 2-1  オブジェクトのisaポインタ

Figure 2-1 オブジェクトのisaポインタ

このセクションでは、オブジェクトの見えない一面をのぞいてみることで、メッセージのディスパッチや継承といったオブジェクトの一般的な動作を可能にするために、Objective-Cのランタイムの中で何が起こるのかを、非常に単純化して理解することができます。ただし、この情報はObjective-Cの大きな強みであるそのダイナミズムを理解するためには不可欠です。

ダイナミズム

Objective-Cはたいへん動的な言語です。Objective-Cのダイナミズムによって、プログラムがコンパイル時やリンク時の制約から解放され、シンボル解決の処理のほとんどが、ユーザによって制御される実行時へ移行されます。Objective-Cはほかのどのプログラミング言語よりも動的です。その理由は、次の3つをダイナミズムの源泉としているからです。

動的型定義については、任意のCocoaオブジェクトを表すことができるid データ型が、Objective-Cで導入されています。この汎用的なオブジェクト型の典型的な使用例として、Listing 2-2の一部を次に示します。

id word;
while (word = [enm nextObject]) {
// その他のコード

idデータ型によって、任意の型のオブジェクトを実行時に代入することが可能になります。したがって、コードの中でどのような種類のオブジェクトを使用するのかを、実行時の状況に応じて決めることができます。動的型定義によって、オブジェクトどうしの関連付けを、静的なデザインの中で強制的にコード化するのではなく、実行時に決定することが可能になります。コンパイル時の静的な型チェックでは、データの整合性がより厳密になることが保証されるかもしれません。しかし、動的型定義ではそのような整合性の代わりとして、はるかに高い柔軟性をプログラムにもたらします。そして、オブジェクトのイントロスペクション(たとえば、動的に型定義された匿名のオブジェクトについて、そのクラスが何であるかを調べることなど)を通じて、引き続きオブジェクトの型を実行時に確認でき、それによって特定の操作に対する型の適合性を検証できます(もちろん、必要ならばいつでもオブジェクトの型を静的にチェックできます)。

動的型定義は、Objective-Cの2番目のダイナミズムである動的バインディングに実体を与えます。動的型定義では、オブジェクトが属するクラスの解決が実行時まで保留されますが、それとまったく同様に、動的バインディングでは呼び出すべきメソッドの決定が実行時まで保留されます。メソッドの呼び出しは、コンパイル時にコードにバインドされず、メッセージが実際に送信される時点で初めてバインドされます。動的型定義と動的バインディングを組み合わせることで、コードを実行するたび異なる結果を得ることができます。実行時の状況に応じて、レシーバの選択や呼び出し先のメソッドが決まることになります。

動的バインディングを可能にするのが、ランタイムのメッセージディスパッチ機構です。動的に型定義されたオブジェクトにメッセージを送信すると、ランタイムシステムによって、レシーバのisaポインタを通じてオブジェクトのクラスが特定され、呼び出すべきメソッドの実装がそこから特定されます。メソッドがメッセージに動的にバインドされるわけです。そして、Objective-Cのコードの中で特別な操作をしなくても、動的バインディングの利点を享受することができます。特に、動的に型定義されたオブジェクトにメッセージを送信するたびに、当たり前の処理として、見えないところで動的バインディングが行われます。

ダイナミズムの最後の要素である動的ロードは、ランタイムサポートをObjective-Cに依存するCocoaの機能の1つです。動的ロードを使用することで、Cocoaプログラムでは、プログラムコンポーネントを起動時にすべてロードする必要がなくなり、実行可能コードやリソースを必要なときにロードすることができます。実行可能コード(ロードの前にリンクされます)には、多くの場合、プログラムのランタイムイメージに組み込まれる新しいクラスが含まれています。コードとローカライズリソース(nibファイルを含む)の両方がバンドルにパッケージ化され、FoundationのNSBundleクラスで定義されているメソッドを使用して明示的にロードされます。

このように、プログラムのコードとリソースを“遅延ロード”することで、システムに必要なメモリ量が減り、全体的なパフォーマンスが向上します。さらに重要なこととして、動的ロードによってアプリケーションの拡張性が高まります。アプリケーションをリリースしてから数か月あるいは数年を経ても動的に追加モジュールをロードすることでアプリケーションを自分やほかの開発者がカスタマイズできるように、アプリケーションのためのプラグインアーキテクチャを考案することが可能です。設計が正しければ、クラスはそれぞれに実装がカプセル化されて独自の名前空間を持つため、それらのモジュール内のクラスが既存のクラスと競合することはありません。

言語拡張

Objective-Cでは、基本の言語に対して“カテゴリ”と“プロトコル”という2つの拡張が組み込まれています。これらは、ソフトウェアの開発において強力なツールとなります。どちらの拡張も、メソッドの宣言やメソッドとクラスとの関連付けを行うためのさまざまな手法が採用されています。

カテゴリ

カテゴリを使用すると、サブクラスを作成せずにクラスにメソッドを追加できます。カテゴリ内のメソッドは(プログラムの有効範囲内で)クラスの型の一部になり、クラスのすべてのサブクラスに継承されます。実行時には、元のメソッドと追加されたメソッドとで違いはまったくありません。クラス(またはそのサブクラス)の任意のインスタンスに対してメッセージを送信し、カテゴリ内に定義されているメソッドを呼び出せます。

カテゴリは、クラスに動作を追加するための便利な手段であるだけにとどまりません。カテゴリを使用してメソッドを区分し、関連するメソッドどうしを個別のカテゴリにグループ化することもできます。特に、大規模なクラス群を整理する場合にカテゴリが便利です。たとえば、複数の開発者が特定のクラスを対象とする作業をしている場合に、個々のカテゴリを個別のソースファイルに割り振ることさえ可能です。

カテゴリの宣言と実装は、サブクラスの場合とほとんど同じです。構文上の唯一の違いは、カテゴリの名前です。カテゴリ名の前に@interfaceまたは@implementationディレクティブを記述し、名前を括弧で囲みます。たとえば、コレクションの説明を、より構造化された方法で印字する、NSArrayというクラスに、メソッドを追加する必要があるとします。この場合、カテゴリ用のヘッダファイルに次のような宣言コードを記述します。

#import <Foundation/NSArray.h> // Foundationがまだインポートされていない場合
 
@interface NSArray (PrettyPrintElements)
- (NSString *)prettyPrintDescription;
@end

次に、実装ファイルに次のようなコードを記述します。

#import “PrettyPrintCategory.h”
 
@implementation NSArray (PrettyPrintElements)
- (NSString *)prettyPrintDescription {
    // 実装コードをここに記述
}
@end

カテゴリにはいくつかの制限事項があります。カテゴリを使用して新しいインスタンス変数をクラスに追加することはできません。カテゴリメソッドは既存のメソッドをオーバーライドできますが、特に現在の動作を拡張する場合は、そのようにすることはお勧めできません。その理由の1つは、カテゴリメソッドはクラスのインターフェイスの一部であるためです。したがって、メッセージをsuperに送信して、クラスですでに定義されている動作を取得することができません。クラスの既存のメソッドの動作を変更する必要がある場合は、クラスのサブクラスを作成するほうが適切です。

ルートクラスであるNSObjectにメソッドを追加するカテゴリを定義することもできます。この場合、追加したメソッドは、自分のコードにリンクされたすべてのインスタンスおよびクラスオブジェクトで使用できます。Cocoaの委任メカニズムの基礎になっている簡易プロトコルは、NSObjectのカテゴリとして宣言されています。ただし、このような広範囲な公開は、利便性だけではなく危険性も伴います。NSObjectのカテゴリを通じてすべてのオブジェクトに追加した動作のために、予想もできないような結果を招き、クラッシュやデータの損傷、あるいはそれ以上の悪い事態をもたらすおそれがあります。

プロトコル

プロトコルと呼ばれるObjective-C拡張は、Javaのインターフェイスにたいへんよく似ています。どちらも、任意のクラスで実装することが可能なインターフェイスを公開しているメソッド宣言の単純なリストです。プロトコル内のメソッドは、ほかの何らかのクラスのインスタンスから送信されるメッセージによって呼び出されます。

プロトコルの主な利点は、カテゴリと同様にサブクラス化の代わりとして使用できることです。プロトコルによって、C++における多重継承の利点の一部がもたらされ、インターフェイス(ひいては実装)の共有が可能になります。プロトコルを使用することで、クラスの識別情報を隠蔽しながらクラスのインターフェイスを宣言できるようになります。そのようなインターフェイスでは、クラスが提供できるサービスをすべて公開したり、またはそのうちの特定範囲のサービスだけを公開したりできます(通常は後者)。クラス階層内のほかのクラスでは、必ずしも継承関係がなくても(ルートクラスへの継承関係さえなくても)、そのプロトコルのメソッドを実装し、公開されているサービスにアクセスできます。プロトコルを使用すれば、別のクラスの識別情報(つまり、クラスの型)がまったくわからないクラスであっても、そのプロトコルによって確立された特定の用途のためにやり取りができます。

プロトコルには正式と簡易の2つの種類があります。 簡易プロトコルについては“「カテゴリ」”で簡単に触れました。これらはNSObjectのカテゴリです。その結果、NSObjectをルートオブジェクト(またはクラスオブジェクト)として持つすべてのオブジェクトでは、カテゴリで公開されているインターフェイスを暗黙に採用します。正式プロトコルとは異なり、クラスでは簡易プロトコル内のすべてのメソッドを実装する必要はなく、必要と思われるメソッドを実装するだけで済みます。簡易プロトコルが動作するためには、簡易プロトコルを宣言しているクラスで、ターゲットオブジェクトへのrespondsToSelector:メッセージに対して肯定応答を受け取り、それからそのオブジェクトにプロトコルメッセージを送信する必要があります(ターゲットオブジェクトにメソッドが実装されていなければ、ランタイム例外となります)。

正式プロトコルは通常、Cocoaにおいて“protocol”によって指定される対象です。正式プロトコルを使用することで、クラスでは、提供されるサービスへのインターフェイスであるメソッドのリストを正式に宣言できます。Objective-Cの言語およびランタイムシステムでは正式プロトコルがサポートされています。コンパイラではプロトコルに基づく型チェックを実行でき、オブジェクトでは実行時にイントロスペクションを実行してプロトコルへの準拠を確認できます。正式プロトコルには独自の用語と構文があります。用語はプロバイダとクライアントとで異なります。

プロトコルの宣言と採用は、どちらもObjective-Cにおいて独自の構文形式があります。プロトコルを宣言するには、@protocolコンパイラディレクティブを使用する必要があります。次の例はNSCodingプロトコルの宣言を示します(FoundationフレームワークのヘッダファイルNSObject.hにあります)。

@protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;
@end

宣言側のクラスではこれらのメソッドを実装する必要はありませんが、準拠側のオブジェクトの中でそれらを呼び出すようにします。

クラスでプロトコルを採用するには、スーパークラスの直後に、クラスの@interfaceディレクティブの末尾で、プロトコルを山括弧で囲んで指定します。複数のプロトコルを採用するには、プロトコルをカンマで区切って指定します。FoundationのNSDataクラスでは次のように3つのプロトコルを採用しています。

@interface NSData :NSObject <NSCopying, NSMutableCopying, NSCoding>

これらのプロトコルを採用することによって、プロトコルで宣言されているすべてのメソッドを実装することをNSData自身が約束します。また、カテゴリでもプロトコルを採用でき、採用したプロトコルはカテゴリのクラス定義の一部になります。

Objective-Cのクラスは、クラス自身が準拠しているプロトコルだけでなく、継承元のクラスが準拠しているプロトコルによっても型が定義されます。クラスが特定のプロトコルに準拠しているかどうかを確かめるには、次のようにしてクラスにconformsToProtocol:メッセージを送信します。

if ([anObject conformsToProtocol:@protocol(NSCoding)]) {
        // 適切な処理を実行
}

型(メソッド、インスタンス変数、または関数)の宣言では、プロトコルへの準拠を型の一部として指定できます。そうすることで、コンパイラによるさらなる型チェックが提供されます。プロトコルは特定の実装に結び付けられていないため、より抽象的なチェックになります。この場合も、プロトコルの採用の場合と同じ構文規則を使用し、プロトコル名を山括弧で囲んで、型の中でプロトコルへの準拠を指定します。しばしば、次の例のように、これらの宣言では動的オブジェクト型であるidが使用されます。

- (void)draggingEnded:(id <NSDraggingInfo>)sender;

この例では、引数の中で参照されるオブジェクトは任意のクラス型になりますが、NSDraggingInfoプロトコルに準拠する必要があります。

Cocoaでは、ここまでで紹介したもの以外にもプロトコルのサンプルがいくつか提供されています。その中で興味深いものとして、NSObjectプロトコルがあります。当然ながら、NSObjectクラスではこのプロトコルが採用されていますが、別のルートクラスであるNSProxyでもこのプロトコルが採用されています。NSProxyクラスではこのプロトコルを通じて、参照カウントやイントロスペクションなど、オブジェクトの基本的な動作にとって必要不可欠なObjective-Cランタイムの各部とやり取りすることができます。

正式プロトコルには独自の制限があります。プロトコルで宣言されているメソッドのリストが将来増大した場合、プロトコルを採用する側が非準拠になると考えられます。そのため、Cocoaの正式プロトコルはNSCopyingNSCodingなどの安定したメソッド群に対して使用されています。プロトコルのメソッド群が将来増大すると予測される場合は、正式プロトコルではなく簡易プロトコルを宣言してください。

Objective-Cの使用

オブジェクト指向プログラムでは、メッセージを使用して処理が実行されます。あるオブジェクトから別のオブジェクトにメッセージが送信されます。送信側のオブジェクトは、メッセージを通じて何らかのデータを受信側のオブジェクト(レシーバ)に要求します。送信側のオブジェクトはレシーバに対して、何らかの動作を実行することや、何らかのオブジェクトまたは値を返すこと、あるいはその両方の処理を実行することを要求します。

Objective-Cでは、メッセージングのための独自の構文形式を採用しています。Listing 2-2のSimpleCocoaToolコードにある次の文を見てみましょう。

NSEnumerator *enm = [sorted_args objectEnumerator];

代入文の右側は、角括弧で囲まれたメッセージ式です。メッセージ式内の左端にある項目はレシーバで、メッセージの送信先のオブジェクトを表す変数です。この例では、レシーバはsorted_argsであり、NSArrayクラスのインスタンスです。レシーバの後にはメッセージ固有の記述が続きます。この例ではobjectEnumeratorです(ここではメッセージの構文を中心に説明しているので、このメッセージやSimpleCocoaTool内のほかのメッセージが実際にどのような動作をするのかについてはあまり詳しく触れません)。objectEnumeratorメッセージによって、sorted_argsオブジェクトのメソッドである、objectEnumeratorという名前のメソッドが呼び出されます。メソッドは、代入式の左側にある変数enmが保持しているオブジェクトへの参照を返します。この変数は、NSEnumeratorクラスのインスタンスとして静的に型定義されています。この文は次のように表現できます。


Object messaging syntax

しばしば、メッセージはパラメータ、すなわち引数を持ちます。単一の引数を持つメッセージでは、メッセージ名の後にコロンが付き、コロンの直後に引数が付きます。


Object messaging with argument

関数のパラメータと同様に、引数の型はメソッド宣言に指定されている型と一致する必要があります。SimpleCocoaToolにある次のメッセージ式を例にとりましょう。

NSCountedSet *cset = [[NSCountedSet alloc] initWithArray:args];

この例では、argsNSArrayクラスのインスタンスでもあり、initWithArray:というメッセージの引数になっています。

メッセージに複数の引数がある場合は、メッセージ名に複数のセグメントが付きます。各セグメントはコロンで終わり、その後に引数が続きます。


Object messaging with multiple arguments

前の例のinitWithArray:は、ネストを示すという点で興味深い例です。Objective-Cでは、あるメッセージの中に別のメッセージをネストすることができます。あるメッセージ式から返されたオブジェクトを、それを囲むメッセージ式でレシーバとして使用します。そのため、ネストされたメッセージ式を解釈するには、内側の式から外側に向かって解釈を行います。上の文の解釈は次のようになります。

  1. allocメッセージがNSCountedSetクラスに送信される。それによってメモリがクラスに割り当てられ、クラスの未初期化インスタンスが作成される。

    注: Objective-Cのクラスはそれ自体がオブジェクトです。クラスのほか、クラスのインスタンスに対してもメッセージを送信できます。メッセージ式では、クラスメッセージのレシーバは常にクラスオブジェクトになります。

  2. initWithArray:メッセージが未初期化インスタンスに送信される。その結果、インスタンスは配列argsを使用して自分自身を初期化し、自分自身への参照を返す。

次に、SimpleCocoaToolのmainルーチンにある下の文を考えてみましょう。

NSArray *sorted_args = [[cset allObjects] sortedArrayUsingSelector:@selector(compare:)];

このメッセージ式で注目すべき点は、sortedArrayUsingSelector:メッセージの引数です。この引数は、@selectorコンパイラディレクティブを使用してセレクタを作成することを要求しています。セレクタとは、Objective-Cランタイムがレシーバのメソッドを一意に識別する際に使用する名前で、コロンを含むメッセージ名のすべての部分が含まれますが、それ以外の戻り型やパラメータ型は含まれません。

ここで、メッセージとメソッドの用語について少し復習してみましょう。メソッドとは、本質的には、メッセージのレシーバがメンバとなっているクラスによって定義され、実装される関数のことです。引数と結合されたセレクタであるメッセージは、レシーバに送られます。その結果、メソッドの呼び出し(または実行)が発生します。メッセージ式にはレシーバとメッセージの両方が含まれています。これらの関係をFigure 2-2に示します。


Figure 2-2  メッセージの用語

Figure 2-2 メッセージの用語

Objective-Cでは、ANSI Cでは見られないような定義済みの型やリテラルがいくつか使用されており、場合によっては、これらの型やリテラルがANSI Cの対応する型やリテラルの代わりとなっています。その中でいくつか重要なものを、Table 2-1に示します。これには、型ごとに使用が許されるリテラルが含まれています。

Table 2-1  Objective-Cにおける重要な定義済みの型およびリテラル

説明とリテラル

id

動的オブジェクト型。その否定のリテラルはnilです。

Class

動的クラス型。その否定のリテラルはNilです。

SEL

セレクタのデータ型(typedef)。ANSI Cの場合と同様に、このような型の否定のリテラルはNULLです。

BOOL

ブール値型。リテラル値はYESおよびNOです。

プログラムのフロー制御文では、該当する否定のリテラルが存在すること(または存在しないこと)をテストすることで、処理方法を決めることができます。たとえば、SimpleCocoaToolコードにある次のwhile文では、wordオブジェクト変数について、返されるオブジェクトが存在すること(別の言い方をすれば、nilが存在しないこと)を暗黙にテストしています。

while (word = [enm nextObject]) {
    printf("%s\n", [word UTF8String]);
}

Objective-Cではしばしば、副作用を起こさずにメッセージをnilに送信することができます。nilに送信したメッセージからの戻り値は、返されるものがオブジェクトとして型定義されている限り、動作が保証されます。

最後に、Objective-Cに慣れていない人にとって一見わかりにくいものが、SimpleCocoaToolコードにあります。この文と

NSEnumerator *enm = [sorted_args objectEnumerator];

次の文を比較してみましょう。

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

表面上はまったく同じことを実行しているように見えます。どちらも、オブジェクトへの参照を返します。しかし、返されるオブジェクトの所有権に関しては意味上の重要な違いがあり、したがって、解放の責任を持つ対象も異なります。1番目の文では、SimpleCocoaToolプログラムは返されるオブジェクトを所有していません。2番目の文では、プログラムでオブジェクトを作成しているので、オブジェクトを所有しています。プログラムの最後では、作成したオブジェクトに対してreleaseメッセージを送信してオブジェクトを解放しています。また、ただ1つ明示的に作成した別のオブジェクト(NSCountedSetインスタンス)についても、プログラムの最後で明示的に解放しています。オブジェクトの所有権と破棄に関するポリシーの概説と、そのポリシーを適用するために使用するメソッドの詳細については、“「Cocoaオブジェクトのライフサイクル」”を参照してください。



< Previous PageNext Page >


Last updated: 2006-05-23




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.
Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2007 Apple Inc.
All rights reserved. | Terms of use | Privacy Notice