Oprócz oczywistych różnic:
- Użyj,
enumerateObjectsUsingBlock
gdy potrzebujesz zarówno indeksu, jak i obiektu Nie używaj,(myliłem się, zobacz odpowiedź bbum)enumerateObjectsUsingBlock
gdy musisz zmodyfikować zmienne lokalne
Czy enumerateObjectsUsingBlock
ogólnie uważa się, że lepiej czy gorzej, kiedy for (id obj in myArray)
również zadziała? Jakie są zalety / wady (na przykład czy jest bardziej lub mniej wydajne)?
objective-c
multithreading
ios4
objective-c-blocks
Paul Wheeler
źródło
źródło
Odpowiedzi:
Ostatecznie użyj dowolnego wzoru, którego chcesz użyć i jest bardziej naturalny w kontekście.
Chociaż
for(... in ...)
jest dość wygodny i krótki składniowo,enumerateObjectsUsingBlock:
ma wiele funkcji, które mogą okazać się interesujące lub nie:enumerateObjectsUsingBlock:
będzie tak samo szybkie lub szybsze niż szybkie wyliczanie (for(... in ...)
używaNSFastEnumeration
wsparcia do implementacji wyliczania). Szybkie wyliczanie wymaga tłumaczenia z reprezentacji wewnętrznej na reprezentację w celu szybkiego wyliczenia. Jest w tym nad głową. Wyliczanie oparte na blokach umożliwia klasie kolekcji wyliczanie zawartości tak szybko, jak najszybsze przechodzenie w rodzimym formacie magazynu. Prawdopodobnie nie ma to znaczenia w przypadku tablic, ale w przypadku słowników może to być ogromna różnica.„Nie używaj enumerateObjectsUsingBlock, gdy musisz zmodyfikować zmienne lokalne” - nie prawda; możesz zadeklarować swoje lokalne adresy jako
__block
i będą dostępne do zapisu w bloku.enumerateObjectsWithOptions:usingBlock:
obsługuje wyliczanie równoczesne lub odwrotne.W przypadku słowników wyliczanie oparte na blokach jest jedynym sposobem jednoczesnego pobrania klucza i wartości.
Osobiście używam
enumerateObjectsUsingBlock:
częściej niżfor (... in ...)
, ale - znowu - osobistego wyboru.źródło
enumerateObjectsUsingBlock
jest on nadal znacznie wolniejszy niż szybkie wyliczanie tablic i zestawów. Zastanawiam się dlaczego? iosdevelopertips.com/objective-c/…enumerateObjectsUsingBlock
zawijają każde wywołanie bloku pulą autorelease (przynajmniej od OS X 10.10). To wyjaśnia różnicę w wydajności w porównaniu do tego,for in
który tego nie robi.W przypadku prostego wyliczania, po prostu użycie szybkiego wyliczania (tj.
for…in…
Pętli) jest bardziej idiomatyczną opcją. Metoda blokowa może być nieznacznie szybsza, ale w większości przypadków nie ma to większego znaczenia - niewiele programów jest związanych z procesorem, a nawet wtedy rzadko zdarza się, że sama pętla zamiast obliczeń wewnątrz będzie wąskim gardłem.Prosta pętla również czyta wyraźniej. Oto schemat dwóch wersji:
Nawet jeśli dodasz zmienną do śledzenia indeksu, prosta pętla będzie łatwiejsza do odczytania.
Kiedy więc powinieneś użyć
enumerateObjectsUsingBlock:
? Kiedy przechowujesz blok do wykonania później lub w wielu miejscach. Jest to dobre rozwiązanie, gdy w rzeczywistości używasz bloku jako funkcji pierwszej klasy, a nie zastępczego zamiennika ciała pętli.źródło
enumerateObjectsUsingBlock:
we wszystkich przypadkach będzie albo taka sama prędkość, albo szybsza niż szybkie wyliczanie.for(... in ...)
wykorzystuje szybkie wyliczanie, które wymaga, aby kolekcja zapewniała tymczasową reprezentację wewnętrznych struktur danych. Jak zauważyłeś, prawdopodobnie nieistotne.When 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.
enumerateObjects...
rzeczywistości może być wolniejsze niż szybkie wyliczanie z pętlą. Przeprowadziłem ten test kilka tysięcy razy; Ciało bloku i pętlą były takie same jednej linii kodu:[(NSOperation *)obj cancel];
. Średnie: szybka pętla wyliczeniowa --[JHStatusBar dequeueStatusMessage:] [Line: 147] Fast enumeration time (for..in..loop): 0.000009
i dla bloku --[JHStatusBar dequeueStatusMessage:] [Line: 147] Enumeration time using block: 0.000043
. Dziwne, że różnica czasu jest tak duża i spójna, ale oczywiście jest to bardzo specyficzny przypadek testowy.Chociaż to pytanie jest stare, nic się nie zmieniło, przyjęta odpowiedź jest nieprawidłowa.
enumerateObjectsUsingBlock
API nie miało zastępująfor-in
, ale na zupełnie innym przypadku użycia:withOptions:
parametru)Fast Enumeration with
for-in
jest nadal idiomatyczną metodą wyliczania kolekcji.Fast Enumeration korzysta ze zwięzłości kodu, czytelności i dodatkowych optymalizacji, które sprawiają, że jest nienaturalnie szybki. Szybszy niż stara pętla for w C!
Szybki test stwierdza, że w roku 2014 na iOS 7
enumerateObjectsUsingBlock
jest konsekwentnie o 700% wolniejszy niż for-in (na podstawie 1 mm iteracji tablicy 100 elementów).Czy wydajność jest tutaj praktycznym problemem?
Zdecydowanie nie, z rzadkimi wyjątkami.
Chodzi o to, aby wykazać, że istnieje niewiele korzyści przy użyciu
enumerateObjectsUsingBlock:
ponadfor-in
bez naprawdę dobrego powodu. Nie czyni kodu bardziej czytelnym ... ani szybszym ... ani bezpiecznym wątkowo. (kolejne powszechne nieporozumienie).Wybór sprowadza się do osobistych preferencji. U mnie wygrywa opcja idiomatyczna i czytelna. W tym przypadku jest to szybkie wyliczanie przy użyciu
for-in
.Reper:
Wyniki:
źródło
enumerateObjectsUsingBlock
jest w rzeczywistości 5X wolniejsze. Wynika to najwyraźniej z powodu puli autowydzielania opakowującej każde wywołanie bloku, co nie ma miejsca w tymfor in
przypadku.enumerateObjectsUsingBlock:
jest nadal 4X wolniejszy na prawdziwym iPhonie 6 iOS9, używając Xcode 7.x do kompilacji.enumerate
składnię, ponieważ jest podobna do FP i nie chcą słuchać analizy wykonania.enumerate:
Aby odpowiedzieć na pytanie dotyczące wydajności, wykonałem kilka testów, korzystając z mojego projektu testów wydajności . Chciałem wiedzieć, która z trzech opcji wysyłania wiadomości do wszystkich obiektów w tablicy jest najszybsza.
Dostępne opcje to:
1) makeObjectsPerformSelector
2) szybkie wyliczanie i regularne wysyłanie wiadomości
3) enumerateObjectsUsingBlock i zwykłe wysyłanie wiadomości
Okazuje się, że makeObjectsPerformSelector był zdecydowanie najwolniejszy. Zajęło to dwa razy więcej czasu niż szybkie wyliczenie. A enumerateObjectsUsingBlock był najszybszy, był około 15-20% szybszy niż szybka iteracja.
Więc jeśli bardzo martwisz się o najlepszą możliwą wydajność, użyj enumerateObjectsUsingBlock. Należy jednak pamiętać, że w niektórych przypadkach czas potrzebny do wyliczenia kolekcji skraca się o czas potrzebny do uruchomienia dowolnego kodu, który ma wykonać każdy obiekt.
źródło
Używanie enumerateObjectsUsingBlock jako zewnętrznej pętli jest dość przydatne, gdy chcesz przerwać zagnieżdżone pętle.
na przykład
Alternatywą jest użycie instrukcji goto.
źródło
Podziękowania dla @bbum i @Chuck za rozpoczęcie wszechstronnych porównań wydajności. Cieszę się, że to trywialne. Wydaje mi się, że poszedłem z:
for (... in ...)
- jako moje domyślne goto. Bardziej intuicyjny dla mnie, więcej historii programowania niż jakiekolwiek rzeczywiste preferencje - ponowne użycie w różnych językach, mniej pisania dla większości struktur danych dzięki automatycznemu uzupełnianiu IDE: P.enumerateObject...
- kiedy potrzebny jest dostęp do obiektu i indeksu. Podczas uzyskiwania dostępu do struktur innych niż tablice lub słownikowe (osobiste preferencje)for (int i=idx; i<count; i++)
- dla tablic, kiedy muszę zacząć od niezerowego indeksuźródło