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

< Previous PageNext Page >

オブジェクトの作成

Cocoaオブジェクトの作成は、常に“割り当て”と“初期化”という2つの段階で行われます。両方の段階が実行されなければ、通常はオブジェクトは使用できる状態ではありません。ほとんどすべての場合において、割り当ての直後に初期化が続きますが、これら2つの操作はオブジェクトの形成において別々の役割を果たします。

In this section:

オブジェクトの割り当て
オブジェクトの初期化
deallocメソッド
クラスファクトリメソッド


オブジェクトの割り当て

オブジェクトを割り当てるときに発生する処理の中では、“割り当て”という言葉から予想されるような処理が発生します。Cocoaによって、アプリケーションの仮想メモリ領域から、オブジェクト用の十分なメモリが割り当てられます。割り当てるメモリ量を計算する際には、オブジェクトのクラスに指定されているオブジェクトのインスタンス変数も、その型や順序も含めて考慮されます。

オブジェクトを割り当てるには、allocまたはallocWithZone:メッセージをオブジェクトのクラスに送ります。すると、クラスの“未加工の”(初期化されていない)インスタンスが返されます。allocメソッドのバリエーションは、アプリケーションのデフォルトのゾーンを使用します。ゾーンとは、アプリケーションによって割り当てられる関連オブジェクトやデータを保持するための、ページ単位のメモリ領域のことです。ゾーンの詳細については、『Memory Management Programming Guide for Cocoa』を参照してください。

割り当てメッセージでは、メモリの割り当て以外にもいくつか重要な処理が実行されます。

オブジェクトのisaインスタンス変数はNSObjectから継承されるため、すべてのCocoaオブジェクトで共通です。割り当てによってisaがオブジェクトのクラスを指し示すように設定された後、オブジェクトは、ランタイムの継承階層、およびプログラムを構成するオブジェクト(クラスおよびインスタンス)の現在のネットワークの対象範囲に組み込まれます。この結果、オブジェクトは、必要なあらゆる情報を実行時に見つけることができるようになります。たとえば、継承階層におけるほかのオブジェクトの位置、ほかのオブジェクトが準拠しているプロトコル、メッセージに応えて実行できるメソッド実装の場所などを取得できます。

まとめると、割り当てでは単にオブジェクトのメモリが割り当てられるだけではなく、オブジェクトが持つ2つの小さな、しかしたいへん重要な属性が、初期化されます。つまり、オブジェクトのisaインスタンス変数と、オブジェクトの保持カウントです。また、それ以外のすべてのインスタンス変数が0に設定されます。ただし、割り当て後のオブジェクトはまだ使用できる状態ではありません。initなどの初期化メソッドによって、特定の性質を持つようにさらにオブジェクトを初期化し、機能しうるオブジェクトを返す必要があります。

オブジェクトの初期化

初期化では、オブジェクトの各インスタンス変数が、適切で有益な初期値に設定されます。また、初期化では、オブジェクトで必要になるほかのグローバルリソースについて割り当てや準備を行い、必要ならばファイルなどの外部リソースからロードすることもできます。インスタンス変数として宣言したすべてのオブジェクトには、すべてを0に設定するというデフォルトの初期化で十分である場合を除いて、初期化メソッドが実装されている必要があります。オブジェクトにイニシャライザが実装されていない場合、代わりに、最も近い祖先のイニシャライザがCocoaによって起動されます。

イニシャライザの形式

NSObjectではイニシャライザ用のinitプロトタイプを宣言しています。これは、id型のオブジェクトを返すように型定義されたインスタンスメソッドです。オブジェクトの初期化の際に追加のデータを必要としないサブクラスの場合は、initをオーバーライドしてもかまいません。しかし、多くの場合、初期化ではオブジェクトを適切な初期状態に設定するために外部データが必要となります。たとえば、Accountというクラスがあったとしましょう。Accountオブジェクトを適切に初期化するためには一意のアカウント番号が必要であり、この番号をイニシャライザに供給する必要があります。そのために、イニシャライザは1つ以上の引数を受け取ることができます。初期化メソッドに求められる唯一の要件は、“init”という文字列で始まることです(イニシャライザを指す表記上の規則として、init...がしばしば使用されます)。

