Como iterar em um NSArray?

Respostas:

667

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

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

Essa construção é usada para enumerar objetos em uma coleção em conformidade com o NSFastEnumerationprotocolo. Essa abordagem tem uma vantagem de velocidade porque armazena ponteiros para vários objetos (obtidos através de uma única chamada de método) em um buffer e itera através deles, avançando pelo buffer usando a aritmética do ponteiro. Isso é muito mais rápido do que ligar -objectAtIndex:todas as vezes no loop.

Também é importante 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 NSEnumeratorimplementação padrão de -countByEnumeratingWithState:objects:count:coloca apenas um objeto no buffer em cada chamada.

Eu relatei isso em radar://6296108(A enumeração rápida de NSEnumerators é lenta), mas foi retornada como Não deve ser corrigido. O motivo é que a enumeração rápida busca previamente um grupo de objetos e se você deseja enumerar apenas um determinado ponto do enumerador (por exemplo, até que um objeto específico seja encontrado ou a condição seja atendida) e use o mesmo enumerador após a quebra do loop, muitas vezes seria o caso de vários objetos serem ignorados.

Se você estiver codificando para o OS X 10.6 / iOS 4.0 e superior, também terá a opção de usar APIs baseadas em bloco para enumerar matrizes e outras coleções:

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

Você também pode usar -enumerateObjectsWithOptions:usingBlock:e passar NSEnumerationConcurrente / ou NSEnumerationReversecomo argumento de opções.


10.4 ou anterior

O idioma padrão para pré-10.5 é usar um NSEnumeratorloop while, como:

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 matriz é inflexível e o suposto aumento de velocidade do uso -objectAtIndex:é insignificante para a melhoria com a enumeração rápida em 10.5 ou superior. (A enumeração rápida realmente usa aritmética de ponteiro na estrutura de dados subjacente e remove a maior parte da sobrecarga de chamada do método.) Otimização prematura nunca é uma boa idéia - resulta em código mais confuso para resolver um problema que não é seu gargalo.

Ao usar -objectEnumerator, você muda muito facilmente para outra coleção enumerável (como NSSet, chaves em um NSDictionaryetc.) ou muda -reverseObjectEnumeratorpara 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 algum NSEnumeratore o código nem precisa se preocupar com o que está iterando. 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 objetos, para que você não precise se preocupar por quanto tempo um objeto liberado automaticamente existirá.

Talvez a maior coisa que uma NSEnumerator(ou enumeração rápida) proteja você seja ter uma coleção mutável (matriz ou outra) alterada abaixo de você sem o seu conhecimento enquanto você a enumera. Se você acessar os objetos por índice, poderá encontrar estranhas exceções ou erros pontuais (geralmente muito tempo após o problema) que podem ser horríveis para depuração. A enumeração usando um dos idiomas padrão possui um comportamento "fail-fast"; portanto, o problema (causado por código incorreto) se manifestará imediatamente quando você tentar acessar o próximo objeto após a mutação. À medida que os programas ficam mais complexos e com vários threads, ou até mesmo dependem de algo que o código de terceiros pode modificar, o código de enumeração frágil se torna cada vez mais problemático. Encapsulamento e abstração FTW! :-)


Quinn Taylor
fonte
28
Nota: a maioria dos compiladores emitirá um aviso sobre "while (object = [e nextObject])". Nesse caso, você realmente pretende usar = em vez de ==. Para suprimir os avisos, você pode adicionar parênteses extras: "while ((object = [e nextObject]))".
14119 Adam Rosenfield
Outra coisa a lembrar com o NSEnumerator é que ele usará mais memória (faz uma cópia da matriz) do que a abordagem objectWithIndex.
diederikh
@QuinnTaylor Usando for (id object in array), existe uma maneira de determinar também o índice atual de objetos na matriz ou um contador separado precisa ser incluído?
Coderama 6/03/12
1
Você precisaria de um contador separado, mas eu sugiro considerar a enumeração baseada em bloco (que inclui o índice) se estiver disponível para você.
Quinn Taylor
Eu sou estranho, mas eu uso um forloop como este:for(;;) { id object = [ e nextObject ] ; if ( !e ) { break ; } ... your loop operation ... }
nielsbot
125

