2012-04-17 15 views
5

Come è possibile enumerare NSString estraendo ciascun unichar da esso? Posso usare characterAtIndex ma è più lento di farlo con un unichar * incrementale. Non ho visto nulla nella documentazione di Apple che non richiedesse di copiare la stringa in un secondo buffer.Enumerazione caratteri NSString tramite puntatore

Qualcosa di simile sarebbe l'ideale:

for (unichar c in string) { ... } 

o

unichar* ptr = (unichar*)string; 
+0

Se sei così preoccupato per le prestazioni, sarebbe meglio utilizzare NSData e accedere alla matrice di byte di quello. – joerick

+0

Si scopre che CFString in realtà ha un modo per farlo, in CFStringGetCharactersPtr ... –

+2

"... ma che sarà più lento di ..." - questo si chiama ** ottimizzazione prematura **. Stai facendo delle ipotesi sulle prestazioni ancor prima di sapere se le prestazioni saranno un problema. Dovresti implementarlo in modo ovvio (usando 'characterAtIndex') e ottimizzarlo solo se hai problemi di prestazioni. – Sulthan

risposta

11

È possibile accelerare -characterAtIndex: convertendolo in sua forma IMP prima:

NSString *str = @"This is a test"; 

NSUInteger len = [str length]; // only calling [str length] once speeds up the process as well 
SEL sel = @selector(characterAtIndex:); 

// using typeof to save my fingers from typing more 
unichar (*charAtIdx)(id, SEL, NSUInteger) = (typeof(charAtIdx)) [str methodForSelector:sel]; 

for (int i = 0; i < len; i++) { 
    unichar c = charAtIdx(str, sel, i); 
    // do something with C 
    NSLog(@"%C", c); 
} 

EDIT: Sembra che il CFString Reference contiene il seguente metodo:

const UniChar *CFStringGetCharactersPtr(CFStringRef theString); 

Questo significa che è possibile effettuare le seguente:

const unichar *chars = CFStringGetCharactersPtr((__bridge CFStringRef) theString); 

while (*chars) 
{ 
    // do something with *chars 
    chars++; 
} 

Se non si desidera allocare m Emory per far fronte al buffer, questa è la strada da percorrere.

+0

Buona ricerca, ma dalla sezione Valore restituito: "Puntatore a un buffer di carattere Unicode o NULL se la memoria interna di theString non consente di restituirlo in modo efficiente". Questo sarebbe più veloce, ma ha ancora bisogno di un backup per ogni evenienza. – ughoavgfhw

+0

Brillante, non ho pensato di usare CF ... API, ma è stata una grande idea. Funziona superbamente. – jjxtra

+0

@ughoavgfhw vero, molto vero, ha bisogno di un backup. Ma per quello che l'OP voleva, dovrebbe funzionare bene. –

0

questo funzionerà:

char *s = [string UTF8String]; 
for (char *t = s; *t; t++) 
    /* use as */ *t; 

[Edit] E se si ha realmente bisogno di caratteri Unicode poi si deve nessuna opzione se non quella di utilizzare la lunghezza e carattereAtIndex. Dalla documentazione:

La classe NSString ha due metodi primitivi: lunghezza e carattereAtIndex: -che forniscono la base per tutti gli altri metodi nella sua interfaccia. Il metodo length restituisce il numero totale di caratteri Unicode nella stringa. characterAtIndex: dà accesso ad ogni carattere della stringa per indice, con valori di indice cominciando a 0.

Così il vostro codice sarebbe:

for (int index = 0; index < string.length; index++) 
    { 
     unichar c = [string characterAtIndex: index]; 
     /* ... */ 
    } 

[modifica 2]

Inoltre, don Dimentichiamo che NSString è un "ponte senza pedaggio" per CFString e quindi tutte le funzioni di interfaccia C-code non-Objective-C sono utilizzabili. Quello pertinente sarebbe CFStringGetCharacterAtIndex

