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

< 前ページ次ページ >

オブジェクトの割り当てと初期化

Objective-Cを使ってオブジェクトを作成するには、次の2つのステップを実行する必要があります。

2つのステップを完了するまで、オブジェクトは完全には機能しません。各ステップを実行するのは別々のメソッドですが、通常は1行のコードに記述します。

id anObject = [[Rectangle alloc] init];

割り当てと初期化を分離すると、各ステップを個別に制御できるため、それぞれを他方から切り離して変更することができます。以降のセクションでは、割り当てと初期化の順に説明し、それらを制御、変更する方法について説明します。

Objective-Cでは、NSObjectクラスで定義されたクラスメソッドを使用して、新しいオブジェクトのメモリを割り当てます。このために、NSObjectでは2つの主要なメソッド、allocallocWithZone:を定義しています。

+ (id)alloc;
+ (id)allocWithZone:(NSZone *)zone;

これらのメソッドは、受信側クラスに属するオブジェクトに、すべてのインスタンス変数を格納するために十分なメモリを割り当てます。これらのメソッドを、サブクラスでオーバーライドして変更する必要はありません。

allocおよびallocWithZone:メソッドは、新たに割り当てられたオブジェクトのisaインスタンス変数を初期化して、オブジェクトのクラス(クラスオブジェクト)を指すようにします。他のインスタンス変数は、すべて0に設定されます。通常、オブジェクトは明確に初期化しないと、安全に使用できません。

このような初期化は、慣例により、省略形「init」で始まるクラス固有のインスタンスメソッドの役割です。メソッドが引数を持たない場合、メソッド名はそれらの4文字のみ、つまりinitになります。引数を持つ場合、「init」プレフィックスの後に引数のラベルが続きます。たとえば、NSViewオブジェクトはinitWithFrame:メソッドで初期化することができます。

インスタンス変数を宣言するどのクラスも、init...メソッドを提供して、インスタンス変数を初期化する必要があります。NSObjectクラスでは、isa変数を宣言し、initメソッドを定義します。しかし、isaはオブジェクトにメモリを割り当てるときに初期化されるため、NSObjectinitメソッドが実行することはselfを返すことだけです。NSObjectでは、主に前述の命名規則を確立するためにメソッドを宣言します。

このセクションの内容:

返されるオブジェクト
引数
クラスの調整
指定イニシャライザ
割り当てと初期化の結合


返されるオブジェクト

init...メソッドは、通常、レシーバのインスタンス変数を初期化して、それを返します。エラーなしで使用できるオブジェクトを返すのはメソッドの役割です。

しかし、場合によっては、この役割は、レシーバではなく別のオブジェクトを返すことを意味する場合もあります。たとえば、命名されたオブジェクトのリストをクラスが保持している場合、新しいインスタンスを初期化するinitWithName:メソッドを提供することができます。同じ名前のオブジェクトが1つしか存在できない場合は、initWithName:で同じ名前を2つのオブジェクトに割り当てることを拒否できます。別のオブジェクトにすでに使用されている名前を新しいインスタンスに割り当てるように要求すると、このメソッドは新たに割り当てられたインスタンスを解放し、他方のオブジェクトを返すことがあります。こうすることで、名前の一意性を確保しながら、要求されたもの、つまり要求された名前の付いたインスタンスを返します。

まれなケースですが、init...メソッドが要求されたことを実行できない場合もあります。たとえば、initFromFile:メソッドは、引数として渡されたファイルから必要なデータを取得する場合があります。渡されたファイル名が実際のファイルと一致していないと、初期化を完了することができません。このような場合、init...メソッドはレシーバを解放して、nilを返し、要求されたオブジェクトを作成できないことを示せます。

init...メソッドは新たに割り当てられたレシーバ以外のオブジェクトを返すか、nilを返すことがあるため、プログラムではallocまたはallocWithZone:が返す値だけでなく、初期化メソッドが返す値を使用することが重要です。次のコードはinitが返す値を無視しているため、非常に危険です。

id anObject = [SomeClass alloc];
[anObject init];
[anObject someOtherMessage];

この代わりに、オブジェクトを安全に初期化するには、割り当ておよび初期化メッセージを1行のコードに結合する必要があります。

id anObject = [[SomeClass alloc] init];
[anObject someOtherMessage];

init...メソッドがnilを返す可能性がある場合は、先へ進む前に戻り値をチェックします。

id anObject = [[SomeClass alloc] init];
if ( anObject )
    [anObject someOtherMessage];