注: 引数を持つイニシャライザの代わりに、サブクラスでは簡単なinitメソッドだけを実装し、初期化の直後に“set”アクセサメソッドを使用して、オブジェクトを有益な初期状態に設定することもできます(アクセサメソッドでは、インスタンス変数の値を設定および取得する手段を提供することによって、オブジェクトデータのカプセル化を可能にします)。

Cocoaには引数を持つイニシャライザの例が数多くあります。そのいくつかを次に示します。

これらのイニシャライザは“init”で始まるインスタンスメソッドであり、動的な型であるid型のオブジェクトを返します。それ以外は、複数の引数を持つメソッドに関するCocoaの規則に従っており、最も重要な第1引数の先頭でしばしばWith Type :FromSource :を使用しています。

イニシャライザに関する問題

init...メソッドは、それぞれのメソッドシグネチャに従ってオブジェクトを返すことが求められますが、そのオブジェクトは、必ずしも直前に割り当てられたオブジェクト(init...メッセージのレシーバ)であるとは限りません。言い換えると、イニシャライザから返されるオブジェクトは、自分が初期化したものと思っているオブジェクトではない場合があります。

割り当てられたばかりのオブジェクトではないオブジェクトが返される原因となる条件は2つあります。1つ目は、“シングルトンインスタンスでなければならない場合”と、“オブジェクトを定義する属性が一意でなければならない場合”の、2つの関連する状況が伴います。たとえばNSWorkspaceなど、一部のCocoaクラスでは、プログラム内で許されているインスタンスは1つのみです。そのようなクラスでは、作成されるインスタンスが1つだけであることを(イニシャライザの中で、または通常はクラスファクトリメソッドの中で)保証し、別の新しいインスタンスを作成する要求があった場合には、作成済みのインスタンスを返すようにする必要があります(シングルトンオブジェクトの実装の詳細については、“「シングルトンインスタンスの作成」”を参照してください)。

同様の状況は、オブジェクトを一意にする属性をオブジェクトに持たせる必要がある場合にも発生します。前述の架空のAccountクラスの例を思い起こしてください。どのようなアカウントにも、一意の識別子を持たせる必要があります。このクラスのイニシャライザ(たとえばinitWithAccountID:など)に、すでにオブジェクトに関連付けられている識別子が渡された場合、イニシャライザにおいて次の2つの処理を行う必要があります。

これらを実行することで、イニシャライザによって識別子の一意性が保たれ、求められたもの、つまり、要求された識別子を持つAccountインスタンスが提供されます。

場合によっては、init...メソッドで、要求された初期化を実行できないことがあります。たとえば、initFromFile:というメソッドで、ファイルの内容に基づいてオブジェクトを初期化するとします。ファイルへのパスは引数として渡されます。しかし、ファイルが指定の場所に存在しなければ、オブジェクトを初期化することはできません。initWithArray:イニシャライザにNSArrayオブジェクトではなくNSDictionaryオブジェクトを渡した場合にも、同様の問題が発生します。init...メソッドでオブジェクトを初期化できない場合には、次の処理を実行します。

イニシャライザからnilを返すことで、要求されたオブジェクトを作成できないことを示します。一般に、オブジェクトを作成するときは、戻り値がnilかどうかを確かめてから処理を続けてください。

id anObject = [[MyClass alloc] init];
if (anObject) {
    [anObject doSomething];
    // さらに多くのメッセージ
} else {
    // エラー処理
}

init...メソッドからは、nilや、明示的に割り当て行ったオブジェクト以外のオブジェクトが返される可能性があります。そのため、イニシャライザから返されるインスタンスではなく、allocallocWithZone:から返されるインスタンスを使用することは危険です。次のコードを考えてみましょう。

id myObject = [MyClass alloc];
[myObject init];
[myObject doSomething];

この例に示したinitメソッドは、nilを返したり、別のオブジェクトを代わりに返したりする可能性があります。nilに対しては例外を発生させずにメッセージを送ることが可能なので、前者の場合は(おそらく)デバッグという悩みの種が生じるくらいで、何も起きません。しかし、割り当てを行っただけの“未加工の”インスタンスではなく、必ず初期化済みのインスタンスを利用するようにしてください。割り当てメッセージと初期化メッセージをネストし、イニシャライザから返されるオブジェクトをテストした後に、処理を続けることをお勧めします。

id myObject = [[MyClass alloc] init];
if ( myObject ) {
    [myObject doSomething];
} else {
    // エラー回復処理
}

いったん初期化されたオブジェクトを再び初期化することはできません。たとえば、次の例に示す2度目の初期化は無視されます。

