NSArrayをどのように反復するのですか?


回答:


667

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:して渡すこともできます。NSEnumerationConcurrentNSEnumerationReverse


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つずれるエラーが発生し(多くの場合、問題が発生してから長い間)、デバッグが非常に困難になる可能性があります。標準のイディオムのいずれかを使用した列挙は「フェイルファースト」の動作をするため、ミューテーションが発生した後で次のオブジェクトにアクセスしようとすると、問題(不正なコードが原因)がすぐに現れます。プログラムがより複雑になり、マルチスレッド化されるか、サードパーティのコードが変更する可能性のあるものに依存するようになると、脆弱な列挙型コードはますます問題になります。カプセル化と抽象化FTW!:-)



28
注:ほとんどのコンパイラは、「while(object = [e nextObject])」に関する警告を表示します。この場合、実際には==の代わりに=を使用することになります。警告を抑制するには、括弧を追加します: "while((object = [e nextObject]))"。
Adam Rosenfield、

NSEnumeratorで覚えておくべきもう1つの点は、objectWithIndexアプローチよりも多くのメモリを使用する(配列のコピーを作成する)ことです。
diederikh 2009年

@QuinnTaylorを使用for (id object in array)して、配列内のオブジェクトの現在のインデックスも特定する方法はありますか、それとも別のカウンタを含める必要がありますか?
Coderama 2012年

1
別のカウンターが必要ですが、利用できる場合は、ブロックベースの列挙(インデックスを含む)を検討することをお勧めします。
Quinn Taylor

私は変なんだけど、私が使用しfor、次のようにループを:for(;;) { id object = [ e nextObject ] ; if ( !e ) { break ; } ... your loop operation ... }
nielsbot

125

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
}

2
最初の例では、ループの外側でintを宣言する必要もありません。これも同様に機能し、必要に応じて後で再利用できるように変数のスコープを適切に設定します:for(int i = 0; i <[myArray count]; i ++)...また、毎回-countを呼び出すことに注意してくださいアレイを介し-objectAtIndexを使用することの利点を相殺することができる:
クインテイラー

1
そして実際には、変更可能なコレクションを列挙している場合は、ループのたびにカウントをチェックする方が技術的にはより正確です。NSEnumeratorまたはNSFastEnumerationを使用すると、配列の同時変更から保護できることを説明するために、私の回答を明確にしました。
クインテイラー

for(int i = 0; ...)構文はC言語の方言(C99はそれを信じます)です。これは私自身も使用しますが、XCodeのデフォルトかどうかはわかりませんでした。
diederikh、2009年

C99は、Xcode 3.1.xまでのデフォルトです。将来的には、デフォルトがGNU99に変更され、(とりわけ)匿名の共用体および構造体がサポートされます。それは素敵な...でなければなりません
クインテイラー

2
for (NSUInteger i = 0, count = [myArray count]; i < count; i++)このアプローチでは、おそらく使用するのが最も効率的で簡潔です。
Quinn Taylor、

17

テストとソースコードの結果は以下のとおりです(アプリで反復回数を設定できます)。時間はミリ秒単位で、各エントリはテストを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


12

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
    }];
  1. 実行の最速の方法です。オートコンプリートを使用すると、反復エンベロープの記述を忘れます。

7

eachメソッドを追加してくださいNSArray category、あなたはそれをたくさん必要とするでしょう

ObjectiveSugarから取得したコード

- (void)each:(void (^)(id object))block {
    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        block(obj);
    }];
}

5

以下は、文字列の配列を宣言して反復する方法です。

NSArray *langs = @[@"es", @"en", @"pt", @"it", @"fr"];

for (int i = 0; i < [langs count]; i++) {
  NSString *lang = (NSString*) [langs objectAtIndex:i];
  NSLog(@"%@, ",lang);
}

0

Swiftの場合

let arrayNumbers = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

// 1
for (index, value) in arrayNumbers.enumerated() {
    print(index, value)
    //... do somthing with array value and index
}


//2
for value in arrayNumbers {
    print(value)
    //... do somthing with array value
}

-1

これを行う :-

for (id object in array) 
{
        // statement
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.