1. ホーム
  2. オブジェクティブC

[解決済み】NSArrayを反復処理する方法は?

2022-04-18 11:49:01

質問

NSArrayを繰り返し処理するための標準的なイディオムを探しています。私のコードはOS X 10.4+に適したものである必要があります。

どのように解決するのですか?

10.5+/iOSで一般的に推奨されるコードです。

for (id object in array) {
    // do something with object
}

に準拠したコレクション内のオブジェクトを列挙するために使用されます。 NSFastEnumeration プロトコルに準拠しています。この方法は、(1回のメソッド呼び出しで得られる)複数のオブジェクトへのポインタをバッファに格納し、ポインタ演算でバッファを進めることで反復処理するため、速度面で有利です。これは 大いに を呼び出すよりも高速です。 -objectAtIndex: をループさせる。

また、技術的には できる をステップ実行するためにフォーインループを使用します。 NSEnumerator しかし、これでは高速な列挙の利点がほとんどなくなってしまうことがわかりました。その理由は、デフォルトの NSEnumerator の実装は -countByEnumeratingWithState:objects:count: は、各コールでバッファにオブジェクトを1つだけ配置します。

で報告しました。 radar://6296108 (Fast enumeration of NSEnumerators is sluggish) が、Not To Be Fixed として返されました。その理由は、高速列挙はオブジェクトのグループを事前にフェッチするため、列挙子内の所定のポイントまでしか列挙せず(例えば、特定のオブジェクトが見つかるか、条件を満たすまで)、ループから抜け出した後に同じ列挙子を使用する場合、いくつかのオブジェクトがスキップされることがよくあるためです。

OS X 10.6 / iOS 4.0 以上でコーディングする場合、配列やその他のコレクションを列挙するためにブロックベースのAPIを使用するオプションも用意されています。

[array enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
    // do something with object
}];

を使用することもできます。 -enumerateObjectsWithOptions:usingBlock: を渡すと NSEnumerationConcurrent および/または NSEnumerationReverse をオプションの引数として指定します。


10.4以前

10.5以前の標準的なイディオムとしては NSEnumerator というように、whileループを使用します。

NSEnumerator *e = [array objectEnumerator];
id object;
while (object = [e nextObject]) {
  // do something with object
}

シンプルにすることをお勧めします。配列の型に縛られるのは柔軟性に欠け、また -objectAtIndex: は、10.5+での高速列挙による改善に比べたら、どうせ取るに足らないものです。(高速列挙は実際には基礎となるデータ構造のポインタ演算を使用し、メソッド呼び出しのオーバーヘッドのほとんどを取り除きます)。早まった最適化は決して良い考えとは言えません。とにかくボトルネックではない問題を解決するために、より複雑なコードになってしまうからです。

を使用する場合 -objectEnumerator のような)他の列挙可能なコレクションに簡単に変更することができます。 NSSet のキーは NSDictionary など)に切り替えたり、あるいは -reverseObjectEnumerator を使えば、配列を逆に列挙することができ、他のコードを変更することなく、すべてを行うことができます。もし反復処理のコードがメソッド内にあるならば、任意の NSEnumerator を気にする必要はありません。 を反復している。さらに NSEnumerator (少なくとも Apple のコードで提供されるものは) 列挙しているコレクションを、さらにオブジェクトがある限り保持するので、自動解放されたオブジェクトがいつまで存在するか心配する必要はありません。

おそらく NSEnumerator (または高速列挙)があなたを守るのは、変更可能なコレクション(配列またはその他)があなたの下で変更されることです。 知らないうちに 列挙している間に インデックスでオブジェクトにアクセスすると、奇妙な例外や1つ違いのエラーに遭遇することがあり、(多くの場合、問題が発生してからずっと後に)デバッグするのが恐ろしいことになります。標準的なイディオムの1つを使った列挙は、"fail-fast"の動作をするので、(間違ったコードが原因の)問題は、変異が起こった後に次のオブジェクトにアクセスしようとすると、すぐに現れます。プログラムがより複雑になり、マルチスレッドになったり、サードパーティのコードが変更する可能性があるものに依存するようになると、脆弱な列挙コードはますます問題になります。カプセル化と抽象化 FTW! :-)