迭代NSString中所有字符的最佳方法是什么?是否要循环遍历字符串的长度并使用该方法.
[aNSString characterAtIndex:index];
Run Code Online (Sandbox Code Playgroud)
或者你想根据NSString使用char缓冲区?
Dan*_*uce 138
我认为人们了解如何处理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]);
}
Run Code Online (Sandbox Code Playgroud)
还在我这儿?好!
当前接受的答案似乎是用字符/字母混淆字节.遇到unicode时这是一个常见问题,尤其是来自C背景.Objective-C中的字符串表示为unicode字符(unichar
),它们比字节大得多,不应与标准C字符串操作函数一起使用.
(编辑:这不是完整的故事!令我非常遗憾的是,我完全忘了考虑可组合字符,其中"字母"由多个unicode代码点组成.这给你一个你可以拥有一个"字母"解析为多个unichars,而每个unichars又是多个字节.Hoo boy.请参阅这个很棒的答案."
问题的正确答案取决于您是要迭代字符/字母(与类型不同char
)还是字符串的字节(类型char
实际意味着什么).本着限制混淆的精神,我将从现在开始使用术语字节和字母,避免可能存在歧义的术语字符.
如果你想做前者并迭代字符串中的字母,你需要专门处理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]);
}
Run Code Online (Sandbox Code Playgroud)
另一方面,如果要迭代字符串中的字节,它会开始变得复杂,结果将完全取决于您选择使用的编码.合适的默认选择是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]);
}
Run Code Online (Sandbox Code Playgroud)
为了说明为什么保持正确的重要性,我将展示以四种不同方式处理此迭代的示例代码,两个错误,两个正确.这是代码:
#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;
}
Run Code Online (Sandbox Code Playgroud)
运行此代码将输出以下内容(修剪出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: °
Run Code Online (Sandbox Code Playgroud)
Cas*_*ser 28
虽然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: @", "]);
}
Run Code Online (Sandbox Code Playgroud)
像喂mañana这样的话可能产生:
mañana = m, a, ñ, a, n, a
Run Code Online (Sandbox Code Playgroud)
但它可以很容易地产生:
mañana = m, a, n, ?, a, n, a
Run Code Online (Sandbox Code Playgroud)
如果字符串是预组合的unicode形式,则生成前者,如果是分解形式,则生成前者.
您可能认为可以通过使用NSString的precomposedStringWithCanonicalMapping或precomposedStringWithCompatibilityMapping的结果来避免这种情况,但不一定是苹果在技术问答1225中警告的情况.例如e?gâds
,即使在转换为预合成形式之后,像我(我完全组成)的字符串仍然会产生以下内容.
e?gâds = e, ?, g, â, d, s
Run Code Online (Sandbox Code Playgroud)
我的解决方案是使用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: @", "]);
}
Run Code Online (Sandbox Code Playgroud)
如果我们提供这个版本,e?gâds
那么我们得到
e?gâds = e?, g, â, d, s
Run Code Online (Sandbox Code Playgroud)
正如所料,这就是我想要的.
关于字符和字母集群的文档部分也可能有助于解释其中的一些内容.
注意:看起来我使用的一些unicode字符串在格式化为代码时会跳闸.我使用的字符串是mañana和e̊gâds.
ma1*_*w28 25
都不是.Xcode文档中"Cocoa性能指南"的"优化文本操作"部分建议:
如果要迭代字符串的字符,则不应该执行的操作之一是使用该
characterAtIndex:
方法分别检索每个字符.此方法不适用于重复访问.相反,请考虑使用该getCharacters:range:
方法一次性获取字符 并直接迭代字节.如果要在字符串中搜索特定字符或子字符串,请不要逐个遍历字符.取而代之的是,使用较高水平的方法,例如
rangeOfString:
,rangeOfCharacterFromSet:
,或substringWithRange:
,其被用于搜索优化的NSString
字符.
请参阅此Stack Overflow答案,了解如何从右端删除空格,以NSString
获取如何rangeOfCharacterFromSet:
迭代字符串字符而不是自己执行此操作的示例.
Jac*_*kin 18
我肯定会首先得到一个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...
}
Run Code Online (Sandbox Code Playgroud)