ควรใช้ enumerateObjectsUsingBlock กับเมื่อใด


150

นอกจากความแตกต่างที่ชัดเจน:

  • ใช้enumerateObjectsUsingBlockเมื่อคุณต้องการทั้งดัชนีและวัตถุ
  • อย่าใช้enumerateObjectsUsingBlockเมื่อคุณต้องการแก้ไขตัวแปรท้องถิ่น (ฉันผิดเกี่ยวกับเรื่องนี้ดูคำตอบของ bbum)

มีการenumerateObjectsUsingBlockพิจารณาโดยทั่วไปดีขึ้นหรือแย่ลงเมื่อfor (id obj in myArray)ยังจะทำงานหรือไม่ อะไรคือข้อดี / ข้อเสีย (เช่นมันเป็นนักแสดงมากหรือน้อย)?


1
ฉันชอบที่จะใช้มันถ้าฉันต้องการดัชนีปัจจุบัน
Besi

ดูเพิ่มเติมที่: stackoverflow.com/questions/8509662/ …
Simon Whitaker

คำตอบ:


350

ในที่สุดใช้รูปแบบใดก็ตามที่คุณต้องการใช้และเป็นธรรมชาติมากขึ้นในบริบท

ในขณะที่for(... in ...)ค่อนข้างสะดวกและสั้น ๆ ทาง syntactically enumerateObjectsUsingBlock:มีจำนวนของคุณสมบัติที่อาจหรือไม่อาจพิสูจน์ที่น่าสนใจ:

  • enumerateObjectsUsingBlock:จะเร็วหรือเร็วกว่าการแจงนับรวดเร็ว ( for(... in ...)ใช้การNSFastEnumerationสนับสนุนเพื่อดำเนินการแจงนับ) การแจงนับอย่างรวดเร็วต้องใช้การแปลจากการเป็นตัวแทนภายในเป็นการแสดงเพื่อการแจงนับอย่างรวดเร็ว มีค่าใช้จ่ายในนั้น การระบุตามบล็อกอนุญาตให้คลาสคอลเล็กชันระบุเนื้อหาได้เร็วที่สุดเท่าที่การส่งผ่านที่รวดเร็วที่สุดของรูปแบบการจัดเก็บแบบเนทีฟ อาจไม่เกี่ยวข้องกับอาร์เรย์ แต่อาจแตกต่างกันมากสำหรับพจนานุกรม

  • "อย่าใช้ enumerateObjectsUsingBlock เมื่อคุณต้องการแก้ไขตัวแปรโลคอล" - ไม่จริง คุณสามารถประกาศคนในท้องถิ่นของคุณเป็น__blockและพวกเขาจะเขียนได้ในบล็อก

  • enumerateObjectsWithOptions:usingBlock: รองรับการแจงนับพร้อมกันหรือย้อนกลับ

  • ด้วยพจนานุกรมการแจงนับจากบล็อกเป็นวิธีเดียวในการดึงข้อมูลคีย์และค่าพร้อมกัน

ส่วนตัวผมใช้enumerateObjectsUsingBlock:บ่อยกว่าfor (... in ...)แต่ - อีกครั้ง - ทางเลือกส่วนตัว


16
ว้าวให้ข้อมูลมาก ฉันหวังว่าฉันจะยอมรับคำตอบทั้งสองนี้ได้ แต่ฉันจะไปกับชัคเพราะมันดังก้องมากขึ้นกับฉัน นอกจากนี้ฉันพบบล็อกของคุณ ( friday.com/bbum/2009/08/29/blocks-tips-tricks ) ในขณะที่ค้นหา __block และเรียนรู้เพิ่มเติม ขอบคุณ.
Paul Wheeler

8
สำหรับการบันทึกการแจงนับบล็อกนั้นไม่ใช่ "เร็วหรือเร็วกว่า" mikeabdullah.net/slow-block-based-dictionary-enumeration.html เสมอ
Mike Abdullah

