İçin enumerateObjectsUsingBlock vs. ne zaman kullanılır?


150

Bariz farklılıkların yanı sıra:

  • enumerateObjectsUsingBlockHem dizine hem de nesneye ihtiyacınız olduğunda kullanın
  • enumerateObjectsUsingBlockYerel değişkenleri değiştirmeniz gerektiğinde kullanmayın (Bu konuda yanılmışım, bumum'un cevabına bakın)

Ne enumerateObjectsUsingBlockzaman for (id obj in myArray)işe yarayacağı genellikle daha iyi veya daha kötü olarak kabul edilir ? Avantajları / dezavantajları nelerdir (örneğin az çok performans mıdır)?


1
Geçerli dizine ihtiyacım varsa kullanmayı seviyorum.
Besi

Yanıtlar:


350

Sonuç olarak, hangi modeli kullanmak istediğinizi kullanın ve bağlamda daha doğal bir şekilde gelir.

for(... in ...)Oldukça kullanışlı ve sözdizimsel olarak kısa olsa enumerateObjectsUsingBlock:da, ilginç olabilecek veya kanıtlamayacak bir dizi özelliğe sahiptir:

  • enumerateObjectsUsingBlock:hızlı numaralandırmadan daha hızlı veya daha hızlı olacaktır ( numaralandırmayı uygulamak for(... in ...)için NSFastEnumerationdesteği kullanır ). Hızlı numaralandırma, dahili numaradan hızlı numaralandırma için temsile çeviri gerektirir. Orada tepegöz var. Blok tabanlı numaralandırma, toplama sınıfının, içerikleri yerel depolama biçiminin en hızlı geçişi kadar hızlı bir şekilde numaralandırmasını sağlar. Diziler için ilgisiz, ancak sözlükler için büyük bir fark olabilir.

  • "Yerel değişkenleri değiştirmeniz gerektiğinde enumerateObjectsUsingBlock kullanmayın" - doğru değil; yerliler olarak ilan edebilirsiniz __blockve onlar blokta yazılabilir olacak.

  • enumerateObjectsWithOptions:usingBlock: eşzamanlı veya ters numaralandırmayı destekler.

  • sözlüklerde, anahtar ve değeri aynı anda almanın tek yolu blok tabanlı numaralandırmadır.

Şahsen, kişisel seçimden enumerateObjectsUsingBlock:daha sık for (... in ...), ama - tekrar - kullanıyorum.


16
Vay canına, çok bilgilendirici. Keşke bu cevapların ikisini de kabul edebilseydim, ama Chuck'ın yanına gidiyorum çünkü benimle biraz daha yankılanıyor. Ayrıca, __block'u ararken blogunuzu ( cumaday.com/bbum/2009/08/29/blocks-tips-tricks ) buldum ve daha fazlasını öğrendim. Teşekkür ederim.
Paul Wheeler

8
Kayıt için, blok tabanlı numaralandırma her zaman "hızlı veya daha hızlı" değildir mikeabdullah.net/slow-block-based-dictionary-enumeration.html
Mike Abdullah

2
@VanDuTran Blokları yalnızca ayrı bir iş parçacığında yürütülmelerini söylerseniz ayrı bir iş parçacığında yürütülür. Numaralandırma eşzamanlılık seçeneğini kullanmazsanız, o zaman çağrı yapılanla aynı iş parçacığında yürütülür
bbum

2
Nick Lockwood bu konuda gerçekten güzel bir makale yaptı ve enumerateObjectsUsingBlockdiziler ve kümeler için hızlı numaralandırmadan hala önemli ölçüde yavaş görünüyor . Nedenini merak ediyorum? iosdevelopertips.com/objective-c/…
Bob Spryn

2
Her ne kadar bir uygulama detayı olsa da, bu cevapta enumerateObjectsUsingBlock, bloğun her çağrılmasını bir otomatik yayın havuzuyla saran iki yaklaşım arasındaki farklar arasında belirtilmelidir (en azından OS X 10.10'dan itibaren). Bu, for inbunu yapmayan performans farkını açıklar .
Pol

83

Basit numaralandırma için, hızlı numaralandırma (örn. for…in… döngü) kullanmak daha deyimsel seçenektir. Block yöntemi marjinal olarak daha hızlı olabilir, ancak bu çoğu durumda önemli değildir - birkaç program CPU'ya bağlıdır ve o zaman bile, içindeki hesaplamadan ziyade döngünün kendisinin bir darboğaz olması nadirdir.

Basit bir döngü de daha net okunur. İşte iki versiyonun temeli:

for (id x in y){
}

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

Dizini izlemek için bir değişken ekleseniz bile, basit döngü daha kolay okunur.

Ne zaman kullanmalısın enumerateObjectsUsingBlock: ? Daha sonra veya birden fazla yerde yürütmek için bir bloğu saklarken. Bir bloğu, bir döngü gövdesi için aşırı işlenmiş bir yedek yerine birinci sınıf bir işlev olarak kullandığınızda iyidir.


5
enumerateObjectsUsingBlock:her durumda aynı hız veya hızlı numaralandırmadan daha hızlı olacaktır. for(... in ...)dahili veri yapılarının geçici temsilini sağlamak için koleksiyonun gerektirdiği hızlı numaralandırma kullanır. Belirttiğiniz gibi, muhtemelen alakasız.
Barbekü

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 Kendi testlerim enumerateObjects..., bir döngü ile aslında daha yavaş ve hızlı numaralandırmanın olabileceğini gösteriyor . Bu testi birkaç bin kez yaptım; blok ve döngünün gövdesi aynı kod tek satır edildi: [(NSOperation *)obj cancel];. Ortalamalar: hızlı numaralandırma döngüsü - -[JHStatusBar dequeueStatusMessage:] [Line: 147] Fast enumeration time (for..in..loop): 0.000009ve blok için - -[JHStatusBar dequeueStatusMessage:] [Line: 147] Enumeration time using block: 0.000043. Zaman farkının çok büyük ve tutarlı olması garip ama açıkçası, bu çok spesifik bir test örneğidir.
chown

42

Bu soru eski olmasına rağmen işler değişmedi, kabul edilen cevap yanlış.

enumerateObjectsUsingBlockAPI supersede demek değildifor-in ama tamamen farklı kullanım durumda,:

  • Keyfi, yerel olmayan mantığın uygulanmasına izin verir. yani bloğun bir dizide kullanmak için ne yaptığını bilmenize gerek yoktur.
  • Büyük koleksiyonlar veya ağır hesaplama için eşzamanlı numaralandırma ( withOptions:parametreyi kullanarak )

Hızlı Sayım for-inhala deyimsel bir koleksiyon sıralandıran yöntemi.

Hızlı Numaralandırma, kodun kısalığı, okunabilirliği ve doğal olmayan hızlı hale getiren ek optimizasyonlardan yararlanır. Eski bir C for-loop'tan daha hızlı!

Hızlı bir test, iOS 7'de 2014 yılında, girişe göre enumerateObjectsUsingBlocksürekli olarak% 700 daha yavaş olduğu sonucuna varıyor (100 öğe dizisinin 1 mm yinelemelerine dayanarak).

Performans burada gerçek bir pratik kaygı mıdır?

Kesinlikle hayır, nadir istisna dışında.

Nokta küçük yararı kullanmanın olduğunu göstermektir enumerateObjectsUsingBlock:üzerindefor-in gerçekten iyi bir neden olmadan. Kodu daha okunabilir ... veya daha hızlı ... veya iş parçacığı açısından güvenli hale getirmez. (başka bir yaygın yanlış anlama).

Seçim kişisel tercihe bağlıdır. Benim için deyimsel ve okunabilir seçenek kazanıyor. Bu durumda, bu Hızlı Numaralandırma kullanarak for-in.

Karşılaştırma:

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

Sonuçlar:

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
Aynı testi bir MacBook Pro Retina 2014'te çalıştırmanın enumerateObjectsUsingBlockaslında 5 kat daha yavaş olduğunu onaylayabilirim . Görünüşe göre bu, bloğun her çağrılmasını saran bir otomatik serbest bırakma havuzundan kaynaklanıyor, bu da durum için gerçekleşmiyor for in.
Pol

2
enumerateObjectsUsingBlock:Oluşturmak için Xcode 7.x kullanarak gerçek iPhone 6 iOS9'da hala 4X daha yavaş olduğunu onaylıyorum .
Cur

1
Onayladığınız için teşekkürler! Keşke bu cevabın çok gömülmemesi ... bazı insanlar enumeratesözdizimini seviyor çünkü FP'ye benziyor ve performans analizini dinlemek istemiyor.
Adam Kaplan

1
Bu arada, sadece otomatik yayın havuzundan değil. Çok daha fazla yığın itme ve haşhaş varenumerate:
Adam Kaplan

24

Performans sorusunu cevaplamak için performans testi projemi kullanarak bazı testler yaptım . Bir dizideki tüm nesnelere mesaj göndermek için üç seçenekten hangisinin en hızlı olduğunu bilmek istedim.

Seçenekler:

1) makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2) hızlı numaralandırma ve düzenli mesaj gönderme

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

