it-swarm.com.ru

Когда использовать enumerateObjectsUsingBlock или для

Помимо очевидных отличий:

  • Используйте enumerateObjectsUsingBlock, когда вам нужен и индекс, и объект
  • Не используйте enumerateObjectsUsingBlock, когда вам нужно изменить локальные переменные (Я был неправ по этому поводу, см ответ Bbum)

enumerateObjectsUsingBlock обычно считается лучше или хуже, когда for (id obj in myArray) также будет работать? Каковы преимущества/недостатки (например, более или менее эффективны)?

150
Paul Wheeler

В конечном счете, используйте любой шаблон, который вы хотите использовать, и он более естественен в контексте.

Хотя for(... in ...) довольно удобен и синтаксически краток, enumerateObjectsUsingBlock: имеет ряд функций, которые могут оказаться или не быть интересными:

  • enumerateObjectsUsingBlock: будет таким же быстрым или быстрым, как быстрое перечисление (for(... in ...) использует поддержку NSFastEnumeration для реализации перечисления). Быстрое перечисление требует перевода из внутреннего представления в представление для быстрого перечисления. Там есть над головой. Перечисление на основе блоков позволяет классу коллекции перечислять содержимое так же быстро, как и самый быстрый обход собственного формата хранения. Вероятно, не имеет значения для массивов, но это может быть огромной разницей для словарей.

  • "Не используйте enumerateObjectsUsingBlock, когда вам нужно изменить локальные переменные" - не верно; Вы можете объявить своих местных как __block, и они будут доступны для записи в блоке.

  • enumerateObjectsWithOptions:usingBlock: поддерживает одновременное или обратное перечисление.

  • в словарях перечисление на основе блоков - единственный способ получить ключ и значение одновременно.

Лично я использую enumerateObjectsUsingBlock: чаще, чем for (... in ...), но - опять же - личный выбор.

346
bbum

Для простого перечисления простое использование быстрого перечисления (то есть цикл for…in…) является более идиоматическим вариантом. Блочный метод может быть немного быстрее, но в большинстве случаев это не имеет большого значения - лишь немногие программы связаны с процессором, и даже в этом случае узким местом будет сам цикл, а не вычисления внутри.

Простой цикл также читается более четко. Вот образец двух версий:

for (id x in y){
}

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

Даже если вы добавите переменную для отслеживания индекса, простой цикл будет легче читать.

Итак, когда вы должны использовать enumerateObjectsUsingBlock:? Когда вы храните блок для выполнения позже или в нескольких местах. Это хорошо, когда вы на самом деле используете блок как первоклассную функцию, а не замену тела цикла.

81
Chuck

Хотя этот вопрос старый, все не изменилось, принятый ответ неверен.

API enumerateObjectsUsingBlock не предназначался для замены 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
41
Adam Kaplan

Чтобы ответить на вопрос о производительности, я провел несколько тестов, используя мой проект тестирования производительности . Я хотел знать, какой из трех вариантов отправки сообщения всем объектам в массиве является самым быстрым.

Варианты были:

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. Но имейте в виду, что в некоторых случаях время, необходимое для перечисления коллекции, значительно меньше времени, которое требуется для выполнения любого кода, который вы хотите, чтобы каждый объект выполнял.

23
LearnCocos2D

Довольно полезно использовать 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.

3
Gabe

Спасибо @bbum и @Chuck за всестороннее сравнение производительности. Рад знать, что это тривиально. Я, кажется, пошел с:

  • for (... in ...) - как переход по умолчанию. Для меня это более интуитивно понятно, больше истории программирования, чем у любого реального предпочтения - многоязычное повторное использование, меньше набираний текста для большинства структур данных благодаря автоматическому завершению IDE: P.

  • enumerateObject... - когда необходим доступ к объекту и индексу. И при доступе к структурам без массивов или словарей (персональные предпочтения)

  • for (int i=idx; i<count; i++) - для массивов, когда мне нужно начать с ненулевого индекса

1
snowbound