2010-11-11 10 views

Respuesta

20

Definitivamente obtendría un búfer char primero, luego iterar sobre eso.

NSString *someString = ... 

unsigned int len = [someString length]; 
char buffer[len]; 

//This way: 
strncpy(buffer, [someString UTF8String]); 

//Or this way (preferred): 

[someString getCharacters:buffer range:NSMakeRange(0, len)]; 

for(int i = 0; i < len; ++i) { 
    char current = buffer[i]; 
    //do something with current... 
} 
+15

Esta es una buena forma de hacerlo, pero vale la pena tener en cuenta que cualquier transformación inteligente de un NSString en estas líneas llegará a algunos casos extremos muy complicados con texto multibyte, y es mejor evitarlo en absoluto posible. (Y solo usar UTF-16 o UTF-32 lamentablemente no es suficiente para resolver todos los problemas del texto internacional, aunque disparará los requisitos de memoria a la luna). – Chuck

+0

@Chuck, Fair point. –

+0

¿por qué harías un búfer de char? – ma11hew28

24

Ninguno. El "Optimize Your Text Manipulations" section of the "Cocoa Performance Guidelines" in the Xcode Documentation recomienda:

Si desea iterar sobre los caracteres de una cadena, una de las cosas que no debe hacer es utilizar el método characterAtIndex: para recuperar cada personaje por separado. Este método no está diseñado para acceso repetido. En su lugar, considere buscar los caracteres todos a la vez mediante el método getCharacters:range: y iterando sobre los bytes directamente.

Si desea buscar una cadena de caracteres o subcadenas específicos, hacer no iterar a través de los caracteres uno por uno . En su lugar, utilizar un mayor nivel métodos tales como rangeOfString:, rangeOfCharacterFromSet:, o substringWithRange:, que son optimizado para la búsqueda en las NSString caracteres.

ver este Stack Overflow answer on How to remove whitespace from right end of NSString para un ejemplo de cómo hacer que rangeOfCharacterFromSet: iterar sobre los caracteres de la cadena en lugar de hacerlo usted mismo.

122

Creo que es importante que las personas entiendan cómo lidiar con Unicode, así que terminé escribiendo una respuesta monstruosa, pero en el espíritu de tl; dr Comenzaré con un fragmento que debería funcionar bien. Si desea conocer los detalles (¡lo que debería hacer!), Continúe leyendo después del fragmento.

NSUInteger len = [str length]; 
unichar buffer[len+1]; 

[str getCharacters:buffer range:NSMakeRange(0, len)]; 

NSLog(@"getCharacters:range: with unichar buffer"); 
for(int i = 0; i < len; i++) { 
    NSLog(@"%C", buffer[i]); 
} 

¿Seguirías conmigo? ¡Bueno!

La respuesta aceptada actualmente parece confundir bytes con caracteres/letras. Este es un problema común al encontrar unicode, especialmente desde un fondo C. Las cadenas en Objective-C se representan como caracteres Unicode (unichar) que son mucho más grandes que los bytes y no deben utilizarse con las funciones de manipulación de cadenas C estándar.

(Editar:!. Esta no es la historia completa Para mi vergüenza, me había olvidado por completo para dar cuenta de caracteres componibles, donde una "carta" se compone de múltiples puntos de código Unicode Esto le da una situación en la que puede tener una "letra" que se resuelve en varios unichars, que a su vez son múltiples bytes cada uno. Hoo boy. Consulte this great answer para obtener detalles sobre eso.)

La respuesta correcta a la pregunta depende de si se desea iterar sobre los caracteres/letras (a diferencia del tipo char) o la bytes de la cadena (lo que el tipo char significa en realidad) . Con el ánimo de limitar la confusión, utilizaré los términos byte y , letra a partir de ahora, evitando el término posiblemente ambiguo , carácter.

Si desea hacer lo anterior e iterar sobre las letras de la cadena, debe tratar exclusivamente con unichars (lo siento, pero estamos en el futuro ahora, ya no puede ignorarlo). Encontrar la cantidad de letras es fácil, es la propiedad de la longitud de la cadena. Un fragmento de ejemplo es como tal (igual al anterior):

NSUInteger len = [str length]; 
unichar buffer[len+1]; 

[str getCharacters:buffer range:NSMakeRange(0, len)]; 

NSLog(@"getCharacters:range: with unichar buffer"); 
for(int i = 0; i < len; i++) { 
    NSLog(@"%C", buffer[i]); 
} 

Si, por el contrario, desea iterar sobre los bytes en una cadena, empieza a ser complicado y el resultado dependerá por completo de la codificación eliges usar La opción predeterminada decente es UTF8, así que eso es lo que mostraré.