NSString *aStr = [[NSString alloc] initWithString:@"Foo"];
aStr = [aStr initWithString:@"Bar"];

イニシャライザの実装

クラスの唯一のイニシャライザとして機能するinit...メソッドを実装する場合、または複数のイニシャライザが存在するならその指定イニシャライザ“「複数のイニシャライザと指定イニシャライザ」”を参照)を実装する場合は、実行する必要のあるいくつかの重要なステップがあります。

Listing 2-3の中のinit...メソッドに、これらのステップを詳しく示します。

Listing 2-3  イニシャライザの例

- (id)initWithAccountID:(NSString *)identifier {
    if ( self = [super init] ) {
        Account *ac = [accountDictionary objectForKey:identifier];
        if (ac) { // そのIDを持つオブジェクトがすでに存在する
            [self release];
            return [ac retain];
        } 
        if (identifier) {
            accountID = [identifier copy];
            return self;
        } else {
            [self release];
            return nil;
        }
    } else
        return nil;
}

注: 簡単にしておくために、この例では引数がnilの場合にnilを返していますが、Cocoaにおいて推奨されるのは例外を引き起こすことです。

オブジェクトのすべてのインスタンス変数を明示的に初期化する必要はなく、オブジェクトが機能するために必要なものだけを初期化すれば十分です。多くの場合、割り当て時にインスタンス変数に対して実行される、0に設定するというデフォルトの初期化で十分です。必要に応じてインスタンス変数の保持またはコピーを必ず行ってください。

スーパークラスのイニシャライザを最初に呼び出さなければならないことが重要です。オブジェクトでは、そのクラスで定義されているインスタンス変数だけでなく、クラスのすべての祖先クラスで定義されているインスタンス変数もカプセル化されることに注意してください。superのイニシャライザを最初に呼び出すことで、継承チェーンをさかのぼった各クラスで定義されているインスタンス変数が確実に先に初期化されます。直接のスーパークラスは、そのイニシャライザの中で自身のスーパークラスのイニシャライザを呼び出します。呼び出されたイニシャライザは、自身のスーパークラスのメインのinit...メソッドを呼び出します。以下、同様の処理が実行されます(Figure 2-7を参照)。初期化ではその順序が適切であることが重要です。これは、スーパークラスで定義されているインスタンス変数が適切な値に初期化されていることが、以降のサブクラスの初期化で必要になる場合があるためです。


Figure 2-7  継承チェーンをさかのぼる初期化

Figure 2-7 継承チェーンをさかのぼる初期化

サブクラスを作成する際は、継承するイニシャライザが問題になります。場合によっては、クラスのインスタンスの初期化がスーパークラスのinit...メソッドで十分なこともあります。しかし、そのようにならない可能性のほうが高く、その場合はオーバーライドをする必要があります。オーバーライドをしなければスーパークラスの実装が呼び出されますが、スーパークラスでは下位のクラスに関して何もわからないため、インスタンスが正しく初期化されない可能性があります。

複数のイニシャライザと指定イニシャライザ

クラスでは複数のイニシャライザを定義できます。複数のイニシャライザがあると、クラスのクライアントから同じ初期化に対して異なる形式の入力を指定できます。たとえば、NSSetクラスは、同じデータを異なる形式で受け取る複数のイニシャライザをクライアントに提供します。あるイニシャライザはNSArrayオブジェクトを受け取り、別のイニシャライザはカウント付きの要素リストを受け取り、また別のイニシャライザは終端がnilの要素リストを受け取ります。

- (id)initWithArray:(NSArray *)array;
- (id)initWithObjects:(id *)objects count:(unsigned)count;
- (id)initWithObjects:(id)firstObj, ...;

サブクラスによっては、簡易イニシャライザを提供するものもあります。簡易イニシャライザは、必要な初期化パラメータをすべて受け取るイニシャライザにデフォルト値を渡します。通常、すべてを受け取るこのイニシャライザは指定イニシャライザであり、クラスの中で最も重要なイニシャライザです。たとえば、Taskというクラスがあり、次のシグネチャを持つ指定イニシャライザが宣言されているとします。

- (id)initWithTitle:(NSString *)aTitle date:(NSDate *)aDate;

Taskクラスには、単純に指定イニシャライザを呼び出し、二次(簡易)イニシャライザが明示的に要求していないパラメータのデフォルト値を渡すだけの、二次イニシャライザ群を含めることが考えられます(Listing 2-4を参照)。

