2012年1月22日日曜日

列挙子の使い方と注意


配列要素にアクセスするとき、Cでは要素数を自分で管理する必要があった。
要素数を超えてアクセスしても言語側は知らんぷりなので、暴走の原因になることもしばしばである。
(高速化などトリックにも使えるのだが、それは上級者のみに許された究極の技。)
そもそも、要素数をユーザーが管理する必要がある。

しかし、Cocoaのクラスでは要素数をシステムが管理してくれるので、範囲を超えて越えてアクセスすると
例外がかかり止まる。また、要素数は[count]メッセージで取得出来る。
そのため、安全(要素個数を超えることなく)にループを回すことが出来る。

要素番号が必要な場合はこのループを使う。
NSUInteger i; // 要素番号
NSArray *array = [NSArray arrayWithObjects:@"abc", @"def", @"ghi", @"jkl", nil];
for (i = 0; i < [array count]; i++) {
    NSLog(@"index: %d, value: %@\n", i, [array objectAtIndex:i]);
}

Objective-Cでは「列挙子」というものを使ってループを構築することも出来る。
これは要素の終端を判定してループを構築するもので、要素番号を保持しておく必要がなくなる
(逆に言えば、ループ内では要素番号を参照することはそのままでは出来ない)。
これにはNSEnumeratorとwhileループを使う。
NSArray *array = [NSArray arrayWithObjects:@"abc", @"def", @"ghi", @"jkl", nil];
NSEnumerator *enumerator = [array objectEnumerator];
id obj;
while (obj = [enumerator nextObject]) { // nilで終了
    NSLog(@"value: %@\n", obj);
}
// enumeratorは解放の必要がない(というかしたら駄目)
objectEnumeratorの部分をreverseObjectEnumeratorにすると、配列の終端から先頭=0に向けての逆順になる。
例はNSArrayに対するものであるが、NSDictionaryでも同様な処理がかける。

Objective-C 2.0ではさらにfor文が拡張され、配列・辞書の内容が高速かつ安全に「参照」出来る様になっている。
高速列挙子である。ここで言う「高速」はコンパイラが最適化したコードを発生するという意味であり、
「安全」は終了判定を記述する必要がないことを意味する。
NSArray *array = [NSArray arrayWithObjects:@"abc", @"def", @"ghi", @"jkl", nil];
for (id obj in array) { // 新しいクラス変数を作り、上記arrayの内容を参照する(最後まで)
    NSLog(@"value: %@\n", obj);
}

(高速)列挙子を使ったループをMutableで構築する場合、1つ注意がある。
それは、ループ内で要素の更新をしてはいけないことである。
NSMutaleArray *marray = [NSMutableArray arrayWithObjects:@"abc", @"def", @"ghi", @"jkl", nil];
NSEnumerator *enumerator = [marray objectEnumerator];
id obj;
NSInterger i=0;
while (obj = [enumerator nextObject]) { // nilで終了
    NSLog(@"value: %@\n", obj);
    ~
    [marray replaceObjectAtIndex:i withObject:obj];
    i++;
}
とすると、例外が発生する。replaceObjectAtIndexのように、配列要素数が変化しない場合でも
同様である。要素を変更する場合は、素直に要素番号を持ってループを回すしかない。






0 件のコメント:

コメントを投稿