Como faço para iterar em um NSArray?

Eu estou procurando o idioma padrão para iterar sobre um NSArray. Meu código precisa ser adequado para o OS X 10.4+.

O código geralmente preferido para o 10.5 + / iOS.

for (id object in array) { // do something with object } 

Essa construção é usada para enumerar objects em uma coleção que esteja de acordo com o [NSFastEnumeration protocol] ( Cocoa Reference ). Essa abordagem tem uma vantagem de velocidade porque armazena pointers para vários objects (obtidos por meio de uma única chamada de método) em um buffer e os percorre avançando pelo buffer usando a aritmética de pointers. Isso é muito mais rápido do que chamar -objectAtIndex: cada vez através do loop.

Também vale a pena notar que, embora tecnicamente você possa usar um loop for-in para percorrer um NSEnumerator , descobri que isso anula praticamente toda a vantagem de velocidade da enumeração rápida. O motivo é que a implementação padrão de NSEnumerator de -countByEnumeratingWithState:objects:count: coloca apenas um object no buffer em cada chamada.

Eu relatei isso no radar://6296108 (a enumeração rápida de NSEnumerators é lenta), mas ela foi retornada como Not To Be Fixed. O motivo é que a enumeração rápida pré-busca um grupo de objects, e se você deseja enumerar apenas um determinado ponto no enumerador (por exemplo, até que um object específico é encontrado ou condição é atendida) e usar o mesmo enumerador após sair do loop, muitas vezes seria o caso de vários objects serem ignorados.

Se você está codificando para o OS X 10.6 / iOS 4.0 e acima, você também tem a opção de usar APIs baseadas em blocos para enumerar matrizes e outras collections:

 [array enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) { // do something with object }]; 

Você também pode usar -enumerateObjectsWithOptions:usingBlock: e passar NSEnumerationConcurrent e / ou NSEnumerationReverse como o argumento de opções.


10.4 ou anterior

O idioma padrão para o pré-10.5 é usar um NSEnumerator e um loop while, da seguinte forma:

 NSEnumerator *e = [array objectEnumerator]; id object; while (object = [e nextObject]) { // do something with object } 

Eu recomendo mantê-lo simples. Amarrar-se a um tipo de array é inflexível, e o supostamente aumento de velocidade do uso de -objectAtIndex: é insignificante para a melhoria com enumeração rápida em 10.5+ de qualquer maneira. (A enumeração rápida, na verdade, usa a aritmética de pointers na estrutura de dados subjacente e remove a maior parte da sobrecarga da chamada do método.) A otimização prematura nunca é uma boa ideia – resulta em um código mais confuso para resolver um problema que não é seu gargalo.

Ao usar -objectEnumerator , você facilmente muda para outra coleção enumerável (como um NSSet , chaves em um NSDictionary , etc.), ou até mesmo alterna para -reverseObjectEnumerator para enumerar uma matriz de trás para frente, tudo sem outras alterações de código. Se o código de iteração estiver em um método, você pode até mesmo passar qualquer NSEnumerator e o código nem precisa se preocupar com o que é iterativo. Além disso, um NSEnumerator (pelo menos aqueles fornecidos pelo código da Apple) retém a coleção que está enumerando, desde que haja mais objects, para que você não precise se preocupar com a duração de um object liberado automaticamente.

Talvez a maior coisa que um NSEnumerator (ou enumeração rápida) o proteja seja ter uma coleção mutável (matriz ou qualquer outra) que mude abaixo de você sem o seu conhecimento enquanto você o está enumerando. Se você acessar os objects por índice, você pode executar em estranhas exceções ou erros off-by (muitas vezes muito tempo depois que o problema ocorreu) que pode ser horrível para depurar. A enumeração usando um dos idiomas padrão tem um comportamento “fail-fast”, então o problema (causado por código incorreto) irá se manifestar imediatamente quando você tentar acessar o próximo object após a mutação ter ocorrido. À medida que os programas se tornam mais complexos e multiencadeados, ou até mesmo dependem de algo que um código de terceiros pode modificar, o código de enumeração frágil torna-se cada vez mais problemático. Encapsulamento e abstração FTW! 🙂


Para o OS X 10.4.xe anterior:

  int i; for (i = 0; i < [myArray count]; i++) { id myArrayElement = [myArray objectAtIndex:i]; ...do something useful with myArrayElement } 

Para OS X 10.5.x (ou iPhone) e além:

 for (id myArrayElement in myArray) { ...do something useful with myArrayElement } 

Os resultados do teste e do código-fonte estão abaixo (você pode definir o número de iterações no aplicativo). O tempo é em milissegundos e cada input é um resultado médio da execução do teste de 5 a 10 vezes. Eu achei que geralmente é preciso para 2-3 dígitos significativos e depois disso variaria com cada execução. Isso dá uma margem de erro inferior a 1%. O teste estava sendo executado em um iPhone 3G, já que essa é a plataforma alvo na qual eu estava interessado.

 numberOfItems NSArray (ms) C Array (ms) Ratio 100 0.39 0.0025 156 191 0.61 0.0028 218 3,256 12.5 0.026 481 4,789 16 0.037 432 6,794 21 0.050 420 10,919 36 0.081 444 19,731 64 0.15 427 22,030 75 0.162 463 32,758 109 0.24 454 77,969 258 0.57 453 100,000 390 0.73 534 

As classs fornecidas pelo Cocoa para manipular conjuntos de dados (NSDictionary, NSArray, NSSet, etc.) fornecem uma interface muito agradável para gerenciar informações, sem ter que se preocupar com a burocracia do gerenciamento de memory, realocação etc. Claro que isso tem um custo . Eu acho que é bastante óbvio que dizer que usar um NSArray de NSNumbers será mais lento que um Array C de floats para simples iterações, então eu decidi fazer alguns testes, e os resultados foram bem chocantes! Eu não esperava que fosse tão ruim assim. Observação: esses testes são realizados em um iPhone 3G, pois essa é a plataforma de destino em que eu estava interessado.

Neste teste eu faço uma comparação de desempenho de access random muito simples entre um C float * e NSArray de NSNumbers

Eu crio um loop simples para sumr o conteúdo de cada array e tempo-los usando mach_absolute_time (). O NSMutableArray leva em média 400 vezes mais !! (não 400%, apenas 400 vezes mais! isso é 40.000% maior!).

Cabeçalho:

// Array_Speed_TestViewController.h

// Teste de velocidade da matriz

// Criado por Mehmet Akten em 05/02/2009.

// Copyright MSA Visuals Ltd. 2009. Todos os direitos reservados.

 #import  @interface Array_Speed_TestViewController : UIViewController { int numberOfItems; // number of items in array float *cArray; // normal c array NSMutableArray *nsArray; // ns array double machTimerMillisMult; // multiplier to convert mach_absolute_time() to milliseconds IBOutlet UISlider *sliderCount; IBOutlet UILabel *labelCount; IBOutlet UILabel *labelResults; } -(IBAction) doNSArray:(id)sender; -(IBAction) doCArray:(id)sender; -(IBAction) sliderChanged:(id)sender; @end 

Implementação:

// Array_Speed_TestViewController.m

// Teste de velocidade da matriz

// Criado por Mehmet Akten em 05/02/2009.

// Copyright MSA Visuals Ltd. 2009. Todos os direitos reservados.

  #import "Array_Speed_TestViewController.h" #include  #include  @implementation Array_Speed_TestViewController // Implement viewDidLoad to do additional setup after loading the view, typically from a nib. - (void)viewDidLoad { NSLog(@"viewDidLoad"); [super viewDidLoad]; cArray = NULL; nsArray = NULL; // read initial slider value setup accordingly [self sliderChanged:sliderCount]; // get mach timer unit size and calculater millisecond factor mach_timebase_info_data_t info; mach_timebase_info(&info); machTimerMillisMult = (double)info.numer / ((double)info.denom * 1000000.0); NSLog(@"machTimerMillisMult = %f", machTimerMillisMult); } // pass in results of mach_absolute_time() // this converts to milliseconds and outputs to the label -(void)displayResult:(uint64_t)duration { double millis = duration * machTimerMillisMult; NSLog(@"displayResult: %f milliseconds", millis); NSString *str = [[NSString alloc] initWithFormat:@"%f milliseconds", millis]; [labelResults setText:str]; [str release]; } // process using NSArray -(IBAction) doNSArray:(id)sender { NSLog(@"doNSArray: %@", sender); uint64_t startTime = mach_absolute_time(); float total = 0; for(int i=0; i 

De: memo.tv

////////////////////

Disponível desde a introdução de blocos, isso permite iterar um array com blocos. Sua syntax não é tão agradável quanto uma enumeração rápida, mas há um recurso muito interessante: a enumeração concorrente. Se a ordem de enumeração não é importante e os trabalhos podem ser feitos em paralelo sem bloqueio, isso pode fornecer uma aceleração considerável em um sistema multi-core. Mais sobre isso na seção de enumeração concorrente.

 [myArray enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) { [self doSomethingWith:object]; }]; [myArray enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { [self doSomethingWith:object]; }]; 

/////////// NSFastEnumerator

A ideia por trás da enumeração rápida é usar o access rápido à matriz C para otimizar a iteração. Não só é suposto ser mais rápido que o NSEnumerator tradicional, mas o Objective-C 2.0 também fornece uma syntax muito concisa.

 id object; for (object in myArray) { [self doSomethingWith:object]; } 

/////////////////

NSEnumerator

Esta é uma forma de iteração externa: [myArray objectEnumerator] retorna um object. Este object tem um método nextObject que podemos chamar em um loop até que ele retorne nil

 NSEnumerator *enumerator = [myArray objectEnumerator]; id object; while (object = [enumerator nextObject]) { [self doSomethingWith:object]; } 

/////////////////

objectAtIndex: enumeração

Usando um loop for que aumenta um inteiro e consultando o object usando [myArray objectAtIndex: index] é a forma mais básica de enumeração.

 NSUInteger count = [myArray count]; for (NSUInteger index = 0; index < count ; index++) { [self doSomethingWith:[myArray objectAtIndex:index]]; } 

////////////// De: darkdust.net

As três formas são:

  //NSArray NSArray *arrData = @[@1,@2,@3,@4]; // 1.Classical for (int i=0; i< [arrData count]; i++){ NSLog(@"[%d]:%@",i,arrData[i]); } // 2.Fast iteration for (id element in arrData){ NSLog(@"%@",element); } // 3.Blocks [arrData enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { NSLog(@"[%lu]:%@",idx,obj); // Set stop to YES in case you want to break the iteration }]; 
  1. É o caminho mais rápido em execução, e 3. com autocomplete esquecer de escrever envelope de iteração.

Adicione each método na sua NSArray category , você vai precisar muito

Código retirado do ObjectiveSugar

 - (void)each:(void (^)(id object))block { [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { block(obj); }]; } 

Aqui está como você declara uma matriz de strings e itera sobre elas:

 NSArray *langs = @[@"es", @"en", @"pt", @"it", @"fr"]; for (int i = 0; i < [langs count]; i++) { NSString *lang = (NSString*) [langs objectAtIndex:i]; NSLog(@"%@, ",lang); } 

Faça isso :-

 for (id object in array) { // statement }