Когда использовать enumerateObjectsUsingBlock или для
Помимо очевидных отличий:
- использование
enumerateObjectsUsingBlock
когда вам нужен и индекс, и объект -
Не использоватьenumerateObjectsUsingBlock
когда вам нужно изменить локальные переменные(я ошибся, см. ответ bbum)
Является enumerateObjectsUsingBlock
как правило, считается лучше или хуже, когда for (id obj in myArray)
также будет работать? Каковы преимущества / недостатки (например, более или менее эффективный)?
6 ответов
В конечном счете, используйте любой шаблон, который вы хотите использовать, и он более естественен в контексте.
В то время как for(... in ...)
довольно удобно и синтаксически кратко, enumerateObjectsUsingBlock:
имеет ряд функций, которые могут оказаться интересными, а могут и не оказаться:
enumerateObjectsUsingBlock:
будет так же быстро или быстрее, чем быстрое перечисление (for(... in ...)
используетNSFastEnumeration
поддержка реализации перечисления). Быстрое перечисление требует перевода из внутреннего представления в представление для быстрого перечисления. Там есть над головой. Перечисление на основе блоков позволяет классу коллекции перечислять содержимое так же быстро, как и самый быстрый обход собственного формата хранения. Вероятно, не имеет значения для массивов, но это может иметь огромное значение для словарей."Не используйте enumerateObjectsUsingBlock, когда вам нужно изменить локальные переменные" - не верно; Вы можете объявить своих местных жителей как
__block
и они будут доступны для записи в блоке.enumerateObjectsWithOptions:usingBlock:
поддерживает одновременное или обратное перечисление.в словарях перечисление на основе блоков - единственный способ получить ключ и значение одновременно.
Лично я пользуюсь enumerateObjectsUsingBlock:
чаще чем for (... in ...)
, но - опять же - личный выбор.
Для простого перечисления, просто используя быстрое перечисление (т.е. for…in…
loop) - более идиоматический вариант. Блочный метод может быть немного быстрее, но в большинстве случаев это не имеет большого значения - лишь немногие программы связаны с процессором, и даже в этом случае узким местом будет сам цикл, а не вычисления внутри.
Простой цикл также читается более четко. Вот образец двух версий:
for (id x in y){
}
[y enumerateObjectsUsingBlock:^(id x, NSUInteger index, BOOL *stop){
}];
Даже если вы добавите переменную для отслеживания индекса, простой цикл будет легче читать.
Поэтому, когда вы должны использовать enumerateObjectsUsingBlock:
? Когда вы храните блок для выполнения позже или в нескольких местах. Это хорошо, когда вы на самом деле используете блок в качестве первоклассной функции, а не замену тела цикла.
Хотя этот вопрос старый, все не изменилось, принятый ответ неверен.
enumerateObjectsUsingBlock
API не должен был заменять for-in
, но для совершенно другого варианта использования:
- Это позволяет применять произвольную нелокальную логику. т.е. вам не нужно знать, что делает блок, чтобы использовать его в массиве.
- Параллельное перечисление для больших коллекций или тяжелых вычислений (используя
withOptions:
параметр)
Быстрое перечисление с for-in
все еще идиоматический метод перечисления коллекции.
Быстрое перечисление выигрывает от краткости кода, читабельности и дополнительных оптимизаций, которые делают его неестественно быстрым. Быстрее, чем старый цикл C!
Быстрый тест показывает, что в 2014 году на iOS 7 enumerateObjectsUsingBlock
последовательно на 700% медленнее, чем for-in (на основе 1-миллиметровых итераций массива из 100 элементов).
Является ли производительность реальной практической проблемой здесь?
Определенно нет, за редким исключением.
Дело в том, чтобы продемонстрировать, что использование 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
Чтобы ответить на вопрос о производительности, я провел несколько тестов, используя свой проект тестирования производительности. Я хотел знать, какой из трех вариантов отправки сообщения всем объектам в массиве является самым быстрым.
Варианты были:
1) makeObjectsPerformSelector
[arr makeObjectsPerformSelector:@selector(_stubMethod)];
2) быстрое перечисление и отправка обычного сообщения
for (id item in arr)
{
[item _stubMethod];
}
3) enumerateObjectsUsingBlock и отправка обычного сообщения
[arr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
[obj _stubMethod];
}];
Оказывается, что makeObjectsPerformSelector был самым медленным на сегодняшний день. Это заняло вдвое больше времени, чем быстрое перечисление. И enumerateObjectsUsingBlock был самым быстрым, он был примерно на 15-20% быстрее, чем быстрая итерация.
Поэтому, если вы очень заинтересованы в максимальной производительности, используйте enumerateObjectsUsingBlock. Но имейте в виду, что в некоторых случаях время, необходимое для перечисления коллекции, значительно меньше времени, которое требуется для выполнения любого кода, который вы хотите, чтобы каждый объект выполнял.
Довольно полезно использовать 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.
Спасибо @bbum и @Chuck за всестороннее сравнение производительности. Рад знать, что это тривиально. Я, кажется, пошел с:
for (... in ...)
- как мой по умолчанию goto. Более интуитивно понятный для меня, больше истории программирования здесь, чем любое реальное предпочтение - многоязычное повторное использование, меньше набирание текста для большинства структур данных благодаря автоматическому завершению IDE: P.enumerateObject...
- когда необходим доступ к объекту и индексу. И при доступе к структурам без массивов или словарей (персональные предпочтения)for (int i=idx; i<count; i++)
- для массивов, когда мне нужно начать с ненулевого индекса