2
@VanDuTran Blocks จะถูกดำเนินการเฉพาะในเธรดแยกต่างหากหากคุณบอกให้บล็อกเหล่านั้นถูกดำเนินการบนเธรดแยกต่างหาก เว้นแต่คุณจะใช้ตัวเลือกการเกิดขึ้นพร้อมกันของการแจงนับมันจะถูกดำเนินการในหัวข้อเดียวกับการโทร
bbum

2
Nick Lockwood ทำบทความที่ดีมากเกี่ยวกับเรื่องนี้และดูเหมือนว่าenumerateObjectsUsingBlockยังช้ากว่าการแจงนับอย่างรวดเร็วสำหรับอาร์เรย์และเซต ฉันสงสัยว่าทำไม? iosdevelopertips.com/objective-c/…
Bob Spryn

2
แม้ว่ามันจะเป็นรายละเอียดของการใช้งาน แต่ก็ควรจะกล่าวถึงในคำตอบนี้ท่ามกลางความแตกต่างระหว่างสองวิธีที่enumerateObjectsUsingBlockล้อมรอบการร้องขอแต่ละบล็อกด้วยการปิดกั้นอัตโนมัติ (ตั้งแต่ OS X 10.10 เป็นอย่างน้อย) สิ่งนี้อธิบายความแตกต่างด้านประสิทธิภาพเมื่อเปรียบเทียบกับfor inสิ่งที่ไม่ทำ
Pol

83

สำหรับการแจงนับอย่างง่ายเพียงใช้การแจงนับอย่างรวดเร็ว (เช่นการfor…in…วนซ้ำ) เป็นตัวเลือกที่ใช้สำนวนมากกว่า วิธีการบล็อกอาจเร็วกว่าเล็กน้อย แต่ไม่สำคัญมากนักในกรณีส่วนใหญ่โปรแกรมบางตัวมีการเชื่อมโยงกับ CPU และถึงแม้จะเป็นเรื่องยากที่ลูปเองมากกว่าการคำนวณภายในจะเป็นคอขวด

การวนรอบแบบง่ายยังอ่านได้ชัดเจนยิ่งขึ้น นี่คือตัวอย่างของสองรุ่น:

for (id x in y){
}

[y enumerateObjectsUsingBlock:^(id x, NSUInteger index, BOOL *stop){
}];

แม้ว่าคุณจะเพิ่มตัวแปรเพื่อติดตามดัชนีลูปแบบง่ายจะอ่านง่ายกว่า

ดังนั้นเมื่อคุณควรใช้enumerateObjectsUsingBlock:? เมื่อคุณจัดเก็บบล็อกเพื่อดำเนินการในภายหลังหรือในหลายสถานที่ มันดีสำหรับเมื่อคุณใช้บล็อกเป็นฟังก์ชันระดับเฟิร์สคลาสแทนที่จะใช้การแทนที่แบบโอเวอร์เวย์สำหรับตัวลูป


5
enumerateObjectsUsingBlock:จะเป็นความเร็วเดียวกันหรือเร็วกว่าการแจงนับที่รวดเร็วในทุกกรณี for(... in ...)ใช้การแจงนับอย่างรวดเร็วซึ่งจำเป็นต้องมีการรวบรวมเพื่อให้เป็นตัวแทนชั่วคราวของโครงสร้างข้อมูลภายใน ตามที่คุณทราบน่าจะไม่เกี่ยวข้อง
bbum

4
+1When you're storing a block to execute later or in multiple places. It's good for when you're actually using a block as a first-class function rather than an overwrought replacement for a loop body.
Steve

7
@bum การทดสอบของฉันเองแสดงให้เห็นว่าenumerateObjects...จริง ๆ แล้วช้าลงอย่างมากจากการแจงนับอย่างรวดเร็วด้วยการวนซ้ำ ฉันวิ่งทดสอบนี้หลายพันครั้ง; [(NSOperation *)obj cancel];ร่างกายของบล็อกและห่วงเป็นบรรทัดเดียวเดียวกันของรหัส: เฉลี่ย: รวดเร็วห่วง enum - -[JHStatusBar dequeueStatusMessage:] [Line: 147] Fast enumeration time (for..in..loop): 0.000009และบล็อก -[JHStatusBar dequeueStatusMessage:] [Line: 147] Enumeration time using block: 0.000043- แปลกที่ความแตกต่างของเวลามีขนาดใหญ่และสม่ำเสมอ แต่เห็นได้ชัดว่านี่เป็นกรณีทดสอบที่เฉพาะเจาะจงมาก
chown