+0

Questo funziona solo per i punti codice unicode inferiori a 128. Non appena si incontra un carattere a bit elevato, si romperà. Inoltre, è molto probabile che crei una seconda copia dei dati, che il richiedente stava cercando di evitare. – grahamparks

+0

Presumo che ciò richiede in qualche modo la copia di utf-8 byte? Dove vive quel puntatore? Sotto c'è NSString utf-8? – jjxtra

+0

La stringa C viene creata. Documentazione per UTF8String: _La stringa C restituita viene automaticamente liberata proprio come sarebbe stato rilasciato un oggetto restituito; è necessario copiare la stringa C se è necessario memorizzarla al di fuori del contesto di autorelease in cui viene creata la stringa C. – GoZoner

0

Non penso che tu possa farlo. NSString è un'interfaccia astratta a una moltitudine di classi che non fornisce alcuna garanzia sulla memorizzazione interna dei dati dei caratteri, quindi è del tutto possibile che non ci sia alcun array di caratteri a cui puntare.

Se nessuna delle opzioni menzionate nella tua domanda è adatta per la tua app, ti consiglio di creare la tua classe di stringhe per questo scopo, o di usare matrici unichar raw mallate invece di oggetti stringa.

4

L'unica opzione è copiare i caratteri in un nuovo buffer. Questo perché la classe NSString non garantisce che sia disponibile un buffer interno. Il modo migliore per farlo è utilizzare il metodo getCharacters:range:.

Se si utilizza potenzialmente stringhe molto lunghe, sarebbe meglio allocare un buffer di dimensioni fisse ed enumerare la stringa in blocchi (questo è in realtà come l'enumerazione veloce funziona).

+0

Hmmm. Mi chiedo se characterAtIndex sia più veloce dato che non deve copiare la memoria ... pensieri? – jjxtra

+3

È possibile, ma improbabile. L'overhead di chiamare un metodo per ogni carattere passerà rapidamente il sovraccarico di scrittura in memoria all'aumentare della dimensione del buffer. A meno che non si stia utilizzando una classe NSString personalizzata che non fornisce un metodo ottimizzato 'getCharacters: range:'. – ughoavgfhw

+0

@PsychoDad Penso che usando '-characterAtIndex:' * potrebbe * essere più veloce, se si scavalca il sovraccarico del runtime objc e si usa semplicemente una funzione C. –

1

Ho creato un metodo di enumerazione a blocchi che utilizza getCharacters:range: con un buffer di dimensioni fisse, come suggerito da ughoavgfhw nella sua risposta. Evita la situazione in cui CFStringGetCharactersPtr restituisce null e non ha malloc un buffer di grandi dimensioni. Puoi rilasciarlo in una categoria NSString o modificarlo per prendere una stringa come parametro, se lo desideri.

-(void)enumerateCharactersWithBlock:(void (^)(unichar, NSUInteger, BOOL *))block 
{ 
    const NSInteger bufferSize = 16; 
    const NSInteger length = [self length]; 
    unichar buffer[bufferSize]; 
    NSInteger bufferLoops = (length - 1)/bufferSize + 1; 
    BOOL stop = NO; 
    for (int i = 0; i < bufferLoops; i++) { 
     NSInteger bufferOffset = i * bufferSize; 
     NSInteger charsInBuffer = MIN(length - bufferOffset, bufferSize); 
     [self getCharacters:buffer range:NSMakeRange(bufferOffset, charsInBuffer)]; 
     for (int j = 0; j < charsInBuffer; j++) { 
      block(buffer[j], j + bufferOffset, &stop); 
      if (stop) { 
       return; 
      } 
     } 
    } 
} 
+0

Funziona, ma non sarà veloce come l'iterazione puntatore raw – jjxtra

+0

Vero, ma come ho detto, questo gestisce il caso in cui CFStringGetCharactersPtr restituisce null. – Aaron