Al hacer esto, tiene que calcular cuántos bytes será la cadena UTF8 resultante, un paso donde es fácil equivocarse y usar el -length de la cadena. Una razón principal por la que esto es muy fácil de hacer, especialmente para un desarrollador estadounidense, es que una cadena con letras que caen en el espectro ASCII de 7 bits tendrá bytes iguales y longitud de letra. Esto se debe a que UTF8 codifica letras ASCII de 7 bits con un solo byte, por lo que una cadena de prueba simple y un texto en inglés básico podrían funcionar perfectamente bien.

La manera apropiada de hacer esto es utilizar el método de -lengthOfBytesUsingEncoding:NSUTF8StringEncoding (u otra codificación), asignar un búfer con esa longitud, entonces convertir la cadena en la misma codificación con -cStringUsingEncoding: y copiarlo en ese búfer. Código de ejemplo aquí:

NSUInteger byteLength = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; 
char proper_c_buffer[byteLength+1]; 
strncpy(proper_c_buffer, [str cStringUsingEncoding:NSUTF8StringEncoding], byteLength); 

NSLog(@"strncpy with proper length"); 
for(int i = 0; i < byteLength; i++) { 
    NSLog(@"%c", proper_c_buffer[i]); 
} 

Sólo para remachar el clavo en cuanto a por qué es importante mantener las cosas en orden, que mostrará código de ejemplo que se encarga de esta iteración de cuatro maneras diferentes, dos y dos mal correcta. Este es el código:

#import <Foundation/Foundation.h> 

int main() { 
    NSString *str = @"буква"; 
    NSUInteger len = [str length]; 

    // Try to store unicode letters in a char array. This will fail horribly 
    // because getCharacters:range: takes a unichar array and will probably 
    // overflow or do other terrible things. (the compiler will warn you here, 
    // but warnings get ignored) 
    char c_buffer[len+1]; 
    [str getCharacters:c_buffer range:NSMakeRange(0, len)]; 

    NSLog(@"getCharacters:range: with char buffer"); 
    for(int i = 0; i < len; i++) { 
    NSLog(@"Byte %d: %c", i, c_buffer[i]); 
    } 

    // Copy the UTF string into a char array, but use the amount of letters 
    // as the buffer size, which will truncate many non-ASCII strings. 
    strncpy(c_buffer, [str UTF8String], len); 

    NSLog(@"strncpy with UTF8String"); 
    for(int i = 0; i < len; i++) { 
    NSLog(@"Byte %d: %c", i, c_buffer[i]); 
    } 

    // Do It Right (tm) for accessing letters by making a unichar buffer with 
    // the proper letter length 
    unichar buffer[len+1]; 
    [str getCharacters:buffer range:NSMakeRange(0, len)]; 

    NSLog(@"getCharacters:range: with unichar buffer"); 
    for(int i = 0; i < len; i++) { 
    NSLog(@"Letter %d: %C", i, buffer[i]); 
    } 

    // Do It Right (tm) for accessing bytes, by using the proper 
    // encoding-handling methods 
    NSUInteger byteLength = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; 
    char proper_c_buffer[byteLength+1]; 
    const char *utf8_buffer = [str cStringUsingEncoding:NSUTF8StringEncoding]; 
    // We copy here because the documentation tells us the string can disappear 
    // under us and we should copy it. Just to be safe 
    strncpy(proper_c_buffer, utf8_buffer, byteLength); 

    NSLog(@"strncpy with proper length"); 
    for(int i = 0; i < byteLength; i++) { 
    NSLog(@"Byte %d: %c", i, proper_c_buffer[i]); 
    } 
    return 0; 
} 

La ejecución de esta salida de código de voluntad lo siguiente (con NSLog cruft recortó hacia fuera), mostrando exactamente cómo las diferentes representaciones de bytes y de la letra puede ser (las dos últimas salidas):

getCharacters:range: with char buffer 
Byte 0: 1 
Byte 1: 
Byte 2: C 
Byte 3: 
Byte 4: : 
strncpy with UTF8String 
Byte 0: Ð 
Byte 1: ± 
Byte 2: Ñ 
Byte 3: 
Byte 4: Ð 
getCharacters:range: with unichar buffer 
Letter 0: б 
Letter 1: у 
Letter 2: к 
Letter 3: в 
Letter 4: а 
strncpy with proper length 
Byte 0: Ð 
Byte 1: ± 
Byte 2: Ñ 
Byte 3: 
Byte 4: Ð 
Byte 5: º 
Byte 6: Ð 
Byte 7: ² 
Byte 8: Ð 
Byte 9: ° 
+15

¿Por qué no es esta la mejor respuesta en SO este año? ¿Por qué no tiene esto más votos al alza? ¿Por qué nadie ha construido una estatua para conmemorar a Daniel? ¿Por qué hay tanta injusticia en el mundo? – Morpheu5

+0

¡Respuesta y explicación impresionantes! Solo curiosidad sobre por qué agrega un +1 cuando hace lo siguiente: 'unichar buffer [len + 1];' – KingPolygon

+1