Para 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
}
morrerikh
fonte
2
No primeiro exemplo, você nem precisa declarar o int fora do loop. Isso funciona tão bem e define a variável de maneira adequada, para que você possa reutilizá-la mais tarde, se necessário: for (int i = 0; i <[contagem de myArray]; i ++) ... Mas também esteja ciente de que chamar -count sempre através da matriz pode cancelar o benefício de usar -objectAtIndex:
Quinn Taylor
1
E, na realidade, se você estiver enumerando uma coleção mutável, é tecnicamente mais correto verificar a contagem todas as vezes através do loop. Esclarei minha resposta para explicar que o uso do NSEnumerator ou NSFastEnumeration pode proteger contra mutações simultâneas da matriz.
Quinn Taylor
O contrato for (int i = 0; ...) é um dialeto em linguagem C (acredita-se C99), que eu mesmo uso, mas não tinha certeza de que era o padrão XCode.
diederikh
C99 é o padrão no Xcode 3.1.x - em algum momento futuro, o padrão mudará para GNU99, que (entre outras coisas) suporta uniões e estruturas anônimas. Isso deve ser bom ...
Quinn Taylor
2
O uso for (NSUInteger i = 0, count = [myArray count]; i < count; i++)é provavelmente o mais eficiente e conciso que você terá para essa abordagem.
Quinn Taylor
17

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 entrada é um resultado médio da execução do teste 5 a 10 vezes. Descobri que geralmente é preciso ter de 2 a 3 dígitos significativos e depois variaria a cada execução. Isso dá uma margem de erro inferior a 1%. O teste estava sendo executado em um iPhone 3G, pois essa é a plataforma de destino em que 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 classes fornecidas pelo Cocoa para lidar com 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 de gerenciamento de memória, realocação etc. É claro que isso tem um custo . Eu acho que é bastante óbvio dizer que o uso de um NSArray de NSNumbers será mais lento que um C Array de flutuadores para iterações simples, então decidi fazer alguns testes e os resultados foram bastante chocantes! Eu não esperava que fosse tão ruim assim. Nota: esses testes são realizados em um iPhone 3G, pois essa é a plataforma de destino em que eu estava interessado.

Neste teste, faço uma comparação de desempenho de acesso aleatório muito simples entre um float C * e o NSArray of NSNumbers

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

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 <UIKit/UIKit.h>

@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 <mach/mach.h>
    #include <mach/mach_time.h>

 @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<numberOfItems; i++) {

        total += [[nsArray objectAtIndex:i] floatValue];

    }

    [self displayResult:mach_absolute_time() - startTime];

}




// process using C Array

-(IBAction) doCArray:(id)sender {

    NSLog(@"doCArray: %@", sender);


    uint64_t start = mach_absolute_time();

    float total = 0;

    for(int i=0; i<numberOfItems; i++) {

        total += cArray[i];

    }

    [self displayResult:mach_absolute_time() - start];

}



// allocate NSArray and C Array 

-(void) allocateArrays {

    NSLog(@"allocateArrays");


    // allocate c array

    if(cArray) delete cArray;

    cArray = new float[numberOfItems];


    // allocate NSArray

    [nsArray release];

    nsArray = [[NSMutableArray alloc] initWithCapacity:numberOfItems];



    // fill with random values

    for(int i=0; i<numberOfItems; i++) {

        // add number to c array

        cArray[i] = random() * 1.0f/(RAND_MAX+1);


        // add number to NSArray

        NSNumber *number = [[NSNumber alloc] initWithFloat:cArray[i]];

        [nsArray addObject:number];

        [number release];

    }


}



// callback for when slider is changed

-(IBAction) sliderChanged:(id)sender {

    numberOfItems = sliderCount.value;

    NSLog(@"sliderChanged: %@, %i", sender, numberOfItems);


    NSString *str = [[NSString alloc] initWithFormat:@"%i items", numberOfItems];

    [labelCount setText:str];

    [str release];


    [self allocateArrays];

}



//cleanup

- (void)dealloc {

    [nsArray release];

    if(cArray) delete cArray;


    [super dealloc];

}


@end

De: memo.tv

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

Disponível desde a introdução dos blocos, isso permite iterar uma matriz com blocos. Sua sintaxe não é tão boa quanto a enumeração rápida, mas há um recurso muito interessante: enumeração simultânea. Se a ordem de enumeração não for importante e os trabalhos puderem ser feitos em paralelo sem travar, isso poderá fornecer uma aceleração considerável em um sistema com vários núcleos. Mais sobre isso na seção de enumeração simultânea.

[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 idéia por trás da enumeração rápida é usar o acesso rápido à matriz C para otimizar a iteração. Não é apenas mais rápido que o NSEnumerator tradicional, mas o Objective-C 2.0 também fornece uma sintaxe muito concisa.

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

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

NSEnumerator

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

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

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

objectAtIndex: enumeração

Usar um loop for que aumenta um número inteiro e consultar o objeto 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

Hitendra Solanki
fonte
12

As três maneiras 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. É a maneira mais rápida de executar e 3. com o preenchimento automático, esqueça de escrever o envelope da iteração.
Javier Calatrava Llavería
fonte
7

Adicione eachmétodo no seu NSArray category, você precisará muito

Código extraído do ObjectiveSugar

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

Aqui está como você declara uma matriz de seqüências de caracteres 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);
}
oabarca
fonte
0

Para Swift

let arrayNumbers = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

// 1
for (index, value) in arrayNumbers.enumerated() {
    print(index, value)
    //... do somthing with array value and index
}


//2
for value in arrayNumbers {
    print(value)
    //... do somthing with array value
}
Nilesh R Patel
fonte
-1

Faça isso :-

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

fonte