Какой лучший способ перебрать все символы в NSString? Хотелось бы перебрать длину строки и использовать метод.
[aNSString characterAtIndex:index];
или вы хотите использовать буфер char на основе NSString?
Какой лучший способ перебрать все символы в NSString? Хотелось бы перебрать длину строки и использовать метод.
[aNSString characterAtIndex:index];
или вы хотите использовать буфер char на основе NSString?
Я бы выбрал сначала буфер char, а затем перебираю его.
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...
}
Я думаю, что важно, чтобы люди понимали, как бороться с unicode, поэтому я закончил писать ответ монстра, но в духе tl; dr я начну с фрагмента, который должен работать нормально, Если вы хотите узнать подробности (что вам нужно!), Продолжайте читать после фрагмента.
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]);
}
Еще со мной? Хорошо!
Текущий принятый ответ, похоже, путает байты с символами/буквами. Это обычная проблема при встрече с unicode, особенно на фоне C. Строки в Objective-C представлены как символы Unicode (unichar
), которые намного больше, чем байты, и не должны использоваться со стандартными функциями управления строкой C.
(Изменить): Это не полная история! К моему большому стыду, я полностью забыл учитывать составные символы, где "письмо" состоит из нескольких кодовых страниц юникода. дает вам ситуацию, когда у вас может быть одно "письмо", разрешающее несколько unichars, которые, в свою очередь, имеют несколько байтов каждый. Hoo boy. Пожалуйста, обратитесь к этому отличному ответу подробности об этом.)
Правильный ответ на вопрос зависит от того, хотите ли вы перебирать символы/буквы (в отличие от типа char
) или байты строки (что на самом деле означает тип char
). В духе ограничения путаницы я буду использовать термины byte и letter с этого момента, избегая, возможно, двусмысленного символа.
Если вы хотите сделать первое и перебрать буквы в строке, вам нужно иметь дело исключительно с unichars (извините, но сейчас мы в будущем, вы больше не можете его игнорировать). Найти количество букв легко, это свойство длины строки. Примерный фрагмент как таковой (такой же, как и выше):
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]);
}
Если, с другой стороны, вы хотите перебирать байты в строке, он начинает усложняться, и результат будет зависеть исключительно от используемой вами кодировки. Достойным выбором по умолчанию является UTF8, так что я покажу.
Выполняя это, вы должны выяснить, сколько байтов будет полученной в результате UTF8-строкой, шаг, где легко ошибиться и использовать строку -length
. Одна из основных причин, почему это очень легко сделать, особенно для американского разработчика, состоит в том, что строка с буквами, попадающими в 7-битный спектр ASCII, будет иметь равные байты и длины букв. Это связано с тем, что UTF8 кодирует 7-битные буквы ASCII с одним байтом, поэтому простая тестовая строка и основной текст на английском могут работать отлично.
Правильный способ сделать это - использовать метод -lengthOfBytesUsingEncoding:NSUTF8StringEncoding
(или другое кодирование), выделить буфер с этой длиной, затем преобразовать строку в ту же кодировку с помощью -cStringUsingEncoding:
и скопировать ее в этот буфер. Пример кода здесь:
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]);
}
Просто чтобы понять, почему важно держать все в порядке, я покажу пример кода, который обрабатывает эту итерацию четырьмя различными способами, двумя неправильными и двумя правильными. Это код:
#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;
}
Запуск этого кода приведет к выходу следующего (с отключенным NSLog cruft), показывающим точно, КАК разные байтовые и буквенные представления могут быть (два последних выхода):
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: °
Ни. В разделе "Оптимизация текстовых манипуляций" в разделе "Рекомендации по производительности Cocoa" в документации Xcode рекомендуется:
Если вы хотите перебрать символы строки, одна из вещи, которые вы не должны делать, это использовать
characterAtIndex:
метод для извлечения каждый символ отдельно. Этот метод не предназначен для повторного доступа. Вместо этого рассмотрите выборку персонажей одновременно, используяgetCharacters:range:
и итерация по байтам напрямую.Если вы хотите найти строку для конкретные символы или подстроки, do не перебирать символы одним. Вместо этого используйте более высокий уровень методы, такие как
rangeOfString:
,rangeOfCharacterFromSet:
, илиsubstringWithRange:
, которые оптимизированный для поискаNSString
символы.
См. этот ответ о том, как удалить пробел с правого конца NSString
для примера того, как позволить rangeOfCharacterFromSet:
итерации по символам вместо того, чтобы делать это самостоятельно.
Хотя решение Daniel, вероятно, будет работать большую часть времени, я думаю, что решение зависит от контекста. Например, у меня есть приложение для орфографии и вам нужно перебирать каждый символ, поскольку он появляется на экране, что может не соответствовать тому, как оно представлено в памяти. Это особенно верно для текста, предоставленного пользователем.
Используя что-то вроде этой категории в 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: @", "]);
}
И подавая ему слово, как манана, может произвести:
mañana = m, a, ñ, a, n, a
Но это так же легко произвести:
mañana = m, a, n, ̃, a, n, a
Первый будет создан, если строка находится в предварительно объединенной форме юникода, а позже, если она в разложенной форме.
Вы могли бы подумать, что этого можно избежать, используя результат NSString precomposedStringWithCanonicalMapping или precomposedStringWithCompatibilityMapping, но это не обязательно так, как Apple предупреждает в Technical Q & A 1225. Например, строка, подобная e̊gâds
(которую я полностью заполнил), все еще производит следующее даже после преобразования в предварительно сложенную форму.
e̊gâds = e, ̊, g, â, d, s
Решением для меня является использование NSString enumerateSubstringsInRange, передающего NSStringEnumerationByComposedCharacterSequences в качестве опции перечисления. Переписывая предыдущий пример, чтобы выглядеть так:
- (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: @", "]);
}
Если мы будем кормить эту версию e̊gâds
, то получим
e̊gâds = e̊, g, â, d, s
как и ожидалось, это то, что я хочу.
Раздел документации Персонажи и кластеры Графема также могут быть полезны для объяснения некоторых из этого.
Примечание. Похоже, что некоторые строки юникода, которые я использовал, отключаются, когда форматируются как код. Струны, которые я использовал, - манана, и e̊gâds.
Хотя вы бы технически получали индивидуальные значения NSString, вот альтернативный подход:
NSRange range = NSMakeRange(0, 1);
for (__unused int i = range.location; range.location < [starring length]; range.location++) {
NSLog(@"%@", [aNSString substringWithRange:range]);
}
(бит __ неиспользуемый int i необходим, чтобы отключить предупреждение компилятора.)
попробуйте перечислить строку с блоками
Создать категорию 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
Пример
NSString *string = @"Hello World";
[string enumerateCharactersUsingBlock:^(NSString *character, NSInteger idx, bool *stop) {
NSLog(@"char %@, i: %li",character, (long)idx);
}];
Вы не должны использовать
NSUInteger len = [str length];
unichar buffer[len+1];
вам следует использовать выделение памяти
NSUInteger len = [str length];
unichar* buffer = (unichar*) malloc (len+1)*sizeof(unichar);
и в конце использовать
free(buffer);
чтобы избежать проблем с памятью.
Это немного другое решение вопроса, но я подумал, может быть, это кому-нибудь пригодится. То, что я хотел, было на самом деле повторять как фактический символ Unicode в NSString. Итак, я нашел это решение:
NSString * str = @"hello 🤠💩";
NSRange range = NSMakeRange(0, str.length);
[str enumerateSubstringsInRange:range
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange,
NSRange enclosingRange, BOOL *stop)
{
NSLog(@"%@", substring);
}];