NSArrayを反復処理する標準的なイディオムを探しています。私のコードはOS X 10.4+に適している必要があります。
NSArrayを反復処理する標準的なイディオムを探しています。私のコードはOS X 10.4+に適している必要があります。
回答:
10.5 + / iOSで一般的に推奨されるコード。
for (id object in array) {
// do something with object
}
この構成は、NSFastEnumeration
プロトコルに準拠するコレクション内のオブジェクトを列挙するために使用されます。このアプローチは、(単一のメソッド呼び出しによって取得された)複数のオブジェクトへのポインターをバッファーに格納し、ポインター演算を使用してバッファーを前進させることによってそれらを反復するため、速度の利点があります。これは、ループを介して毎回呼び出すよりもはるかに高速です-objectAtIndex:
。
また、技術的には for-inループを使用してをステップスルーできますNSEnumerator
が、これにより高速列挙の速度の利点のほとんどすべてが無効になることもわかりました。その理由は、のデフォルトのNSEnumerator
実装で-countByEnumeratingWithState:objects:count:
は、各呼び出しでバッファにオブジェクトを1つだけ配置するためです。
radar://6296108
(NSEnumeratorsの高速列挙が遅い)でこれを報告しましたが、修正予定ではないとして返されました。その理由は、高速列挙はオブジェクトのグループをプリフェッチし、列挙子の特定のポイントのみを列挙したい場合(たとえば、特定のオブジェクトが見つかるか、条件が満たされるまで)、ブレークアウト後に同じ列挙子を使用するためです。ループの場合、いくつかのオブジェクトがスキップされることがよくあります。
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.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つずれるエラーが発生し(多くの場合、問題が発生してから長い間)、デバッグが非常に困難になる可能性があります。標準のイディオムのいずれかを使用した列挙は「フェイルファースト」の動作をするため、ミューテーションが発生した後で次のオブジェクトにアクセスしようとすると、問題(不正なコードが原因)がすぐに現れます。プログラムがより複雑になり、マルチスレッド化されるか、サードパーティのコードが変更する可能性のあるものに依存するようになると、脆弱な列挙型コードはますます問題になります。カプセル化と抽象化FTW!:-)
for (id object in array)
して、配列内のオブジェクトの現在のインデックスも特定する方法はありますか、それとも別のカウンタを含める必要がありますか?
for
、次のようにループを:for(;;) { id object = [ e nextObject ] ; if ( !e ) { break ; } ... your loop operation ... }
OS X 10.4.x以前の場合:
int i;
for (i = 0; i < [myArray count]; i++) {
id myArrayElement = [myArray objectAtIndex:i];
...do something useful with myArrayElement
}
OS X 10.5.x(またはiPhone)以降の場合:
for (id myArrayElement in myArray) {
...do something useful with myArrayElement
}
for (NSUInteger i = 0, count = [myArray count]; i < count; i++)
このアプローチでは、おそらく使用するのが最も効率的で簡潔です。
テストとソースコードの結果は以下のとおりです(アプリで反復回数を設定できます)。時間はミリ秒単位で、各エントリはテストを5〜10回実行した平均結果です。私は通常、2〜3桁の有効桁数であり、その後は実行ごとに異なることを発見しました。これにより、誤差が1%未満になります。テストは私が興味を持っていたターゲットプラットフォームなので、iPhone 3Gで実行されていました。
numberOfItems NSArray (ms) C Array (ms) Ratio
100 0.39 0.0025 156
191 0.61 0.0028 218
3,256 12.5 0.026 481
4,789 16 0.037 432
6,794 21 0.050 420
10,919 36 0.081 444
19,731 64 0.15 427
22,030 75 0.162 463
32,758 109 0.24 454
77,969 258 0.57 453
100,000 390 0.73 534
Cocoaがデータセットを処理するために提供するクラス(NSDictionary、NSArray、NSSetなど)は、メモリ管理や再割り当てなどの官僚性を心配する必要なく、情報を管理するための非常に優れたインターフェースを提供します。もちろん、これにはコストがかかります。NSNumbersのNSArrayを使用すると、単純な反復でfloatのC配列よりも遅くなることは明らかなので、いくつかのテストを行うことにしましたが、結果はかなり衝撃的でした!こんなに悪いとは思っていませんでした。注:これらのテストは、私が興味を持っていたターゲットプラットフォームであるため、iPhone 3Gで実行されます。
このテストでは、C float *とNSNumbersのNSArrayの間の非常に単純なランダムアクセスパフォーマンス比較を行います
単純なループを作成して、各配列の内容を合計し、mach_absolute_time()を使用して時間を計測します。NSMutableArrayは平均で400倍長くかかります!! (400%ではなく、400倍長くなります!つまり、40,000%長くなります!)。
ヘッダ:
// Array_Speed_TestViewController.h
//アレイ速度テスト
// 2009年5月2日にMehmet Aktenによって作成されました。
// Copyright MSA Visuals Ltd. 2009. All rights reserved。
#import <UIKit/UIKit.h>
@interface Array_Speed_TestViewController : UIViewController {
int numberOfItems; // number of items in array
float *cArray; // normal c array
NSMutableArray *nsArray; // ns array
double machTimerMillisMult; // multiplier to convert mach_absolute_time() to milliseconds
IBOutlet UISlider *sliderCount;
IBOutlet UILabel *labelCount;
IBOutlet UILabel *labelResults;
}
-(IBAction) doNSArray:(id)sender;
-(IBAction) doCArray:(id)sender;
-(IBAction) sliderChanged:(id)sender;
@end
実装:
// Array_Speed_TestViewController.m
//アレイ速度テスト
// 2009年5月2日にMehmet Aktenによって作成されました。
// Copyright MSA Visuals Ltd. 2009. All rights reserved。
#import "Array_Speed_TestViewController.h"
#include <mach/mach.h>
#include <mach/mach_time.h>
@implementation Array_Speed_TestViewController
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
NSLog(@"viewDidLoad");
[super viewDidLoad];
cArray = NULL;
nsArray = NULL;
// read initial slider value setup accordingly
[self sliderChanged:sliderCount];
// get mach timer unit size and calculater millisecond factor
mach_timebase_info_data_t info;
mach_timebase_info(&info);
machTimerMillisMult = (double)info.numer / ((double)info.denom * 1000000.0);
NSLog(@"machTimerMillisMult = %f", machTimerMillisMult);
}
// pass in results of mach_absolute_time()
// this converts to milliseconds and outputs to the label
-(void)displayResult:(uint64_t)duration {
double millis = duration * machTimerMillisMult;
NSLog(@"displayResult: %f milliseconds", millis);
NSString *str = [[NSString alloc] initWithFormat:@"%f milliseconds", millis];
[labelResults setText:str];
[str release];
}
// process using NSArray
-(IBAction) doNSArray:(id)sender {
NSLog(@"doNSArray: %@", sender);
uint64_t startTime = mach_absolute_time();
float total = 0;
for(int i=0; i<numberOfItems; i++) {
total += [[nsArray objectAtIndex:i] floatValue];
}
[self displayResult:mach_absolute_time() - startTime];
}
// process using C Array
-(IBAction) doCArray:(id)sender {
NSLog(@"doCArray: %@", sender);
uint64_t start = mach_absolute_time();
float total = 0;
for(int i=0; i<numberOfItems; i++) {
total += cArray[i];
}
[self displayResult:mach_absolute_time() - start];
}
// allocate NSArray and C Array
-(void) allocateArrays {
NSLog(@"allocateArrays");
// allocate c array
if(cArray) delete cArray;
cArray = new float[numberOfItems];
// allocate NSArray
[nsArray release];
nsArray = [[NSMutableArray alloc] initWithCapacity:numberOfItems];
// fill with random values
for(int i=0; i<numberOfItems; i++) {
// add number to c array
cArray[i] = random() * 1.0f/(RAND_MAX+1);
// add number to NSArray
NSNumber *number = [[NSNumber alloc] initWithFloat:cArray[i]];
[nsArray addObject:number];
[number release];
}
}
// callback for when slider is changed
-(IBAction) sliderChanged:(id)sender {
numberOfItems = sliderCount.value;
NSLog(@"sliderChanged: %@, %i", sender, numberOfItems);
NSString *str = [[NSString alloc] initWithFormat:@"%i items", numberOfItems];
[labelCount setText:str];
[str release];
[self allocateArrays];
}
//cleanup
- (void)dealloc {
[nsArray release];
if(cArray) delete cArray;
[super dealloc];
}
@end
差出人:memo.tv
////////////////////
ブロックの導入以来利用可能で、これはブロックで配列を反復することを可能にします。その構文は高速列挙ほど良くありませんが、非常に興味深い機能が1つあります。それは同時列挙です。列挙の順序が重要ではなく、ロックせずにジョブを並行して実行できる場合、これによりマルチコアシステムでかなりのスピードアップが得られます。詳細については、同時列挙セクションをご覧ください。
[myArray enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
[self doSomethingWith:object];
}];
[myArray enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[self doSomethingWith:object];
}];
//////////// NSFastEnumerator
高速列挙の背後にある考え方は、高速C配列アクセスを使用して反復を最適化することです。従来のNSEnumeratorよりも高速であることが想定されているだけでなく、Objective-C 2.0は非常に簡潔な構文も提供します。
id object;
for (object in myArray) {
[self doSomethingWith:object];
}
/////////////////
NSEnumerator
これは、外部反復の形式です。[myArray objectEnumerator]はオブジェクトを返します。このオブジェクトには、nilが返されるまでループで呼び出すことができるnextObjectメソッドがあります。
NSEnumerator *enumerator = [myArray objectEnumerator];
id object;
while (object = [enumerator nextObject]) {
[self doSomethingWith:object];
}
/////////////////
objectAtIndex:列挙
整数を増やすforループを使用し、[myArray objectAtIndex:index]を使用してオブジェクトをクエリすることが、列挙の最も基本的な形式です。
NSUInteger count = [myArray count];
for (NSUInteger index = 0; index < count ; index++) {
[self doSomethingWith:[myArray objectAtIndex:index]];
}
////////////// From:darkdust.net
3つの方法は次のとおりです。
//NSArray
NSArray *arrData = @[@1,@2,@3,@4];
// 1.Classical
for (int i=0; i< [arrData count]; i++){
NSLog(@"[%d]:%@",i,arrData[i]);
}
// 2.Fast iteration
for (id element in arrData){
NSLog(@"%@",element);
}
// 3.Blocks
[arrData enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(@"[%lu]:%@",idx,obj);
// Set stop to YES in case you want to break the iteration
}];
にeach
メソッドを追加してくださいNSArray category
、あなたはそれをたくさん必要とするでしょう
ObjectiveSugarから取得したコード
- (void)each:(void (^)(id object))block {
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
block(obj);
}];
}
これを行う :-
for (id object in array)
{
// statement
}