Para dejar espacio para el terminador nulo. :) –

2

A pesar de que técnicamente sería conseguir valores NSString individual, aquí es un enfoque alternativo:

NSRange range = NSMakeRange(0, 1); 
for (__unused int i = range.location; range.location < [starring length]; range.location++) { 
    NSLog(@"%@", [aNSString substringWithRange:range]); 
} 

(El __unused I int bit es necesario para silenciar la advertencia del compilador.)

+0

O simplemente 'for (; range.location <[starring length]; range.location ++)', no hay necesidad de '__unused int i'. – mojuba

22

Si bien la solución de Daniel probablemente funcionará la mayor parte del tiempo, creo que la solución depende del contexto. Por ejemplo, tengo una aplicación de ortografía y necesito repetir sobre cada carácter tal como aparece en pantalla, que puede no corresponderse con la forma en que se representa en la memoria. Esto es especialmente cierto para el texto proporcionado por el usuario.

usando algo como esta categoría en NSString:

- (void) dumpChars 
{ 
    NSMutableArray *chars = [NSMutableArray array]; 
    NSUInteger  len = [self length]; 
    unichar   buffer[len+1]; 

    [self getCharacters: buffer range: NSMakeRange(0, len)]; 
    for (int i=0; i<len; i++) { 
     [chars addObject: [NSString stringWithFormat: @"%C", buffer[i]]]; 
    } 

    NSLog(@"%@ = %@", self, [chars componentsJoinedByString: @", "]); 
} 

y la alimentación que una palabra como mañana podría producir:

mañana = m, a, ñ, a, n, a 

Pero podría fácilmente producir:

mañana = m, a, n, ̃, a, n, a 

La primera se producirá si la cadena está en formato Unicode precompuesto y la última si está en deco forma mposed.

Puede pensar que esto podría evitarse utilizando el resultado de precomposedStringWithCanonicalMapping o precomposedStringWithCompatibilityMapping de NSString, pero este no es necesariamente el caso, ya que Apple advierte en Technical Q&A 1225. Por ejemplo, una cadena como e̊gâds (que inventé por completo) sigue produciendo lo siguiente incluso después de convertir a una forma precompuesta.

e̊gâds = e, ̊, g, â, d, s 

La solución para mí es utilizar NSStringEnumerationByComposedCharacterSequences que pasan enumerateSubstringsInRange de NSString como la opción de enumeración. Reescribir el ejemplo anterior se parezca a esto:

- (void) dumpSequences 
{ 
    NSMutableArray *chars = [NSMutableArray array]; 

    [self enumerateSubstringsInRange: NSMakeRange(0, [self length]) options: NSStringEnumerationByComposedCharacterSequences 
     usingBlock: ^(NSString *inSubstring, NSRange inSubstringRange, NSRange inEnclosingRange, BOOL *outStop) { 
     [chars addObject: inSubstring]; 
    }]; 

    NSLog(@"%@ = %@", self, [chars componentsJoinedByString: @", "]); 
} 

Si alimentamos esta versión e̊gâds A continuación, obtener

e̊gâds = e̊, g, â, d, s 

como era de esperar, que es lo que quiero.

La sección de documentación en Characters and Grapheme Clusters también puede ser útil para explicar algo de esto.

Nota: Parece que algunas de las cadenas Unicode que utilicé están tropezando SO al formatearlas como código. Las cadenas que utilicé son mañana y e̊gâds.

+1

Oh cariño, me olvidé por completo de los personajes compostables. Añadí una nota y una referencia a tu respuesta en la mía. Creo que la lección aquí es: el texto es difícil, tíos. –

1

intento cadena de enumeración con bloques

Crear categoría de NSString

.h

@interface NSString (Category) 

- (void)enumerateCharactersUsingBlock:(void (^)(NSString *character, NSInteger idx, bool *stop))block; 

@end 

.m

@implementation NSString (Category) 

- (void)enumerateCharactersUsingBlock:(void (^)(NSString *character, NSInteger idx, bool *stop))block 
{ 
    bool _stop = NO; 
    for(NSInteger i = 0; i < [self length] && !_stop; i++) 
    { 
     NSString *character = [self substringWithRange:NSMakeRange(i, 1)]; 
     block(character, i, &_stop); 
    } 
} 
@end 

ejemplo

NSString *string = @"Hello World"; 
[string enumerateCharactersUsingBlock:^(NSString *character, NSInteger idx, bool *stop) { 
     NSLog(@"char %@, i: %li",character, (long)idx); 
}]; 
0

No use

NSUInteger len = [str length]; 
unichar buffer[len+1]; 

se debe utilizar la asignación de memoria

NSUInteger len = [str length]; 
unichar* buffer = (unichar*) malloc (len+1)*sizeof(unichar); 

y en el uso final

free(buffer); 

con el fin de evitar problemas de memoria.

Cuestiones relacionadas