3) enumerateObjectsUsingBlock ve normal mesaj gönderme

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

Görünüşe göre makeObjectsPerformSelector açık ara en yavaş olanıydı. Hızlı numaralandırmanın iki katı kadar zaman aldı. Ve enumerateObjectsUsingBlock en hızlıydı, hızlı yinelemeden yaklaşık% 15-20 daha hızlıydı.

Dolayısıyla, mümkün olan en iyi performansla ilgili endişeleriniz varsa enumerateObjectsUsingBlock kullanın. Ancak, bazı durumlarda bir koleksiyonun numaralandırılması için geçen sürenin, her nesnenin yürütmesini istediğiniz kodu çalıştırmak için gereken zamana göre gölgesinde kaldığını unutmayın.


Özel testinize dikkat çekebilir misiniz? Yanlış bir cevap vermişsiniz gibi görünüyor.
Cœur

3

İç içe döngüleri kırmak istediğinizde enumerateObjectsUsingBlock öğesini dış döngü olarak kullanmak oldukça yararlıdır.

Örneğin

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

Alternatif goto ifadeleri kullanmaktır.


1
Ya da tıpkı burada yaptığınız gibi yöntemden dönebilirsiniz :-D
Adam Kaplan

1

Performansla ilgili kapsamlı karşılaştırmalar başlatmak için @bbum ve @Chuck'a teşekkürler. Önemsiz olduğunu bildiğim için memnunum. Ben gitmiş gibi görünüyor:

  • for (... in ...)- varsayılan goto'um olarak. Bana göre daha sezgisel, gerçek tercihlerden daha fazla programlama geçmişi - diller arası yeniden kullanım, IDE otomatik tamamlama nedeniyle çoğu veri yapısı için daha az yazma: P.

  • enumerateObject...- nesneye ve dizine erişim gerektiğinde. Dizi dışı veya sözlük yapılarına erişirken (kişisel tercih)

  • for (int i=idx; i<count; i++) - diziler için, sıfırdan farklı bir dizinde başlamam gerektiğinde

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.