else
    ...

引数

init...メソッドでは、オブジェクトのインスタンス変数のすべてが、適正な値であることを保証する必要があります。これは、各変数に対応して引数を用意する必要があるという意味ではありません。一部をデフォルト値に設定するか、新しいオブジェクトに割り当てられたメモリのすべてのビット(isaを除く)が0に設定されるという事実に依存することができます。たとえば、あるクラスがそのインスタンスに名前とデータソースを持つことを要求する場合は、initWithName:fromFile:メソッドを用意することも考えられますが、重要でないインスタンス変数は任意の値に設定したり、デフォルトでNULL値に設定することができます。そして初期化段階が完了した後で、デフォルト値を変更するために、setEnabled:setFriend:、およびsetDimensions:のようなメソッドに頼ることができます。

引数を持つinit...メソッドでは、不適切な値が渡された場合の処理を準備する必要があります。

クラスの調整

インスタンス変数を宣言するすべてのクラスでは、それらを初期化するためにinit...メソッドを提供する必要があります(ただし、変数が初期化を必要としない場合は除きます)。クラスで定義するinit...メソッドは、クラスで宣言されている変数のみを初期化します。継承したインスタンス変数は、継承階層の上位のどこかで定義された初期化メソッドを実行するため、superにメッセージを送信することで初期化します。

- initWithName:(char *)string
{
    if ( self = [super init] ) {
        name = (char *)NSZoneMalloc([self zone],
            strlen(string) + 1);
        strcpy(name, string);
    }
    return self;
}

superへのメッセージは、継承クラスのすべての初期化メソッドを結び付けます。これが先に実行されるため、スーパークラスの変数がサブクラスで宣言されている変数よりも先に初期化されることが保証されます。たとえば、RectangleオブジェクトはRectangleとして初期化される前に、NSObject、Graphic、およびShapeとして初期化される必要があります。

前述のinitWithName:メソッドと、そこに組み込まれている継承したinitメソッドとの関係を、図 12-1に示します。


図 12-1  継承した初期化メソッドの組み込み

継承した初期化メソッドの組み込み

また、クラスでは、継承したすべての初期化メソッドが必ず機能するようにしなければなりません。たとえば、図 12-1で示すように、クラスAでinitメソッドを定義し、そのサブクラスBでinitWithName:メソッドを定義する場合、サブクラスBもinitメッセージがBインスタンスを確実に初期化できる必要があります。これを実行する最も簡単な方法は、継承したinitメソッドを、initWithName:を呼び出すバージョンに置き換えることです。

- init
{
    return [self initWithName:"default"];
}

前述のように、initWithName:メソッドも、継承したメソッドを呼び出します。図 12-2は、initのサブクラスBのバージョンを示しています。


図 12-2  継承した初期化モデルの組み込み

継承した初期化モデルの組み込み

継承した初期化メソッドを組み込むと、定義したクラスを他のアプリケーションに移植しやすくなります。継承したメソッドを組み込まない場合、それを使用する誰か別の人によって、定義したクラスの不正に初期化されたインスタンスが作成される可能性があります。

指定イニシャライザ

上記の例では、initWithName:が対象クラス(クラス B)の指定イニシャライザになります。指定イニシャライザは、(継承メソッドを実行するため、superにメッセージを送信することで)継承したインスタンス変数の初期化を保証する各クラスのメソッドです。指定イニシャライザはまた、作業の大部分を実行するメソッドであり、同じクラスの他の初期化メソッドが呼び出すメソッドでもあります。Cocoaの規則として、指定イニシャライザは常に、新しいインスタンスの特性を決めるための最大の自由を与えるメソッドです(通常、これは最も多くの引数を持つメソッドですが、いつもそうとはかぎりません)。

サブクラスを定義する際には、指定イニシャライザを知っていることが重要です。たとえば、クラスBのサブクラスとしてクラスCを定義し、initWithName:fromFile:メソッドを実装するとします。このメソッドに加えて、継承したinitおよびinitWithName:メソッドも、Cのインスタンスに対して機能するようにしなければなりません。これを行うには、initWithName:fromFile:を呼び出すバージョンに、BのinitWithName:を組み込みます。

- initWithName:(char *)string
{
    return [self initWithName:string fromFile:NULL];
}

Cクラスのインスタンスでは、継承したinitメソッドはinitWithName:fromFile:を呼び出す、initWithName:の新しいバージョンを呼び出します。これらのメソッド間の関係を図 12-3に示します。


