ฉันจะวนซ้ำ NSArray อย่างไร


คำตอบ:


667

รหัสที่ต้องการโดยทั่วไปสำหรับ 10.5 + / iOS

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

โครงสร้างนี้ใช้เพื่อระบุวัตถุในคอลเลกชันซึ่งสอดคล้องกับNSFastEnumerationโปรโตคอล วิธีการนี้มีความได้เปรียบด้านความเร็วเพราะมันเก็บพอยน์เตอร์ไปยังวัตถุหลาย ๆ อัน (ที่ได้จากการเรียกใช้เมธอดเดียว) ในบัฟเฟอร์และวนซ้ำผ่านพวกมันโดยเลื่อนผ่านบัฟเฟอร์โดยใช้เลขคณิตของตัวชี้ นี้เป็นมากได้เร็วขึ้นกว่าการเรียก-objectAtIndex:แต่ละครั้งที่ผ่านห่วง

นอกจากนี้ยังเป็นที่น่าสังเกตว่าในขณะที่คุณได้ในทางเทคนิคสามารถใช้สำหรับในวงไปยังขั้นตอนผ่านNSEnumeratorผมได้พบว่าเป็นโมฆะเกือบทั้งหมดของข้อได้เปรียบที่ความเร็วของการแจงนับอย่างรวดเร็วนี้ เหตุผลคือการNSEnumeratorใช้งานเริ่มต้นของ-countByEnumeratingWithState:objects:count:สถานที่เพียงหนึ่งวัตถุในบัฟเฟอร์ในการโทรแต่ละครั้ง

ฉันรายงานสิ่งนี้ใน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.4 หรือก่อนหน้า

สำนวนมาตรฐานสำหรับ pre-10.5 คือการใช้NSEnumeratorห่วงและในขณะที่เช่น:

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

ฉันแนะนำให้ทำให้มันง่าย การผูกตัวเองกับประเภทอาเรย์นั้นไม่ยืดหยุ่นและการเพิ่มความเร็วในการใช้งานโดย-objectAtIndex:ไม่ได้ตั้งใจนั้นไม่สำคัญต่อการปรับปรุงด้วยการแจงนับอย่างรวดเร็วที่ 10.5+ ต่อไป (การแจงนับอย่างรวดเร็วใช้ตัวชี้เลขคณิตจริงในโครงสร้างข้อมูลพื้นฐานและลบค่าใช้จ่ายส่วนใหญ่ของการเรียกเมธอด) การเพิ่มประสิทธิภาพก่อนกำหนดไม่ใช่ความคิดที่ดี

เมื่อใช้-objectEnumeratorงานคุณจะเปลี่ยนเป็นคอลเล็กชั่นนับจำนวนอื่นได้อย่างง่ายดาย (เช่นNSSetกุญแจในNSDictionaryและอื่น ๆ ) หรือแม้กระทั่งเปลี่ยนไปใช้-reverseObjectEnumeratorเพื่อระบุอาเรย์ด้านหลังทั้งหมดโดยไม่มีการเปลี่ยนแปลงรหัสอื่น ๆ หากรหัสการวนซ้ำอยู่ในวิธีการคุณสามารถส่งผ่านรหัสใด ๆ ได้NSEnumeratorและรหัสนั้นไม่จำเป็นต้องสนใจว่าจะวนซ้ำก็ตาม นอกจากนี้NSEnumerator(อย่างน้อยที่มีให้โดยรหัส Apple) จะยังคงมีการเก็บสะสมตราบใดที่มีวัตถุมากขึ้นดังนั้นคุณไม่ต้องกังวลเกี่ยวกับระยะเวลาที่วัตถุ autoreleased จะมีอยู่

