Quando usar enumerateObjectsUsingBlock vs. for

150

Além das diferenças óbvias:

  • Use enumerateObjectsUsingBlockquando você precisar do índice e do objeto
  • Não use enumerateObjectsUsingBlockquando precisar modificar variáveis ​​locais (eu estava errado sobre isso, veja a resposta do bbum)

enumerateObjectsUsingBlockGeralmente é considerado melhor ou pior quando for (id obj in myArray)também funcionaria? Quais são as vantagens / desvantagens (por exemplo, é mais ou menos desempenho)?

Paul Wheeler
fonte
1
Eu gosto de usá-lo se precisar do índice atual.
Sep12
Veja também: stackoverflow.com/questions/8509662/…
Simon Whitaker

Respostas:

350

Por fim, use o padrão que você deseja usar e seja mais natural no contexto.

Embora for(... in ...)seja bastante conveniente e sintaticamente breve, enumerateObjectsUsingBlock:possui vários recursos que podem ou não ser interessantes:

  • enumerateObjectsUsingBlock:será tão rápido ou mais rápido que a enumeração rápida ( for(... in ...)usa o NSFastEnumerationsuporte para implementar a enumeração). A enumeração rápida requer tradução de uma representação interna para a representação para enumeração rápida. Há sobrecarga nele. A enumeração baseada em bloco permite que a classe de coleção enumere o conteúdo tão rapidamente quanto a travessia mais rápida do formato de armazenamento nativo. Provavelmente irrelevante para matrizes, mas pode ser uma enorme diferença para dicionários.

  • "Não use enumerateObjectsUsingBlock quando precisar modificar variáveis ​​locais" - não é verdade; você pode declarar seus locais como __blocke eles serão graváveis ​​no bloco.

  • enumerateObjectsWithOptions:usingBlock: suporta enumeração simultânea ou reversa.

  • com dicionários, a enumeração baseada em bloco é a única maneira de recuperar a chave e o valor simultaneamente.

Pessoalmente, uso com enumerateObjectsUsingBlock:mais frequência do que for (... in ...), mas - novamente - escolha pessoal.

bbum
fonte
16
Uau, muito informativo. Eu gostaria de poder aceitar as duas respostas, mas vou com a de Chuck porque isso ressoa um pouco mais comigo. Além disso, encontrei seu blog ( friday.com/bbum/2009/08/29/blocks-tips-tricks ) enquanto pesquisava __block e aprendi ainda mais. Obrigado.
Paul Wheeler
8
Para o registro, enumeração baseada em blocos nem sempre é "tão ou mais rápido" mikeabdullah.net/slow-block-based-dictionary-enumeration.html
Mike Abdullah
2
Os blocos @VanDuTran são executados apenas em um thread separado se você solicitar que eles sejam executados em um thread separado. A menos que você use a opção de simultaneidade de enumeração, então ele vai ser executado no mesmo segmento como a chamada foi feita
bbum
2
Nick Lockwood fez um artigo muito bom sobre isso, e parece que enumerateObjectsUsingBlockainda é significativamente mais lento que a enumeração rápida para matrizes e conjuntos. Eu quero saber porque? iosdevelopertips.com/objective-c/...
Bob Spryn
2
Embora seja um tipo de detalhe de implementação, deve-se mencionar nesta resposta entre as diferenças entre as duas abordagens que enumerateObjectsUsingBlockenvolvem cada chamada do bloco com um pool de liberação automática (pelo menos no OS X 10.10). Isso explica a diferença de desempenho em comparação à for inqual não faz isso.
Pol
83

Para enumeração simples, basta usar a enumeração rápida (ou seja, um for…in… loop) é a opção mais idiomática. O método de bloco pode ser um pouco mais rápido, mas isso não importa muito na maioria dos casos - poucos programas são vinculados à CPU e, mesmo assim, é raro que o próprio loop em vez da computação interna seja um gargalo.

Um loop simples também lê com mais clareza. Aqui está o boilerplate das duas versões:

for (id x in y){
}

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

Mesmo se você adicionar uma variável para rastrear o índice, o loop simples será mais fácil de ler.

Então, quando você deve usar enumerateObjectsUsingBlock:? Quando você está armazenando um bloco para executar mais tarde ou em vários lugares. É bom para quando você está realmente usando um bloco como uma função de primeira classe, em vez de uma substituição exagerada de um corpo de loop.

Mandril
fonte
5
enumerateObjectsUsingBlock:será a mesma velocidade ou mais rápida que a enumeração rápida em todos os casos. for(... in ...)usa enumeração rápida que requer que a coleção forneça alguma representação intermediária das estruturas de dados internas. Como você observa, provavelmente irrelevante.
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 Meus próprios testes mostram que, enumerateObjects...na verdade, pode ser mais lento que a enumeração rápida com um loop. Eu fiz esse teste milhares de vezes; o corpo do bloco e ciclo eram os mesmos única linha de código: [(NSOperation *)obj cancel];. As médias: loop enum rápido - -[JHStatusBar dequeueStatusMessage:] [Line: 147] Fast enumeration time (for..in..loop): 0.000009e para o bloco - -[JHStatusBar dequeueStatusMessage:] [Line: 147] Enumeration time using block: 0.000043. Estranho que a diferença de horário seja tão grande e consistente, mas, obviamente, esse é um caso de teste muito específico.
chown
42