Listing 2-4  二次イニシャライザ

- (id)initWithTitle:(NSString *)aTitle {
    return [self initWithTitle:title date:[NSDate date]];
}
 
- (id)init {
    return [self initWithTitle:@”Task”];
}

指定イニシャライザはクラスにとって重要な役割を果たします。superの指定イニシャライザを呼び出すことによって、継承したインスタンス変数が確実に初期化されます。この指定イニシャライザは通常、最も多くの引数を持ち、初期化処理の大部分を実行するinit...メソッドです。また、クラスの二次イニシャライザがメッセージをselfに送信して呼び出すのもこのイニシャライザです。

サブクラスを定義するときは、スーパークラスの指定イニシャライザを識別し、サブクラスの指定イニシャライザの中でメッセージをsuperに送ってスーパークラスの指定イニシャライザを呼び出せる必要があります。.また、継承したイニシャライザについても何らかの方法で確実に対応しておく必要があります。そして、必要と思われる簡易イニシャライザをいくつでも提供できます。クラスのイニシャライザの設計時には、指定イニシャライザどうしがsuperへメッセージを送ることで互いに結び付いており、他のイニシャライザがselfへメッセージを送ることでそのクラスの指定イニシャライザに結び付いていることに注意してください。

このことをわかりやすく説明するために、例を示しましょう。A、B、Cという3つのクラスがあるとします。クラスBはクラスAを継承し、クラスCはクラスBを継承します。各サブクラスでは属性をインスタンス変数として追加し、このインスタンス変数を初期化するためのinit...メソッド(つまり指定イニシャライザ)を実装します。また、二次イニシャライザも定義し、継承したイニシャライザを必要に応じて確実にオーバーライドします。これら3つすべてのクラスのイニシャライザとそれらの関係を、Figure 2-8に示します。


Figure 2-8  二次イニシャライザと指定イニシャライザとのやり取り

Figure 2-8 二次イニシャライザと指定イニシャライザとのやり取り

各クラスの指定イニシャライザは対象範囲の最も広いイニシャライザであり、サブクラスによって追加される属性を初期化するメソッドです。また、指定イニシャライザはsuperへメッセージを送ることでスーパークラスの指定イニシャライザを呼び出すinit...メソッドでもあります。この例では、クラスCの指定イニシャライザinitWithTitle:date:が、そのスーパークラスの指定イニシャライザinitWithTitle:を呼び出します。そして指定イニシャライザはさらにクラスAのinitメソッドを呼び出します。サブクラスを作成するときは、スーパークラスの指定イニシャライザを知っていることが常に重要です。

指定イニシャライザはこのように、superへのメッセージを通じて継承チェーンを上に向かって結び付いているのに対し、二次イニシャライザはselfへのメッセージを通じて自身のクラスの指定イニシャライザに結び付いています。二次イニシャライザは多くの場合、(この例のように)継承したイニシャライザをオーバーライドしたものになります。クラスCではinitWithTitle:をオーバーライドし、指定イニシャライザを呼び出してデフォルトの日付を渡しています。この指定イニシャライザはさらにクラスBの指定イニシャライザを呼び出します。クラスBの指定イニシャライザはオーバーライドされているメソッドinitWithTitle:です。initWithTitle:メッセージをクラスBおよびクラスCのオブジェクトに送ると、異なるメソッド実装を呼び出すことになります。一方、クラスCでinitWithTitle:をオーバーライドしなかった場合、クラスCのインスタンスにメッセージを送ると、クラスBの実装を呼び出すことになります。その結果、Cのインスタンスの初期化は不完全になります(日付がないため)。サブクラスを作成するときは、継承するすべてのイニシャライザについて適切に対応しておくことが重要です。

場合によっては、サブクラスにとってスーパークラスの指定イニシャライザで十分で、サブクラス独自の指定イニシャライザを実装する必要がないことがあります。それ以外の場合、クラスの指定イニシャライザが、そのスーパークラスの指定イニシャライザをオーバーライドしたものとなります。この状況は、サブクラスで独自のインスタンス変数を追加しない場合でも(あるいは追加したインスタンス変数が明示的な初期化を必要としない場合でも)、スーパークラスの指定イニシャライザによって実行される処理をサブクラスで補う必要があるときに、多く発生します。

deallocメソッド

