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

< 前ページ次ページ >

高速列挙

Objective-C 2.0には、簡潔な構文を使ってコレクションの内容を効率よく安全に列挙できる言語機能が提供されています。

for…in機能

Objective-C 2.0には、コレクションの内容を列挙できる言語機能が提供されています。構文は次のように定義されています。

for ( Type newVariable in expression ) { stmts }

または

Type existingItem;
for ( existingItem in expression ) { stmts }

どちらの場合も、expressionNSFastEnumerationプロトコルに準拠したオブジェクト、すなわち通常は配列または列挙子を生成します(CocoaコレクションクラスのNSArrayNSDictionaryNSSetはこのプロトコルを採用し、NSEnumeratorも同様に採用しています)。他のオブジェクトのコレクションにアクセスできるインスタンスを持つクラスはすべて、NSFastEnumerationプロトコルを採用できます(NSFastEnumerationプロトコルの実装を参照)。NSArrayNSSetの場合は、列挙は自身の内容が対象であることは明白です。それ以外のクラスは、反復処理の対象のプロパティを明確にする必要があります。たとえばNSDictionary、およびCore DataクラスのNSManagedObjectModelは高速列挙に対応しており、NSDictionaryはそのキーを、NSManagedObjectModelはそのエンティティを列挙します。

高速列挙を使用する利点は次のようにいくつかあります。

反復中のオブジェクトの変更が禁止されているため、複数の列挙を同時に実行できます。

高速列挙の使用

次のコード例は、NSArrayオブジェクトとNSDictionaryオブジェクトによる高速列挙の使用を示しています。

NSArray *array = [NSArray arrayWithObjects:
        @"One", @"Two", @"Three", @"Four", nil];
 
for (NSString *element in array) {
    NSLog(@"element:%@", element);
}
 
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
    @"quattuor", @"four", @"quinque", @"five", @"sex", @"six", nil];
 
NSString *key;
for (key in dictionary) {
    NSLog(@"English:%@, Latin:%@", key, [dictionary valueForKey:key]);
}

次の例に示すように、NSEnumeratorオブジェクトと高速列挙を使うこともできます。

NSArray *array = [NSArray arrayWithObjects:
        @"One", @"Two", @"Three", @"Four", nil];
 
NSEnumerator *enumerator = [array reverseObjectEnumerator];
for (NSString *element in enumerator) {
    if ([element isEqualToString:@"Three") {
        break;
    }
}
 
NSString *next = [enumerator nextObject];
// next = "Four"

NSFastEnumerationプロトコルの実装

高速列挙をサポートするカスタムクラスを実装するには、NSFastEnumerationプロトコルを実装する必要があります。このプロトコルは、単一のメソッド、countByEnumeratingWithState:objects:count:で構成されています。

@protocol NSFastEnumeration
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len;
@end

最初のパラメータは列挙構造体です(NSFastEnumerationState)。

typedef struct {
    unsigned long state;
    id *itemsPtr;
    unsigned long *mutationsPtr;
    unsigned long extra[5];
} NSFastEnumerationState;

どの実装も、次のルールに従う必要があります。

  1. 初回の開始時は"state"フィールドは0であり、それ以外の場合ではクライアントはstateを変更できず、変更する必要も生じません。stateは、ゼロ以外のカウントが返される前にゼロ以外に設定する必要があります(そうしないと、クライアントはゼロが返されることを期待して無限ループに入り、オブジェクトは常に新しい列挙を開始することになります)。複雑な列挙の場合は、追加の状態を確保するために"extra"を指定します。

  2. 終了時は、0のカウントは列挙に使用できるオブジェクトがないことを意味します。これは、不正あるいは矛盾した内部状態を示している場合がありますが、正常な場合は単にそれ以上使用可能なオブジェクトがないことを示していることもあります。しかし値がゼロの場合、クライアントはstateの内容について何も想定してはなりません。

  3. ゼロ以外のカウントの戻り値は、itemsPtrがゼロ個でないオブジェクトの先頭を指すように設定されていることを意味します。これは、可能であれば内部オブジェクト状態へのポインタであることが推奨されます。実現可能でない場合、クライアントは"objects"パラメータにスタックバッファを、"count"パラメータに長さを指定して、反復すべきオブジェクトポインタがそこにコピーされるようにします(GCプリミティブは必要ありません)。さらに、mutationsPtrはこのオブジェクトの何らかの形の変更カウンタを追跡できる有効なアドレスに設定されます。オブジェクトが不変の場合、このポインタはオブジェクト自身を指すこともできます。

  4. このプロトコルは、nilオブジェクトに送信されたときに動作するように設計されています。

countByEnumeratingWithState:objects:count:が呼び出されると、レシーバには最初のフィールド状態がゼロに設定された列挙構造体が渡されます。列挙が終了すると、このメソッドは0を返します。項目は、itemsPtrフィールドを設定することにより間接的に返されます。mutationsPtrフィールドは、オブジェクトが変更された場合に変わった場所を指すように設定する必要があります。変更が不可能であれば、このポインタはオブジェクト自身に設定でき、その最初のフィールドは不変になります。stackbuf引数を指定すると、これをコピーされた複数の項目を保持するバッファとして使うか、または“extra”状態フィールドを使って返された値を保持することができます。この状態フィールドは、反復を実行するために必要な任意の方法で使用できます。状態構造体はガーベジコレクションの観点からはスタックローカルメモリであると見なされ、格納時に書き込みバリアを必要としないため、渡された状態構造体をより反復に適した構造体に再キャストできます。

コンパイラは次のコードを、

for ( existingItem in expression ) { stmts }

次のように変換します。

{
    id elem;
    NSEnumerationState __enumState = { 0 };
    id __items[16];
 
    unsigned long __limit =
        [collection countByEnumeratingWithState:&__enumState objects:__items count:16];
    unsigned __counter = 0;
    unsigned long __startMutations = * __enumState.mutationsPtr;
 
    if (__limit) do {
 
        __counter = 0
        do {
            if (__startMutations != * __enumState.mutationsPtr) objc_enumerationMutation();
            elem = __enumState.itemsPtr[__counter++];
stmts;
        } while (__counter0 < __limit);
 
    } while (__limit = [collection countByEnumeratingWithState:&__enumState objects:__items count:16]);
};

まず、stateフィールドが0に設定され、初回のエントリであることを示します。次に、countByEnumeratingWithState:objects:count:が呼び出され、反復する項目の最初のバッチのカウントが返されます。この値がゼロであれば列挙は終了し、それ以外の場合、プログラムは内側のループに入り、項目に対して反復処理を行います。項目はそれが終了すると、別のバッチを取得する外側のループへ抜ける準備ができています。項目ごとに、コンパイラは間接ポインタを逆参照する前に、まずコレクションが変更されたかどうかをチェックします。コレクションが変更されていれば、例外が発生します。変更されていなければ、項目が取り出され、指定されたstmtsに制御が渡されます。



< 前ページ次ページ >


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