Embora essa pergunta seja antiga, as coisas não mudaram, a resposta aceita está incorreta.

A enumerateObjectsUsingBlockAPI não foi substituída for-in, mas para um caso de uso totalmente diferente:

  • Permite a aplicação de lógica arbitrária e não local. ou seja, você não precisa saber o que o bloco faz para usá-lo em uma matriz.
  • Enumeração simultânea para coleções grandes ou computação pesada (usando o withOptions:parâmetro)

A Enumeração Rápida com for-inainda é o método idiomático de enumerar uma coleção.

A Enumeração Rápida se beneficia da brevidade do código, da legibilidade e das otimizações adicionais, que a tornam estranhamente rápida. Mais rápido que um loop for C antigo!

Um teste rápido conclui que no ano de 2014 no iOS 7, enumerateObjectsUsingBlocké consistentemente 700% mais lento que o for-in (com base nas iterações de 1 mm de uma matriz de 100 itens).

O desempenho é uma preocupação prática real aqui?

Definitivamente não, com raras exceções.

O objetivo é demonstrar que há pouco benefício em usar enumerateObjectsUsingBlock:mais defor-in sem uma boa razão. Não torna o código mais legível ... ou mais rápido ... ou seguro para threads. (outro equívoco comum).

A escolha se resume à preferência pessoal. Para mim, a opção idiomática e legível vence. Nesse caso, isso é enumeração rápida usando for-in.

Referência:

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

Resultados:

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
fonte
4
Posso confirmar que a execução do mesmo teste em um MacBook Pro Retina 2014 enumerateObjectsUsingBlocké realmente 5X mais lenta. Aparentemente, isso ocorre devido a um pool de autorelease que envolve cada invocação do bloco, o que não acontece no for incaso.
Pol
2
Confirmo que enumerateObjectsUsingBlock:ainda é 4X mais lento no iPhone 6 iOS9 real, usando o Xcode 7.x para compilar.
Cœur
1
Obrigado por confirmar! Eu gostaria que essa resposta não ficasse tão enterrada ... algumas pessoas gostam da enumeratesintaxe, porque ela se parece com um FP e não quer ouvir a análise de desempenho.
Adam Kaplan
1
A propósito, não é apenas devido ao pool de liberação automática. Há muito mais pilha empurrando & popping comenumerate:
Adam Kaplan
24

Para responder à pergunta sobre desempenho, fiz alguns testes usando meu projeto de teste de desempenho . Eu queria saber qual das três opções para enviar uma mensagem para todos os objetos em uma matriz é a mais rápida.

As opções foram:

1) makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2) enumeração rápida e envio regular de mensagens

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

3) enumerateObjectsUsingBlock e envio regular de mensagens

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

Acontece que makeObjectsPerformSelector foi o mais lento de longe. Demorou o dobro do tempo da enumeração rápida. E o enumerateObjectsUsingBlock foi o mais rápido, cerca de 15 a 20% mais rápido que a iteração rápida.

Portanto, se você estiver muito preocupado com o melhor desempenho possível, use enumerateObjectsUsingBlock. Mas lembre-se de que, em alguns casos, o tempo que leva para enumerar uma coleção é diminuído pelo tempo que leva para executar o código que você deseja que cada objeto execute.

LearnCocos2D
fonte
Você pode indicar seu teste específico? Você parece ter dado uma resposta errada.
Cœur
3

É bastante útil usar enumerateObjectsUsingBlock como um loop externo quando você deseja interromper loops aninhados.

por exemplo

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

A alternativa é usar instruções goto.

Gabe
fonte
1
Ou você poderia apenas voltar a partir do método, assim como você fez aqui :-D
Adam Kaplan
1

Agradecemos a @bbum e @Chuck por iniciarem comparações abrangentes sobre desempenho. Fico feliz em saber que é trivial. Eu pareço ter ido com:

  • for (... in ...)- como meu padrão ir. Mais intuitivo para mim, mais histórico de programação aqui do que qualquer preferência real - reutilização entre idiomas, menos digitação para a maioria das estruturas de dados devido ao preenchimento automático do IDE: P.

  • enumerateObject...- quando o acesso ao objeto e ao índice for necessário. E ao acessar estruturas que não são de matriz ou de dicionário (preferência pessoal)

  • for (int i=idx; i<count; i++) - para matrizes, quando eu preciso iniciar em um índice diferente de zero

snowbound
fonte