บางทีสิ่งที่ใหญ่ที่สุดที่NSEnumerator(หรือการแจงนับอย่างรวดเร็ว) ปกป้องคุณจากการที่มีคอลเลกชันที่ไม่แน่นอน (อาเรย์หรืออย่างอื่น) การเปลี่ยนแปลงภายใต้คุณโดยที่คุณไม่รู้ในขณะที่คุณแจกแจง หากคุณเข้าถึงวัตถุตามดัชนีคุณสามารถพบข้อยกเว้นแปลก ๆ หรือข้อผิดพลาดแบบ off-one (บ่อยครั้งหลังจากปัญหาเกิดขึ้น) ซึ่งอาจเป็นข้อผิดพลาดที่น่ากลัว การแจงนับโดยใช้หนึ่งในสำนวนมาตรฐานมีพฤติกรรม "ล้มเหลวอย่างรวดเร็ว" ดังนั้นปัญหา (เกิดจากรหัสที่ไม่ถูกต้อง) จะปรากฏขึ้นทันทีเมื่อคุณพยายามเข้าถึงวัตถุถัดไปหลังจากการกลายพันธุ์เกิดขึ้น เมื่อโปรแกรมมีความซับซ้อนมากขึ้นและมีหลายเธรดหรือแม้กระทั่งขึ้นอยู่กับสิ่งที่รหัสของบุคคลที่สามอาจแก้ไขรหัสการแจกแจงที่เปราะบางกลายเป็นปัญหามากขึ้น การห่อหุ้มและสิ่งที่เป็นนามธรรม FTW! :-)



28
หมายเหตุ: คอมไพเลอร์ส่วนใหญ่จะแจ้งเตือนเกี่ยวกับ "while (object = [e nextObject])" ในกรณีนี้คุณหมายถึงใช้ = แทน == ในการระงับคำเตือนคุณสามารถเพิ่มวงเล็บพิเศษ: "while ((object = [e nextObject]))"
Adam Rosenfield

อีกสิ่งที่ต้องจำด้วย NSEnumerator คือมันจะใช้หน่วยความจำเพิ่มเติม (ทำสำเนาของอาร์เรย์) กว่าวิธีการ objectWithIndex
30902 deaderikh

@QuinnTaylor การใช้for (id object in array)เป็นวิธีการกำหนดวัตถุดัชนีปัจจุบันในอาร์เรย์หรือไม่หรือเคาน์เตอร์แยกต่างหากจะต้องรวม?
Coderama

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 นอกลูป วิธีนี้ใช้ได้เช่นกันและกำหนดขอบเขตตัวแปรได้อย่างดีดังนั้นคุณสามารถนำมาใช้ซ้ำได้ในภายหลังหากต้องการ: สำหรับ (int i = 0; i <[นับ myArray]; i ++) ... แต่โปรดทราบว่าการเรียก -count แต่ละครั้ง ผ่านแถวสามารถยกเลิกการออกประโยชน์ของการใช้ -objectAtIndex นี้:
ควินน์เทย์เลอร์

1
และในความเป็นจริงหากคุณระบุคอลเลกชันที่ไม่แน่นอนมันเป็นเทคนิคที่ถูกต้องมากขึ้นในการตรวจสอบการนับทุกครั้งที่ผ่านลูป ฉันชี้แจงคำตอบของฉันเพื่ออธิบายว่าการใช้ NSEnumerator หรือ NSFastEnumeration สามารถป้องกันจากการกลายพันธุ์พร้อมกันของอาร์เรย์
Quinn Taylor

โครงสร้างสำหรับ (int i = 0; ... ) เป็นภาษา C (เชื่อว่า C99) ซึ่งฉันเองใช้ แต่ฉันไม่แน่ใจว่าเป็นค่าเริ่มต้นของ XCode
diederikh

C99 เป็นค่าเริ่มต้นผ่าน Xcode 3.1.x - ในบางจุดในอนาคตค่าเริ่มต้นจะเปลี่ยนเป็น GNU99 ซึ่ง (เหนือสิ่งอื่นใด) สนับสนุนสหภาพและโครงสร้างที่ไม่ระบุชื่อ ที่ควรจะดี ...
ควินน์เทย์เลอร์

2
การใช้for (NSUInteger i = 0, count = [myArray count]; i < count; i++)อาจเป็นวิธีที่มีประสิทธิภาพและรัดกุมที่สุดสำหรับวิธีการนี้
Quinn Taylor

17