deallocメソッドは、数多くの点でクラスのinit...メソッドと対になる存在であり、特にクラスの指定イニシャライザと対になる存在です。deallocはオブジェクトの割り当ての直後に呼び出されるのではなく、オブジェクトの破棄の直前に呼び出されます。また、deallocメソッドはオブジェクトのインスタンス変数が適切に初期化されることを保証するのではなく、オブジェクトのインスタンス変数が解放され、動的に割り当てられたメモリがすべて解放されることを保証します。

そして、最後の対照点は、同じメソッドのスーパークラス実装の呼び出しに関することです。イニシャライザでは、最初のステップとして、スーパークラスの指定イニシャライザの呼び出しを行います。deallocでは、最後のステップとして、スーパークラスのdealloc実装の呼び出しを行います。この理由は、イニシャライザを最初に呼び出す理由の正反対です。つまり、サブクラスでは、祖先クラスのインスタンス変数を解放する前に、自身が所有するインスタンス変数を先に解放する必要があるからなのです。

このメソッドの実装例をListing 2-5に示します。

Listing 2-5  deallocメソッドの例

- (void)dealloc {
    [accountDictionary release];
    if ( mallocdChunk != NULL )
        free(mallocdChunk);
    [super dealloc];
}

この例に示すように、mallocのインスタンス変数を解放するときは、その前にインスタンス変数がNULLでないことを確認するのが賢明です。オブジェクトのインスタンス変数の場合は、nilオブジェクトにメッセージを送っても安全なので、そのような注意は必要ありません。

クラスファクトリメソッド

クラスファクトリメソッドは、クライアント向けに簡易メソッドとして、クラスによって実装されます。クラスファクトリメソッドは、割り当てと初期化を1つのステップに結合し、作成したオブジェクトを自動解放に設定して返します。これらのメソッドは+ ( type ) className ...という形式です(classNameではプレフィックスがすべて除外されます)。

Cocoaでは、特に“値”クラス群においてこれらのメソッドの例が多数あります。NSDateには次のクラスファクトリメソッドが含まれます。

+ (id)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs;
+ (id)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)secs;
+ (id)dateWithTimeIntervalSince1970:(NSTimeInterval)secs;

また、NSDataは次のファクトリメソッドを提供します。

+ (id)dataWithBytes:(const void *)bytes length:(unsigned)length;
+ (id)dataWithBytesNoCopy:(void *)bytes length:(unsigned)length;
+ (id)dataWithBytesNoCopy:(void *)bytes length:(unsigned)length
        freeWhenDone:(BOOL)b;
+ (id)dataWithContentsOfFile:(NSString *)path;
+ (id)dataWithContentsOfURL:(NSURL *)url;
+ (id)dataWithContentsOfMappedFile:(NSString *)path;

ファクトリメソッドは単純な簡易メソッド以上の働きが可能です。割り当てと初期化を結合するだけでなく、割り当て処理から初期化処理に対して通知を行うことができます。たとえば、コレクション(NSStringオブジェクト、NSDataオブジェクト、NSNumberオブジェクトなど)用に任意の数の要素をエンコードしたプロパティリストファイルから、コレクションオブジェクトを初期化しなければならないとします。ファクトリメソッドでは、コレクションの割り当てに必要なメモリ量を知るために、まずファイルを読み取ってプロパティリストを解析し、要素の個数とそれぞれの型を調べる必要があります。

クラスファクトリメソッドのもう1つの目的は、特定のクラス(NSWorkspaceなど)によってシングルトンインスタンスが供給されることを保証することです。init...メソッドでは、プログラムに存在するインスタンスが常時1つのみであること確認できますが、それには事前に“未加工の”インスタンスを割り当て、その後そのインスタンスを解放する必要が生じます。一方、ファクトリメソッドでは、オブジェクト用に使用しない可能性のあるメモリをやみくもに割り当てるのを防ぐ手段が用意されています(Listing 2-6を参照)。

Listing 2-6  シングルトンインスタンス用のファクトリメソッド

static AccountManager *DefaultManager = nil;
 
+ (AccountManager *)defaultManager {
    if (!DefaultManager) DefaultManager = [[self allocWithZone:NULL] init];
    return DefaultManager;
}

参考資料: Cocoaオブジェクトの割り当てと初期化に関連することがらの詳細については、『Objective-Cプログラミング言語』の「Objective-Cランタイムシステム」を参照してください。



< 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