Kapan menggunakan enumerateObjectsUsingBlock vs. for

150

Selain perbedaan yang jelas:

  • Menggunakan enumerateObjectsUsingBlock saat Anda membutuhkan indeks dan objek
  • Jangan gunakan enumerateObjectsUsingBlockketika Anda perlu memodifikasi variabel lokal (saya salah tentang ini, lihat jawaban bbum)

Apakah enumerateObjectsUsingBlockumumnya dianggap lebih baik atau lebih buruk ketika for (id obj in myArray)juga akan bekerja? Apa kelebihan / kekurangannya (misalnya performanya kurang lebih)?

Paul Wheeler
sumber
1
Saya suka menggunakannya jika saya membutuhkan indeks saat ini.
Besi
Lihat juga: stackoverflow.com/questions/8509662/...
Simon Whitaker

Jawaban:

350

Pada akhirnya, gunakan pola apa pun yang ingin Anda gunakan dan datang lebih alami dalam konteks.

Meskipun for(... in ...)cukup nyaman dan singkat secara sintaksis, enumerateObjectsUsingBlock:memiliki sejumlah fitur yang mungkin atau mungkin tidak terbukti menarik:

  • enumerateObjectsUsingBlock:akan lebih cepat atau lebih cepat daripada pencacahan cepat ( for(... in ...)menggunakan NSFastEnumerationdukungan untuk mengimplementasikan pencacahan). Penghitungan cepat memerlukan terjemahan dari representasi internal ke representasi untuk penghitungan cepat. Ada overhead di dalamnya. Enumerasi berbasis blok memungkinkan kelas koleksi untuk menghitung konten secepat traversal tercepat dari format penyimpanan asli. Agak tidak relevan untuk array, tetapi bisa menjadi perbedaan besar untuk kamus.

  • "Jangan gunakan enumerateObjectsUsingBlock ketika Anda perlu memodifikasi variabel lokal" - tidak benar; Anda dapat mendeklarasikan penduduk lokal Anda sebagai __blockdan mereka akan dapat ditulis di blok.

  • enumerateObjectsWithOptions:usingBlock: mendukung enumerasi bersamaan atau terbalik.

  • dengan kamus, enumerasi berbasis blok adalah satu-satunya cara untuk mengambil kunci dan nilai secara bersamaan.

Secara pribadi, saya menggunakan enumerateObjectsUsingBlock:lebih sering daripadafor (... in ...) , tetapi - lagi - pilihan pribadi.

Bbum
sumber
16
Wow, sangat informatif. Saya berharap saya bisa menerima kedua jawaban ini, tetapi saya akan menggunakan jawaban Chuck karena itu sedikit lebih beresonansi dengan saya. Juga, saya menemukan blog Anda ( friday.com/bbum/2009/08/29/blocks-tips-tricks ) saat mencari __block dan belajar lebih banyak lagi. Terima kasih.
Paul Wheeler
8
Sebagai catatan, enumerasi berbasis blok tidak selalu "secepat atau lebih cepat" mikeabdullah.net/slow-block-based-dictionary-enumeration.html
Mike Abdullah
2
@VanDuTran Blok hanya dieksekusi pada utas terpisah jika Anda menyuruhnya dieksekusi pada utas terpisah. Kecuali jika Anda menggunakan opsi concurrency enumerasi, maka itu akan dieksekusi pada utas yang sama dengan panggilan yang dibuat
bbum
2
Nick Lockwood melakukan artikel yang sangat bagus tentang ini, dan tampaknya enumerateObjectsUsingBlockmasih jauh lebih lambat daripada penghitungan cepat untuk array dan set. Kenapa ya? iosdevelopertips.com/objective-c/…
Bob Spryn
2
Meskipun ini semacam detail implementasi, harus disebutkan dalam jawaban ini di antara perbedaan antara dua pendekatan yang enumerateObjectsUsingBlockmembungkus setiap doa blok dengan kumpulan autorelease (setidaknya pada OS X 10.10). Ini menjelaskan perbedaan kinerja dibandingkan for inyang tidak melakukan itu.
Pol
83

Untuk enumerasi sederhana, cukup menggunakan enumerasi cepat (yaitu for…in…loop) adalah opsi yang lebih idiomatis. Metode blok mungkin sedikit lebih cepat, tetapi itu tidak terlalu penting dalam banyak kasus - beberapa program terikat dengan CPU, dan meskipun itu jarang bahwa loop itu sendiri daripada perhitungan di dalamnya akan menjadi hambatan.

Simpul sederhana juga membaca lebih jelas. Inilah pelat dari kedua versi:

for (id x in y){
}

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

Bahkan jika Anda menambahkan variabel untuk melacak indeks, loop sederhana lebih mudah dibaca.

Jadi kapan sebaiknya Anda gunakan enumerateObjectsUsingBlock:? Saat Anda menyimpan blok untuk dieksekusi nanti atau di banyak tempat. Ini bagus untuk saat Anda benar-benar menggunakan blok sebagai fungsi kelas satu daripada pengganti yang terlalu padat untuk bodi loop.

Membuang
sumber
5
enumerateObjectsUsingBlock:akan memiliki kecepatan yang sama atau lebih cepat dari penghitungan cepat dalam semua kasus. for(... in ...)menggunakan penghitungan cepat yang membutuhkan pengumpulan untuk menyediakan beberapa representasi sementara dari struktur data internal. Seperti yang Anda perhatikan, kemungkinan tidak relevan.
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
@bbum Tes saya sendiri menunjukkan bahwa enumerateObjects...sebenarnya bisa lebih lambat daripada penghitungan cepat dengan satu lingkaran. Saya menjalankan tes ini beberapa ribu kali; tubuh blok dan loop adalah baris kode yang sama: [(NSOperation *)obj cancel];. Rata-rata: loop cepat enum - -[JHStatusBar dequeueStatusMessage:] [Line: 147] Fast enumeration time (for..in..loop): 0.000009dan untuk blok - -[JHStatusBar dequeueStatusMessage:] [Line: 147] Enumeration time using block: 0.000043. Aneh bahwa perbedaan waktu begitu besar dan konsisten tetapi, jelas, ini adalah kasus uji yang sangat spesifik.
chown
42