ผลลัพธ์ของการทดสอบและซอร์สโค้ดอยู่ด้านล่าง (คุณสามารถกำหนดจำนวนการวนซ้ำในแอป) เวลาเป็นมิลลิวินาทีและแต่ละรายการเป็นผลลัพธ์โดยเฉลี่ยของการทดสอบ 5-10 ครั้ง ฉันพบว่าโดยทั่วไปแล้วมันมีความถูกต้องถึงตัวเลขสองหลักและหลังจากนั้นมันก็จะแตกต่างกันไปในการวิ่งแต่ละครั้ง ที่ให้ขอบของข้อผิดพลาดน้อยกว่า 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 ฯลฯ ) ให้อินเทอร์เฟซที่ดีมากสำหรับการจัดการข้อมูลโดยไม่ต้องกังวลเกี่ยวกับระบบราชการของการจัดการหน่วยความจำการจัดสรรใหม่ ฯลฯ แน่นอนว่า . ฉันคิดว่ามันค่อนข้างชัดเจนว่าการใช้ NSArray ของ NSNumbers จะช้ากว่า C Array of floats สำหรับการทำซ้ำอย่างง่ายดังนั้นฉันจึงตัดสินใจทำการทดสอบและผลลัพธ์ก็น่าตกใจมาก! ฉันไม่ได้คาดหวังว่ามันจะแย่ขนาดนี้ หมายเหตุ: การทดสอบเหล่านี้ดำเนินการบน iPhone 3G ซึ่งเป็นแพลตฟอร์มเป้าหมายที่ฉันสนใจ

ในการทดสอบนี้ฉันทำการเปรียบเทียบประสิทธิภาพการเข้าถึงแบบสุ่มที่ง่ายมากระหว่าง C float * และ NSArray ของ NSNumbers

ฉันสร้างลูปง่าย ๆ เพื่อสรุปเนื้อหาของแต่ละอาร์เรย์และใช้เวลาในการใช้ Mach_absolute_time () NSMutableArray ใช้เวลานานกว่า 400 เท่าโดยเฉลี่ย !! (ไม่ใช่ 400 เปอร์เซ็นต์นานกว่า 400 เท่า! นั่นนานกว่า 40,000%!)

หัวข้อ:

// Array_Speed_TestViewController.h

// การทดสอบความเร็วอาร์เรย์

// สร้างโดย Mehmet Akten เมื่อวันที่ 05/02/2552

// ลิขสิทธิ์ MSA Visuals Ltd. 2009 สงวนลิขสิทธิ์

#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

// การทดสอบความเร็วอาร์เรย์

// สร้างโดย Mehmet Akten เมื่อวันที่ 05/02/2552

// ลิขสิทธิ์ MSA Visuals Ltd. 2009 สงวนลิขสิทธิ์

    #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

////////////////////

มีให้ตั้งแต่เปิดตัวบล็อกสิ่งนี้จะอนุญาตให้วนซ้ำอาร์เรย์กับบล็อก ไวยากรณ์ของมันไม่ดีเท่าการแจงนับอย่างรวดเร็ว แต่มีคุณลักษณะที่น่าสนใจอย่างหนึ่งนั่นคือการแจงนับพร้อมกัน ถ้าลำดับการแจงนับไม่สำคัญและสามารถทำงานแบบขนานโดยไม่ล็อคสิ่งนี้สามารถให้การเร่งความเร็วที่รวดเร็วในระบบมัลติคอร์ เพิ่มเติมเกี่ยวกับสิ่งนั้นในส่วนการแจงนับพร้อมกัน

[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] ส่งคืนวัตถุ วัตถุนี้มีวิธีถัดไปอ็อบเจ็กที่เราสามารถเรียกใช้ในลูปจนกว่ามันจะส่งกลับศูนย์

NSEnumerator *enumerator = [myArray objectEnumerator];
id object;
while (object = [enumerator nextObject]) {
    [self doSomethingWith:object];
}

/////////////////

objectAtIndex: การแจงนับ

การใช้ห่วงสำหรับเพิ่มจำนวนเต็มและค้นหาวัตถุโดยใช้ [myArray objectAtIndex: index] เป็นรูปแบบพื้นฐานที่สุดของการแจงนับ

NSUInteger count = [myArray count];
for (NSUInteger index = 0; index < count ; index++) {
    [self doSomethingWith:[myArray objectAtIndex:index]];
}

////////////// จาก: darkdust.net


12

สามวิธีคือ:

        //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. เป็นวิธีที่เร็วที่สุดในการดำเนินการและ 3 ด้วยการเติมข้อความอัตโนมัติลืมเกี่ยวกับการเขียนซองจดหมายซ้ำ

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

สำหรับสวิฟท์

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.