図 12-3  指定イニシャライザの組み込み

指定イニシャライザの組み込み

この図では、重要な部分が省略されています。initWithName:fromFile:メソッドは、Cクラスの指定イニシャライザであるため、継承した初期化メソッドを呼び出すため、superにメッセージを送信します。しかし、Bクラスのメソッドのうち、それが呼び出すのはinitまたはinitWithName:のどちらでしょう。次の2つの理由により、initを呼び出すことはできません。

したがって、initWithName:fromFile:initWithName:を呼び出す必要があります。

- initWithName:(char *)string fromFile:(char *)pathname
{
    if ( self = [super initWithName:string] )
        ...
}

一般原則:: クラスの指定イニシャライザは、superへのメッセージを通じて、スーパークラスの指定イニシャライザを呼び出さなければなりません。

指定イニシャライザはsuperへのメッセージを通して相互に結び付いており、他の初期化メソッドはselfへのメッセージを通して指定イニシャライザに結び付いています。

図 12-4 は、クラスA、B、およびCのすべての初期化メソッドがどのようにリンクしているかを示しています。selfへのメッセージが左側、superへのメッセージが右側に示されています。


図 12-4  初期化チェーン

初期化チェーン

initのBバージョンは、initWithName:メソッドを呼び出すため、selfにメッセージを送信することに注目してください。そのため、レシーバがBクラスのインスタンスである場合はinitWithName:のBのバージョンを呼び出し、レシーバがCクラスのインスタンスである場合はそのCのバージョンを呼び出します。

割り当てと初期化の結合

Cocoaでは、割り当てと初期化の2つのステップを結合し、クラスの初期化された新しいインスタンスを返す、作成メソッドをいくつかのクラスで定義しています。これらのメソッドは、簡易コンストラクタとも呼ばれ、通常は+ className...classNameはクラス名)の形式をとります。たとえば、NSStringには(他を含め)次のメソッドがあります。

+ (NSString *)stringWithCString:(const char *)bytes;
+ (NSString *)stringWithFormat:(NSString *)format, ...;

同様に、NSArrayでは、割り当てと初期化を結合する次のクラスメソッドを定義しています。

+ (id)array;
+ (id)arrayWithObject:(id)anObject;
+ (id)arrayWithObjects:(id)firstObj, ...;

重要: ガーベジコレクションを使用しない場合は、これらのメソッドの使用から生じるメモリ管理上の影響を理解することが重要です(メモリ管理を参照)。これらの簡易コンストラクタに適用されるポリシーを理解するには、『Memory Management Programming Guide for Cocoa』を読む必要があります。

割り当てと初期化を結合するメソッドは、割り当てが何らかの形で初期化によって通知される必要がある場合に特に役立ちます。たとえば、初期化のためのデータをファイルから取得する場合に、そのファイルに複数のオブジェクトを初期化するのに十分なデータが含まれていても、ファイルを開くまではいくつのオブジェクトを割り当てるか分かりません。このような場合は、ファイル名を引数として受け取るlistFromFile:メソッドを実装することができます。このメソッドは、ファイルを開いて、割り当てるオブジェクトの数を確認し、新しいオブジェクトをすべて格納するのに十分な大きさのListオブジェクトを作成します。次に、ファイル内のデータに基づいてオブジェクトを割り当てて初期化し、Listに入れ、最終的にそのListを返します。

また、使用しない可能性のある新しいオブジェクトに対して無駄にメモリを割り当てるステップを回避したい場合は、割り当てと初期化を1つのメソッドに結合することには意味があります。返されるオブジェクトで説明したように、init...メソッドではレシーバの代わりに別のオブジェクトを使用できます。たとえば、すでに割り当てられている名前がinitWithName:に渡されると、レシーバが解放され、その代わりに、先にその名前が割り当てられたオブジェクトを返すことが考えられます。これはもちろん、オブジェクトを割り当て、使用せずにすぐ解放することを意味します。

レシーバを初期化するかどうかを決定するコードが、init...の中ではなく、割り当てを実行するメソッド内にあれば、不要な新しいインスタンスを割り当てるステップを回避することができます。

次の例では、soloistメソッドで、Soloistクラスのインスタンスが1つしかないことを確認しています。このメソッドは、インスタンスを一度だけ割り当てて初期化します。

+ soloist
{
    static Soloist *instance = nil;
 
    if ( instance == nil )
    {
        instance = [[self alloc] init];
    }
    return instance;
}


< 前ページ次ページ >


Last updated: 2007-10-31




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