42

แม้ว่าคำถามนี้จะเก่า แต่สิ่งต่าง ๆ ไม่ได้เปลี่ยนไป แต่คำตอบที่ยอมรับนั้นไม่ถูกต้อง

enumerateObjectsUsingBlockAPI ไม่ได้หมายถึงแทนที่for-inแต่สำหรับกรณีการใช้งานที่แตกต่างกันโดยสิ้นเชิง:

  • อนุญาตให้แอปพลิเคชันของตรรกะโดยพลการไม่ใช่ท้องถิ่น เช่นคุณไม่จำเป็นต้องรู้ว่าบล็อกทำอะไรเพื่อใช้กับอาร์เรย์
  • การแจงนับพร้อมกันสำหรับคอลเลกชันขนาดใหญ่หรือการคำนวณหนัก (ใช้withOptions:พารามิเตอร์)

ได้อย่างรวดเร็วด้วยการแจงนับfor-inยังคงเป็นสำนวนวิธีการแจงคอลเลกชัน

การแจงนับอย่างรวดเร็วได้รับประโยชน์จากความกะทัดรัดของโค้ดความสามารถในการอ่านและการเพิ่มประสิทธิภาพเพิ่มเติมที่ทำให้รวดเร็วอย่างไม่เป็นธรรมชาติ เร็วกว่า C สำหรับวงเก่า!

การทดสอบอย่างรวดเร็วสรุปว่าในปี 2557 บน iOS 7 นั้นenumerateObjectsUsingBlockจะช้ากว่าการใช้งาน 700% อย่างต่อเนื่อง

ประสิทธิภาพการทำงานเป็นเรื่องที่น่ากังวลจริงหรือไม่?

ไม่แน่นอนมีข้อยกเว้นที่หายาก

จุดคือการแสดงให้เห็นว่ามีผลประโยชน์เล็ก ๆ น้อย ๆ ที่จะใช้enumerateObjectsUsingBlock:ในช่วงfor-inไม่มีเหตุผลที่ดีจริงๆ มันไม่ได้ทำให้โค้ดอ่านได้ง่ายขึ้น ... หรือเร็วกว่า ... หรือปลอดภัยต่อเธรด (ความเข้าใจผิดอื่นทั่วไป)

ตัวเลือกจะลงมาตามความชอบส่วนตัว สำหรับฉันตัวเลือกที่ใช้สำนวนและสามารถอ่านได้ชนะ for-inในกรณีนี้ที่มีการแจงนับจานโดยใช้

เกณฑ์มาตรฐาน:

NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < 100; i++) {
    arr[i] = [NSString stringWithFormat:@"%d", i];
}
int i;
__block NSUInteger length;

i = 1000 * 1000;
uint64_t a1 = mach_absolute_time();
while (--i > 0) {
    for (NSString *s in arr) {
        length = s.length;
    }
}
NSLog(@"For-in %llu", mach_absolute_time()-a1);

i = 1000 * 1000;
uint64_t b1 = mach_absolute_time();
while (--i > 0) {
    [arr enumerateObjectsUsingBlock:^(NSString *s, NSUInteger idx, BOOL *stop) {
        length = s.length;
    }];
}
NSLog(@"Enum %llu", mach_absolute_time()-b1);

ผล:

2014-06-11 14:37:47.717 Test[57483:60b] For-in 1087754062
2014-06-11 14:37:55.492 Test[57483:60b] Enum   7775447746

4
ฉันสามารถยืนยันได้ว่าการรันการทดสอบเดียวกันบน MacBook Pro Retina 2014 enumerateObjectsUsingBlockนั้นช้ากว่า 5X มาก เห็นได้ชัดว่ามีสาเหตุมาจากสระว่ายน้ำอัตโนมัติที่ล้อมรอบการร้องขอบล็อกแต่ละรายการซึ่งไม่ได้เกิดขึ้นสำหรับfor inกรณี
Pol

