2012-04-17 13 views
5

¿Cómo puedo enumerar NSString sacando cada unichar de él? Puedo usar characterAtIndex, pero eso es más lento que hacerlo mediante un unichar incremental *. No vi nada en la documentación de Apple que no requiriera copiar la cadena en un segundo buffer.Enumerar caracteres NSString mediante el puntero

Algo como esto sería ideal:

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

o

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

Si está tan preocupado por el rendimiento, sería mejor usar NSData y acceder a la matriz de bytes de eso. – joerick

+0

Resulta que CFString en realidad tiene una forma de hacerlo, en CFStringGetCharactersPtr ... –

+2

"... pero va a ser más lento que ...": esto se llama ** optimización prematura **. Está haciendo suposiciones sobre el rendimiento incluso antes de saber si el rendimiento va a ser un problema. Debe implementarlo de la manera más obvia (utilizando 'characterAtIndex') y optimizarlo solo si tiene problemas de rendimiento. – Sulthan

Respuesta

11

Puede acelerar -characterAtIndex: mediante la conversión a su primer IMP formulario:

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: Parece ser que el CFString Reference contiene el siguiente método:

const UniChar *CFStringGetCharactersPtr(CFStringRef theString); 

Esto significa que puede hacer lo siguiente:

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

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

Si no desea asignar m Emory para hacer frente al buffer, este es el camino a seguir.

+0

Bien encontrado, pero desde la sección Valor devuelto: "Un puntero a un búfer de carácter Unicode, o NULL si el almacenamiento interno de theString no permite que esto se devuelva de manera eficiente". Esto sería más rápido, pero aún necesita una copia de seguridad por si acaso. – ughoavgfhw

+0

Brillante, no pensé en usar CF ... API, pero esa fue una gran idea. Funciona magníficamente. – jjxtra

+0

@ughoavgfhw cierto, muy cierto, necesita una copia de seguridad. Pero para lo que OP quería, esto debería funcionar bien. –

0

esto funcionará:

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

[Editar] Y si realmente necesita caracteres Unicode entonces usted tiene no hay otra opción que usar longitud y characterAtIndex. De la documentación:

La clase NSString tiene dos métodos primitivos-length y characterAtIndex: -que proporcionan la base para todos los otros métodos en su interfaz. El método de longitud devuelve la cantidad total de caracteres Unicode en la cadena. characterAtIndex: da acceso a cada carácter de la cadena mediante un índice, con valores de índice a partir de las 0.

lo que el código sería:

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

[editar 2]

Además, Don No olvides que NSString tiene un 'puente libre' para CFString y, por lo tanto, todas las funciones de interfaz C-code directas que no sean Objective-C son utilizables. El relevante sería CFStringGetCharacterAtIndex

+0

Eso solo funciona para puntos de código Unicode menores que 128. Tan pronto como encuentre un carácter de bit alto, se romperá. Además, es muy probable que esté creando una segunda copia de los datos, que el asker estaba tratando de evitar. – grahamparks

+0

Supongo que esto requiere copiar los bytes utf-8 de alguna manera? ¿Dónde vive ese puntero? ¿Está NSString utf-8 debajo? – jjxtra

+0

Se crea la cadena C Documentación para UTF8String: _La cadena C devuelta se libera automáticamente al igual que se liberaría un objeto devuelto; debe copiar la cadena C si necesita almacenarla fuera del contexto de liberación automática en el que se crea la cadena C._ – GoZoner

0

No creo que pueda hacer esto. NSString es una interfaz abstracta para una multitud de clases que no garantizan el almacenamiento interno de los datos de caracteres, por lo que es muy posible que no haya una matriz de caracteres para obtener un puntero.

Si ninguna de las opciones mencionadas en su pregunta es adecuada para su aplicación, le recomiendo que cree su propia clase de cadena para este fin, o que use matrices unichar en malloc en lugar de objetos de cadena.

4

Su única opción es copiar los caracteres en un nuevo búfer. Esto se debe a que la clase NSString no garantiza que haya un búfer interno que pueda usar. La mejor manera de hacerlo es usar el método getCharacters:range:.

Si está utilizando potencialmente cadenas muy largas, sería mejor asignar un búfer de tamaño fijo y enumerar la cadena en trozos (esto es en realidad cómo funciona la enumeración rápida).

+0

Hmmm. Me pregunto si characterAtIndex es más rápido dado que no tiene que copiar la memoria ... ¿pensamientos? – jjxtra

+3

Es posible, pero poco probable. La sobrecarga de llamar a un método para cada personaje pasará rápidamente la sobrecarga de escribir en la memoria a medida que aumenta el tamaño del búfer. A menos, por supuesto, que esté utilizando una clase NSString personalizada que no proporcione un método optimizado 'getCharacters: range:'. – ughoavgfhw

+0

@PsychoDad Creo que el uso de '-characterAtIndex:' * podría * ser más rápido si omitiera la sobrecarga del tiempo de ejecución objc y simplemente usara una función C. –

1

Creé un método de enumeración de estilo de bloque que usa getCharacters:range: con un búfer de tamaño fijo, según la sugerencia de ughoavgfhw en su respuesta. Evita la situación en la que CFStringGetCharactersPtr devuelve nulo y no tiene que malloc un búfer grande. Puede colocarlo en una categoría NSString, o modificarlo para tomar una cadena como parámetro si lo desea.

-(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

Esto funciona, pero no será tan rápido como la iteración del puntero sin procesar – jjxtra

+0

Cierto, pero como he dicho, maneja el caso donde CFStringGetCharactersPtr devuelve nulo. – Aaron

Cuestiones relacionadas