Meskipun pertanyaan ini sudah tua, banyak hal belum berubah, jawaban yang diterima salah.

The enumerateObjectsUsingBlockAPI tidak dimaksudkan untuk menggantikan for-in, tapi untuk kasus penggunaan yang sama sekali berbeda:

  • Ini memungkinkan penerapan logika non-lokal yang sewenang-wenang. yaitu Anda tidak perlu tahu apa yang dilakukan blok untuk menggunakannya pada array.
  • Pencacahan serentak untuk koleksi besar atau perhitungan berat (menggunakan withOptions:parameter)

Enumerasi cepat dengan for-inmasih idiomatik metode untuk menghitung koleksi.

Penghitungan Cepat mendapat manfaat dari singkatnya kode, keterbacaan, dan optimisasi tambahan yang membuatnya cepat secara tidak wajar. Lebih cepat dari pada C-loop lama!

Tes cepat menyimpulkan bahwa pada tahun 2014 di iOS 7, enumerateObjectsUsingBlock secara konsisten lebih lambat 700% dari pada untuk-in (berdasarkan iterasi 1mm dari array 100 item).

Apakah kinerja merupakan masalah praktis yang nyata di sini?

Jelas tidak, dengan pengecualian langka.

Intinya adalah untuk menunjukkan bahwa ada sedikit keuntungan untuk menggunakan enumerateObjectsUsingBlock:lebih for-intanpa alasan yang benar-benar baik. Itu tidak membuat kode lebih mudah dibaca ... atau lebih cepat ... atau aman utas. (Kesalahpahaman umum lainnya).

Pilihannya adalah preferensi pribadi. Bagi saya, opsi idiomatis dan mudah dibaca menang. Dalam hal ini, yaitu Penghitungan Cepat menggunakan for-in.

Benchmark:

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);

Hasil:

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
Adam Kaplan
sumber
4
Saya dapat mengonfirmasi bahwa menjalankan tes yang sama pada MacBook Pro Retina 2014, enumerateObjectsUsingBlocksebenarnya 5X lebih lambat. Itu tampaknya karena kolam autorelease yang membungkus setiap doa blok, yang tidak terjadi untuk for inkasus ini.
Pol
2
Saya konfirmasikan enumerateObjectsUsingBlock:masih 4X lebih lambat pada iPhone 6 iOS9 nyata, menggunakan Xcode 7.x untuk membangun.
Cœur
1
Terimakasih sudah mengkonfirmasi! Saya berharap jawaban ini tidak begitu terkubur ... beberapa orang hanya menyukai enumeratesintaksis karena rasanya seperti FP dan tidak ingin mendengarkan analisis kinerja.
Adam Kaplan
1
Omong-omong, itu bukan hanya karena kolam autorelease. Ada lebih banyak tumpukan mendorong & bermunculan denganenumerate:
Adam Kaplan
24

Untuk menjawab pertanyaan tentang kinerja, saya melakukan beberapa tes menggunakan proyek tes kinerja saya . Saya ingin tahu yang mana dari tiga opsi untuk mengirim pesan ke semua objek dalam array yang tercepat.

Pilihannya adalah:

1) makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2) penghitungan cepat & pengiriman pesan reguler

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

3) enumerateObjectsUsingBlock & kirim pesan biasa

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

Ternyata makeObjectsPerformSelector adalah yang paling lambat sejauh ini. Butuh pencacahan dua kali lebih lama. Dan enumerateObjectsUsingBlock adalah yang tercepat, itu sekitar 15-20% lebih cepat dari iterasi cepat.

Jadi, jika Anda sangat khawatir tentang kinerja terbaik, gunakan enumerateObjectsUsingBlock. Tetapi perlu diingat bahwa dalam beberapa kasus waktu yang diperlukan untuk menghitung koleksi dikerdilkan oleh waktu yang diperlukan untuk menjalankan kode apa pun yang Anda ingin setiap objek dieksekusi.

LearnCocos2D
sumber
Bisakah Anda menunjukkan tes khusus Anda? Anda sepertinya memberikan jawaban yang salah.
Cœur
3

Ini cukup berguna untuk menggunakan enumerateObjectsUsingBlock sebagai loop luar ketika Anda ingin memecah loop bersarang.

misalnya

[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;
      }
    }
  }
}];

Alternatifnya adalah menggunakan pernyataan goto.

Gabe
sumber
1
Atau Anda bisa saja kembali dari metode seperti yang Anda lakukan di sini :-D
Adam Kaplan
1

Terima kasih kepada @bbum dan @Chuck untuk memulai perbandingan komprehensif tentang kinerja. Senang mengetahui ini sepele. Saya sepertinya telah mengikuti:

  • for (... in ...)- sebagai goto default saya. Lebih intuitif bagi saya, lebih banyak sejarah pemrograman di sini daripada preferensi sebenarnya - penggunaan kembali lintas bahasa, kurang mengetik untuk sebagian besar struktur data karena IDE otomatis lengkap: P.

  • enumerateObject...- ketika akses ke objek dan indeks diperlukan. Dan ketika mengakses struktur non-array atau kamus (preferensi pribadi)

  • for (int i=idx; i<count; i++) - untuk array, ketika saya harus mulai dengan indeks yang tidak nol

bersalju
sumber