2
ฉันยืนยันenumerateObjectsUsingBlock:ว่ายังช้ากว่า 4X ใน iPhone จริง 6 iOS9 โดยใช้ Xcode 7.x เพื่อสร้าง
Cœur

1
ขอบคุณสำหรับการยืนยัน! ฉันหวังว่าคำตอบนี้จะไม่ถูกฝังอยู่ ... บางคนก็ชอบenumerateไวยากรณ์เพราะมันให้ความรู้สึกเหมือน FP และไม่ต้องการฟังการวิเคราะห์ประสิทธิภาพ
Adam Kaplan

1
โดยวิธีการที่มันไม่ได้เป็นเพียงเพราะสระว่ายน้ำอัตโนมัติ มีจำนวนมากขึ้นผลักดัน & popping ด้วยenumerate:
Adam Kaplan

24

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

ตัวเลือกคือ:

1) makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2) การแจงนับที่รวดเร็วและการส่งข้อความปกติ

for (id item in arr)
{
    [item _stubMethod];
}

3) enObjectObjectsUsingBlock และส่งข้อความปกติ

[arr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) 
 {
     [obj _stubMethod];
 }];

ปรากฎว่า makeObjectsPerformSelector ช้าที่สุด การแจงนับรวดเร็วใช้เวลาสองเท่า และแจงนับวัตถุการใช้บล็อกนั้นเร็วที่สุดมันเร็วกว่าการทำซ้ำอย่างรวดเร็วประมาณ 15-20%

ดังนั้นหากคุณกังวลเกี่ยวกับประสิทธิภาพที่ดีที่สุดให้ใช้ enumerateObjectsUsingBlock แต่โปรดจำไว้ว่าในบางกรณีเวลาที่ใช้ในการระบุคอลเลกชันจะถูกแคระเมื่อถึงเวลาที่จะเรียกใช้โค้ดใด ๆ ที่คุณต้องการให้แต่ละอ็อบเจกต์ดำเนินการ


คุณสามารถชี้ให้เห็นการทดสอบที่เฉพาะเจาะจงของคุณ? คุณดูเหมือนจะได้รับคำตอบที่ผิด
Cœur

3

มันค่อนข้างมีประโยชน์ที่จะใช้ enumerateObjectsUsingBlock เป็นวงนอกเมื่อคุณต้องการแบ่งลูปซ้อนกัน

เช่น

[array1 enumerateObjectsUsingBlock:^(id obj1, NSUInteger idx, BOOL * _Nonnull stop) {
  for(id obj2 in array2) {
    for(id obj3 in array3) {
      if(condition) {
        // break ALL the loops!
        *stop = YES;
        return;
      }
    }
  }
}];

ทางเลือกกำลังใช้คำสั่ง goto


1
หรือคุณอาจกลับมาจากวิธีการเดียวกับที่คุณทำที่นี่ :-D
Adam Kaplan

1

ขอบคุณ @bbum และ @Chuck สำหรับการเริ่มต้นการเปรียบเทียบประสิทธิภาพที่ครอบคลุม ดีใจที่ได้รู้ว่ามันเป็นเรื่องเล็กน้อย ฉันดูเหมือนจะไปกับ:

  • for (... in ...)- เป็น goto เริ่มต้นของฉัน ง่ายขึ้นสำหรับฉันมากกว่าประวัติการเขียนโปรแกรมที่นี่มากกว่าการตั้งค่าจริง - การใช้ซ้ำภาษาไขว้พิมพ์น้อยลงสำหรับโครงสร้างข้อมูลส่วนใหญ่เนื่องจาก IDE อัตโนมัติสมบูรณ์: P

  • enumerateObject...- เมื่อจำเป็นต้องเข้าถึงวัตถุและดัชนี และเมื่อเข้าถึงโครงสร้างที่ไม่ใช่อาร์เรย์หรือพจนานุกรม (การตั้งค่าส่วนตัว)

  • for (int i=idx; i<count; i++) - สำหรับอาร์เรย์เมื่อฉันต้องเริ่มต้นกับดัชนีที่ไม่เป็นศูนย์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.