RangeError: Invalid string length 在拼接c string字符串拼接的时候使用了两层f

收藏,11.6k 浏览
在每个应用里我们都大量使用字符串。下面我们将快速看看一些常见的操作字符串的方法,过一遍常见操作的最佳实践。
字符串的比较、搜索和排序
排序和比较字符串比第一眼看上去要复杂得多。不只是因为字符串可以包含代理对(surrogate pairs )(详见 ) ,而且比较还与字符串的本地化相关。在某些极端情况下相当棘手。
苹果文档中 String Programming Guide 里有一节叫做 “字符与字形集群(Characters and Grapheme Clusters)”,里面提到一些陷阱。例如对于排序来说,一些欧洲语言将序列“ch”当作单个字母。在一些语言里,“?”被认为等同于 ‘a’ ,而在其它语言里它却被排在 ‘z’ 后面。
而 NSString 有一些方法来帮助我们处理这种复杂性。首先看下面的方法:
- (NSComparisonResult)compare:(NSString *)aString
options:(NSStringCompareOptions)mask
range:(NSRange)range
locale:(id)locale
它带给我们充分的灵活性。另外,还有很多“便捷函数”都使用了这个方法。
与比较有关的可用参数如下:
NSCaseInsensitiveSearch
NSLiteralSearch
NSNumericSearch
NSDiacriticInsensitiveSearch
NSWidthInsensitiveSearch
NSForcedOrderingSearch
它们都可以用逻辑或运算组合在一起。
NSCaseInsensitiveSearch :“A”等同于“a”,然而在某些地方还有更复杂的情况。例如,在德国,“ss” 和 “SS”是等价的。
NSLiteralSearch :Unicode 的点对 Unicode 点比较。它只在所有字符都用相同的方式组成的情况下才会返回相等。LATIN CAPITAL LETTER A 加上 COMBINING RING ABOVE 并不等同于 LATIN CAPITAL LETTER A WITH RING ABOVE.
译注:这个要解释一下,首先,每一个Unicode都是有官方名字的!LATIN CAPITAL & LETTER A是一个大写“A”,COMBINING RING ABOVE是一个
?,LATIN CAPITAL & LETTER A WITH RING ABOVE,这是?前两者的组合不等同于后者。
NSNumericSearch:它对字符串里的数字排序,所以 “Section 9” \& “Section 20” \& “Section 100.”
NSDiacriticInsensitiveSearch : “A” 等同于 “?” 等同于 “?.”
NSWidthInsensitiveSearch : 一些东亚文字(平假名 和 片假名)有全宽与半宽两种形式。
很值得一提的是 - (NSComparisonResult)localizedStandardCompare: ,它排序的方式和 Finder 一样。它对应的选项是 NSCaseInsensitiveSearch 、 NSNumericSearch 、NSWidthInsensitiveSearch 以及 NSForcedOrderingSearch 。如果我们要在UI上显示一个文件列表,用它就最合适不过了。
大小写不敏感的比较和音调符号不敏感的比较都是相对复杂和昂贵的操作。如果我们需要比较很多次字符串那这就会成为一个性能上的瓶颈(例如对一个大的数据集进行排序),一个常见的解决方法是同时存储原始字符串和折叠字符串。例如,我们的 Contact
类有一个正常的 name
属性,在内部它还有一个foldedName
属性,它将自动在 name变化时更新。那么我们就可以使用 NSLiteralSearch
来比较 name
的折叠版本。 NSString
有一个方法来创建折叠版本:
- (NSString *)stringByFoldingWithOptions:(NSStringCompareOptions)options
locale:(NSLocale *)locale
要在一个字符串中搜索子字符串,最灵活性的方法是:
- (NSRange)rangeOfString:(NSString *)aString
options:(NSStringCompareOptions)mask
range:(NSRange)searchRange
locale:(NSLocale *)locale
同时,还有一些“便捷方法”,它们在最终都会调用上面这个方法,我们可以传入上面列出的参数,以及以下这些额外的参数:
NSBackwardsSearch
NSAnchoredSearch
NSRegularExpressionSearch
NSBackwardsSearch :在字符串的末尾开始反向搜索。
NSAnchoredSearch : 只考虑搜索的起始点(单独使用)或终止点(当与 NSBackwardsSearch
结合使用时)。这个方法可以用来检查前缀或者后缀,以及大小写不敏感(case-insensitive)或者音调不敏感(diacritic-insensitive)的比较。
NSRegularExpressionSearch :使用正则表达式搜索,要了解更多与使用正则表达式有关的信息,请关注 Chris’s 的 String Parsing 。
另外,还有一个方法:
- (NSRange)rangeOfCharacterFromSet:(NSCharacterSet *)aSet
options:(NSStringCompareOptions)mask
range:(NSRange)aRange
与前面搜索字符串不同的是, 它只搜索给定字符集的第一个字符。即使只搜索一个字符,但如果由于此字符是由元字符组成的序列(composed character sequence),所以返回范围的长度也可能大于1。
大写与小写
一定不要使用 NSString
的 -uppercaseString
或者 -lowercaseString
的方法来处理 UI 显示的字符串,而应该使用 -uppercaseStringWithLocale
来代替, 比如:
NSString *name = @"Tómas";
cell.text = [name uppercaseStringWithLocale:[NSLocale currentLocale]];
格式化字符串
同C语言中的 sprintf 函数( ANSI C89 中的一个函数 )类似, Objective C 中的 NSString 类也有如下的3个方法:
-initWithFormat:
-initWithFormat:arguments:
+stringWithFormat:
需要注意这些格式化方法都是 非本地化 的 。所以这些方法得到的字符串是不能直接拿来显示在用户界面上的。如果需要本地化,那我们需要使用下面这些方法:
-initWithFormat:locale:
-initWithFormat:locale:arguments:
+localizedStringWithFormat:
Florian 有一篇关于
的文章更详细地讨论了这个问题。
printf(3)的man页面有关于它如何格式化字符串的全部细节。除了所谓的转换格式(它以%字符开始),格式化字符串会被逐字复制:
double a = 4;
float b = 376.;
NSString *s = [NSString stringWithFormat:@"%g :: %g", a, b];
// "25812.8 :: 376.73"
我们格式化了两个浮点数。注意单精度浮点数和双精度浮点数共同了一个转换格式。
除了来自 printf(3) 的转换规范,我们还可以使用 %@
来输出一个对象。在对象描述那一节中有述,如果对象响应 -descriptionWithLocale:
方法,则调用它,否则调用 -description 。
被结果替换。
使用整形数字时,有些需要注意的细节。首先,有符号数(d和i)和无符号数(o、u、x和X)分别有转换规范。需要使用者选择具体的类型。
如果我们使用的东西是 printf不知道的,我们必须要做类型转换。 NSUInteger
正是这样一个例子,它在64位和32位平台上是不一样的。下面的例子可以同时工作在32位和64位平台。
uint64_t p = 3693951;
NSString *s = [NSString stringWithFormat:@"The ninth Mersenne prime is %llu", (unsigned long long) p];
// "The ninth Mersenne prime is 3693951"
o, u, x, X
-------------- --------------- ----------------------
signed char
unsigned char
unsigned short
unsigned int
unsigned long
ll (ell ell)
unsigned long long
uintmax\_t
ptrdiff\_t
适用于整数的转换规则有:
int m = -;
uint n = U;
NSString *s = [NSString stringWithFormat:@"d:%d i:%i o:%o u:%u x:%x X:%X", m, m, n, n, n, n];
// "d:- i:- o: u: x:8f0e135 X:8F0E135"
具有一样的功能,它们都打印出有符号十进制数。 %o
就较为晦涩了:它使用八进制表示。 %u
输出无符号十进制数——它是我们常用的。最后 %x
使用十六进制表示——后者使用大写字母。
,我们可以在 0x 前面添加 “#” 井字符前缀看,增加可读性。
我们可以传入特定参数,来设置最小字段宽度和最小数字位数(默认两者都是0),以及左/右对齐。请查看man页面获取详细信息。下面是一些例子:
int m = 42;
NSString *s = [NSString stringWithFormat:@"'%4d' '%-4d' '%+4d' '%4.3d' '%04d'", m, m, m, m, m];
// ‘42’ ‘42 ’ ‘ +42’ ‘ 042’ ‘0042’
NSString *s = [NSString stringWithFormat:@"'%4d' '%-4d' '%+4d' '%4.3d' '%04d'", m, m, m, m, m];
// ‘ -42’ ‘-42 ’ ‘ -42’ ‘-042’ ‘-042’
可用于打印出指针——它和 %#x
相似但可同时在32位和64位平台上正常工作。
关于浮点数的转换规则有8个:eEfFgGaA。但除了 %f 和 %g 外我们很少使用其它的。对于指数部分,小写的版本使用小写 e,大写的版本就使用大写 E。
是浮点数的全能转换符 ,它与 %f
的不同在下面的例子里显示得很清楚:
double v[5] = {1.12, 0.34, 0.8901234};
NSString *s = [NSString stringWithFormat:@"%g %g %g %g %g", v[0], v[1], v[2], v[3], v[4]];
// ".12 0..23457e-06"
NSString *s = [NSString stringWithFormat:@"%f %f %f %f %f", v[0], v[1], v[2], v[3], v[4]];
// " 12....000001"
和整数一样,我们依然可以指定最小字段宽度和最小数字数。
格式化字符串允许使用参数来改变顺序:
[NSString stringWithFormat:@"%2$@ %1$@", @"1st", @"2nd"];
// "2nd 1st"
我们只需将从1开始的参数与一个$接在%后面。这种写法在进行本地化的时候极其常见,因为在不同语言中,各个参数所处的顺序位置可能不尽相同。
NSLog() 函数与
+stringWithFormat: 的工作方式一样。我们可以调用:
int magic = 42;
NSLog(@"The answer is %d", magic);
下面的代码可以用同样的方式构造字符串:
int magic = 42;
NSString *output = [NSString stringWithFormat:@"The answer is %d", magic];
NSLog()会输出字符串,并且它会加上时间戳、进程名、进程ID以及线程ID作为前缀。
实现能接受格式化字符串的方法
有时在我们自己的类中提供一个能接受格式化字符串的方法会很方便使用。假设我们要实现的是一个 To Do 应用,它包含一个 Item 类。我们想要提供:
+ (instancetype)itemWithTitleFormat:(NSString *)format, ...
如此我们就可以使用:
Item *item = [Item itemWithFormat:@"Need to buy %@ for %@", food, pet];
这种类型的方法可以接受可变数量的参数,所以被称为可变参数方法。我们必须使用一个定义在stdarg.h里的宏来使用可变参数。上面方法的实现代码可能会像下面这样:
+ (instancetype)itemWithTitleFormat:(NSString *)format, ...;
va_start(ap, format);
NSString *title = [[NSString alloc] initWithFormat:format locale:[NSLocale currentLocale] arguments:ap];
va_end(ap);
return [self itemWithTitle:title];
进一步,我们要添加 NS_FORMAT_FUNCTION 到方法的定义里(在头文件中),如下所示:
+ (instancetype)itemWithTitleFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
NS_FORMAT_FUNCTION 展开为一个方法 __attribute__,它会告诉编译器在索引1处的参数是一个格式化字符串,而实际参数从索引2开始。这将允许编译器检查格式化字符串而且会像 NSLog() 和 -[NSString stringWithFormat:] 一样输出警告信息。
字符与字符串组件
如有一个字符串 “bird” ,找出组成它的独立字母是很简单的。第二个字母是“i”(Unicode: LATIN SMALL LETTER I)。而对于像?se这样的字符串就没那么简单了。看起来像三个字母的组合可有多种方式,例如:
A LATIN CAPITAL LETTER A
? COMBINING RING ABOVE
s LATIN SMALL LETTER S
e LATIN SMALL LETTER E
? LATIN CAPITAL LETTER A WITH RING ABOVE
s LATIN SMALL LETTER S
e LATIN SMALL LETTER E
里可以读到更多关于联合标记(combining marks)的信息,其他语言文字有更多复杂的代理对(complicated surrogate pairs)。
如果我们要在字符层面处理一个字符串,那我们就要小心翼翼。苹果官方文档中 String Programming Guide 有一节叫做 “Characters and Grapheme Clusters”,里面有更多关于这一点的细节。
NSString有两个方法:
-rangeOfComposedCharacterSequencesForRange:
-rangeOfComposedCharacterSequenceAtIndex:
上面这两个方法在有的时候很有帮助,例如,分开一个字符串时保证我们不会分开被称为代理对(surrogate pairs)的东西。
如果我们要在字符串的字符上做工作, NSString 有个叫做 -enumerateSubstringsInRange:options:usingBlock: 的方法。
将 NSStringEnumerationByComposedCharacterSequences 作为选项传递,我们就能扫描所有的字符。例如,用下面的方法,我们可将字符串 “International Business Machines” 变成 “IBM”。
- (NSString *)
NSMutableString *result = [NSMutableString string];
[self enumerateSubstringsInRange:NSMakeRange(0, self.length)
options:NSStringEnumerationByWords | NSStringEnumerationLocalized
usingBlock:^(NSString *word, NSRange wordRange, NSRange enclosingWordRange, BOOL *stop1) {
__block NSString *firstLetter =
[self enumerateSubstringsInRange:NSMakeRange(0, word.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *letter, NSRange letterRange, NSRange enclosingLetterRange, BOOL *stop2) {
firstLetter =
*stop2 = YES;
if (letter != nil) {
[result appendString:letter];
如文档所示,词和句的分界可能基于地区的变化而变化。因此有 NSStringEnumerationLocalized选项。
多行文字字面量
编译器的确有一个隐蔽的特性:把空格分隔开的字符串衔接到一起。这是什么意思呢?下面两段代码是完全等价的:
NSString *limerick = @"A lively young damsel named Menzies\n"
@"Inquired: <>\n"
@"Her aunt, with a gasp,\n"
@"Replied: "It's a wasp,\n"
@"And you're holding the end where the stenzies.\n";
NSString *limerick = @"A lively young damsel named Menzies\nInquired: <>\nHer aunt, with a gasp,\nReplied: "It's a wasp,\nAnd you're holding the end where the stenzies.\n";
前者看起来更舒服,但是有一点要注意千万不要在任意一行末尾加入逗号或者分号。
同时也可以这样做:
NSString * string = @"The man " @"who knows everything " @"learns nothing" @".";
译者注:上面这行代码原文是有误的,原文是 NSString * @"The man " @"who knows everything " @"learns nothing" @"."
,读者可以尝试一下,如果这样写是无法通过编译的。
编译器只是为我们提供了一个便捷的方式,将多个字符串在编译期组合在了一起。
可变字符串
可变字符串有两个常见的使用场景:(1)拼接字符串(2)替换部分字符串
创建字符串
可变字符串可以很轻易地把多个字符串在你需要的时候组合起来。
- (NSString *)magicToken
NSMutableString *string = [NSMutableString string];
if (usePrefix) {
[string appendString:@"&&&"];
[string appendFormat:@"%d--%d", self.foo, self.bar];
if (useSuffix) {
[string appendString:@"&&&"];
这里要注意的是,虽然原本返回值应该是一个 NSString
类型的对象,我们只是简单地返回一个NSMutableString 类型的对象。
替换字符串
可变字符串除了追加组合之外,还提供了以下4个方法:
-deleteCharactersInRange:
-insertString:atIndex:
-replaceCharactersInRange:withString:
-replaceOccurrencesOfString:withString:options:range:
这些方法和 NSString 的类似:
-stringByReplacingOccurrencesOfString:withString:
-stringByReplacingOccurrencesOfString:withString:options:range:
-stringByReplacingCharactersInRange:withString:
但是它没有创建新的字符串仅仅把当前字符串变成了一个可变的类型,这样让代码更容易阅读,以及提升些许性能。
NSMutableString * // 假设我们已经有了一个名为 string 的字符串
// 现在要去掉它的一个前缀,做法如下:
NSString *prefix = @"WeDon’tWantThisPrefix"
NSRange r = [string rangeOfString:prefix
options:NSAnchoredSearch
range:NSMakeRange(0, string.length)
locale:nil];
if (r.location != NSNotFound) {
[string deleteCharactersInRange:r];
一个看似微不足道但很常见的情况是字符串连接。比如现在有这样几个字符串:
我们想用它们来创建下面这样的一个字符串:
Hildr, Heidrun, Gerd, Gu?rún, Freya, Nanna, Siv, Ska?i, Gróa
那么就可以这样做:
NSArray *names = @["Hildr", @"Heidrun", @"Gerd", @"Gu?rún", @"Freya", @"Nanna", @"Siv", @"Ska?i", @"Gróa"];
NSString *result = [names componentsJoinedByString:@", "];
如果我们将其显示给用户,我们就要使用本地化表达,确保将最后一部分替换相应语言的 , and :
@implementation NSArray (ObjcIO_GroupedComponents)
- (NSString *)groupedComponentsWithLocale:(NSLocale *)
if (self.count & 1) {
return @"";
} else if (self.count & 2) {
return self[0];
} else if (self.count & 3) {
NSString *joiner = NSLocalizedString(@"joiner.2components", @"");
return [NSString stringWithFormat:@"%@%@%@", self[0], joiner, self[1]];
NSString *joiner = [NSString stringWithFormat:@"%@ ", [locale objectForKey:NSLocaleGroupingSeparator]];
NSArray *first = [self subarrayWithRange:NSMakeRange(0, self.count - 1)];
NSMutableString *result = [NSMutableString stringWithString:[first componentsJoinedByString:joiner]];
NSString *lastJoiner = NSLocalizedString(@"joiner.3components", @"");
[result appendString:lastJoiner];
[result appendString:self.lastObject];
那么在本地化的时候,如果是英语,应该是:
"joiner.2components" = " and ";
"joiner.3components" = ", and ";
如果是德语,则应该是:
"joiner.2components" = " und ";
"joiner.3components" = " und ";
结合组件的逆过程可以用
-componentsSeparatedByString: ,这个方法会将一个字符串变成一个数组。例如,将 “12|5|3” 变成 “12”、“5” 和 “3”。
在许多面向对象编程语言里,对象有一个叫做 toString() 或类似的方法。在 Objective C 里,这个方法是:
- (NSString *)description
以及它的兄弟方法:
- (NSString *)debugDescription
当自定义模型对象时,覆写 -description 方法是一个好习惯,在UI上显示该对象时调用的就是description方法的返回值。假定我们有一个 Contact类,下面是它的 description方法实现。
- (NSString *)description
return self.
我们可以像下面代码这样格式化字符串:
label.text = [NSString stringWithFormat:NSLocalizedString(@"%@ has been added to the group “%@”.", @""), contact, group];
因为该字符串是用来做UI显示的,我们可能需要做本地化,那么我们就需要覆写descriptionWithLocale:(NSLocale *)locale方法。
- (NSString *)descriptionWithLocale:(NSLocale *)
%@ 会首先调用 -descriptionWithLocale,如果没有返回值,再调用 -description,在调试时,打印一个对象,我们用 po这个命令(它是print object的缩写)
(lldb) po contact
如果在调试窗口的终端下输入 po contact, 它会调用对象的 debugDescription方法。默认情况下debugDescription是直接调用 description。如果你希望输出不同的信息,那么就分别覆写两个方法。大多数情况下,尤其是对于非数据模型的对象,你只需要覆写 -description就能满足需求了。
实际上对象的标准格式化输出是这样的:
- (NSString *)
return [NSString stringWithFormat:@"&%@: %p&", self.class, self];
NSObject就是这么干的。当你覆写该方法时,也可以像这样写。假定我们有一个DetailViewController,在它的UI上要显示一个 contact ,我们可能会这样覆写该方法:
- (NSString *)
return [NSString stringWithFormat:@"&%@: %p& contact = %@", self.class, self, self.contact.debugDescription];
NSManagedObject子类的描述
我们将特别注意向 NSManagedObject 的子类添加 -description / -debugDescription 的情况。由于 Core Data的惰性加载机制(faulting mechanism)允许未加载数据的对象存在,所以当我们调用 -debugDescription 我们并不希望改变我们的应用程序的状态,因此我要确保检查 isFault
这个属性。例如,我们可如下这样实现它:
- (NSString *)debugD
NSMutableString *description = [NSMutableString stringWithFormat:@"&%@: %p&", self.class, self];
if (! self.isFault) {
[description appendFormat:@" %@ \"%@\" %gL", self.identifier, self.name, self.metricVolume];
再次,因为它们是模型对象,重载 -description 简单地返回描述实例的属性名就可以了。
简单来说就是我们不应该使用 NSString来描述文件路径。对于 OS X 10.7 和 iOS 5, NSURL更便于使用,而且更有效率,它还能缓存文件系统的属性。
再者, NSURL 有八个方法来访问被称为 resource values 的东西。它们提供给我们一个稳定的接口来获取和设置文件与目录的多种属性,例如本地化文件名( NSURLLocalizedNameKey)、文件大小(NSURLFileSizeKey),以及创建日期( NSURLCreationDateKey),等等。
尤其是在遍历目录内容时,使用 -[NSFileManagerenumeratorAtURL:includingPropertiesForKeys:options:errorHandler:] 附带一个关键词列表,然后用 -getResourceValue:forKey:error: 检索它们,能带来显著的性能提升。
下面是一个简短的例子展示了如何将它们组合在一起:
NSError *error =
NSFileManager *fm = [[NSFileManager alloc] init];
NSURL *documents = [fm URLForDirectory:NSDocumentationDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
error:&error];
NSArray *properties = @[NSURLLocalizedNameKey, NSURLCreationDateKey];
NSDirectoryEnumerator *dirEnumerator = [fm enumeratorAtURL:documents
includingPropertiesForKeys:properties
errorHandler:nil];
for (NSURL *fileURL in dirEnumerator) {
NSString *name =
NSDate *creationDate =
if ([fileURL getResourceValue:&name
forKey:NSURLLocalizedNameKey
error:NULL] &&
[fileURL getResourceValue:&creationDate
forKey:NSURLCreationDateKey
error:NULL])
NSLog(@"'%@' was created at %@", name, creationDate);
我们把属性的键传给
-enumeratorAtURL: 方法中,在遍历目录内容时,这个方法能确保用非常高效的方式获取它们。在循环中,调用 -getResourceValue:… 能简单地从 NSURL 得到已缓存的值,而不用去访问文件系统。
传递路径到UNIX API
因为 Unicode 非常复杂,同一个字母有多种表示方式,所以我们需要很小心地传递路径给UNIX API。在这些情况里,一定不能使用 UTF8String ,正确地做法是使用 -fileSystemRepresentation 方法,如下:
NSURL *documentURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
error:NULL];
documentURL = [documentURL URLByAppendingPathComponent:name];
int fd = open(documentURL.fileSystemRepresentation, O_RDONLY);
与 NSURL 类似,同样的情况也发生在 NSString 上。如果我们不这么做,在打开一个文件名或路径名包含合成字符的文件时我们将看到随机错误。在 OS X 上,当用户的短名刚好包含合成字符时就会显得特别糟糕。
我们需要一个 char const * 版本的路径的一些常见情况是UNIX open() 和 close() 指令。但这也可能发生在 GCD / libdispatch 的 I/O API 上。
dispatch_io_t
dispatch_io_create_with_path(dispatch_io_type_t type,
const char *path, int oflag, mode_t mode,
dispatch_queue_t queue,
void (^cleanup_handler)(int error));
如果我们要使用 NSString 来做,那我们要保证像下面这样做:
NSString *path = ... // 假设我们已经有一个名为 path 的字符串
io = dispatch_io_create_with_path(DISPATCH_IO_STREAM,
path.fileSystemRepresentation,
O_RDONLY, 0, queue, cleanupHandler);
-fileSystemRepresentation 所做的是它首先将这个字符串转换成文件系统的规范形式然后用UTF-8编码。
你可能感兴趣的文章
1 收藏,809 浏览
2 收藏,507 浏览
本文隶属于专栏
新闻资讯一手掌握
分享到微博?
与我们一起探索更多的未知
专业的开发者技术社区,为用户提供多样化的线上知识交流,丰富的线下活动及给力的工作机会
加入只需一步
举报理由:
推广(招聘、广告、SEO 等)方面的内容
带有人身攻击、辱骂、仇恨等违反条款的内容
与已有问题重复(请编辑该提问指向已有相同问题)
不友善内容
答非所问,不符合答题要求
其他原因(请补充说明)
补充说明:
扫扫下载 App
SegmentFault
一起探索更多未知iOS 字符串处理笔记
时间: 11:58:54
&&&& 阅读:226
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&iOS字符串处理笔记,包括如何使用正则表达式解析,NSScanner扫描,设置和使用CoreParse解析器来解析处理自定义符号等内容
在一个字符串中搜索子字符串&
最灵活的方法
格式化字符串&
-initWithFormat:
-initWithFormat:arguments:
+stringWithFormat:
可以同时工作在32位和64位的
uint64_t p = 3693951;
NSString *s = [NSString stringWithFormat:@"The ninth Mersenne prime is %llu", (unsigned long long) p];
Modifierd, io, u, x, X
signed char
unsigned char
unsigned short
unsigned int
unsigned long
int m = -;
uint n = U;
NSString *s = [NSString stringWithFormat:@"d:%d i:%i o:%o u:%u x:%x X:%X", m, m, n, n, n, n];
// "d:- i:- o: u: x:8f0e135 X:8F0E135"
//o是八进制
设置最小字段宽度和最小数字位数
int m = 42;
NSString *s = [NSString stringWithFormat:@"‘%4d‘ ‘%-4d‘ ‘%+4d‘ ‘%4.3d‘ ‘%04d‘", m, m, m, m, m];
// "[ 42] [42 ] [ +42] [ 042] [0042]"
NSString *s = [NSString stringWithFormat:@"‘%4d‘ ‘%-4d‘ ‘%+4d‘ ‘%4.3d‘ ‘%04d‘", m, m, m, m, m];
// "[ -42] [-42 ] [ -42] [-042] [-042]"
%p可打印指针,和%#x不同的是它可以同时在32位和64位执行
使用%f和%g
double v[5] = {12345, 12, 0.12, 0.34, 0.8901234};
NSString *s = [NSString stringWithFormat:@"%g %g %g %g %g", v[0], v[1], v[2], v[3], v[4]];
// "12345 12 0.12 0.123457 1.23457e-06"
NSString *s = [NSString stringWithFormat:@"%f %f %f %f %f", v[0], v[1], v[2], v[3], v[4]];
// " 12....000001"
NSString *limerick = @"A lively young damsel named Menzies
@"Inquired: &Do you know what this thenzies?&
@"Her aunt, with a gasp,
@"Replied: "It‘s a wasp,
@"And you‘re holding the end where the stenzies.
NSString *limerick = @"A lively young damsel named Menzies
Inquired: &Do you know what this thenzies?&
Her aunt, with a gasp,
Replied: "It‘s a wasp,
And you‘re holding the end where the stenzies.
更简洁的方法
NSString * string = @"The man " @"who knows everything " @"learns nothing" @".";
替换字符串&
NSMutableString的四个方法
NSString的方法
NSMutableString不会创建新字符串,性能会好点
NSMutableString *
连接字符串&
NSArray *names = @["Hildr", @"Heidrun", @"Gerd", @"Gu&r&n", @"Freya", @"Nanna", @"Siv", @"Ska&i", @"Gr&a"];
NSString *result = [names componentsJoinedByString:@", "];
字符串解析&
正则表达式&
NSError *error =
NSString *pattern = @"(\w+) =
将字符串分解成数组,使用componentsSeparatedByString:这个方法,或者enumerateSubstringsInRange:options:usingBlock:。如果是按照行来进行分解可以使用option这个参数传NSStringEnumerationByLines
NSString *input = @&
backgroundColor =
NSScanner *scanner = [NSScanner scannerWithString:string];
设计一个能够用(100,0,255)或者#ff0000这样的字符来定义颜色的方法。
- (NSDictionary *)parse:(NSString *)string error:(NSError **)error
self.scanner = [NSScanner scannerWithString:string];
self.scanner.charactersToBeSkipped = [NSCharacterSet whitespaceCharacterSet];
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSCharacterSet *letters = [NSCharacterSet letterCharacterSet]
while (!self.scanner.isAtEnd) {
NSString *key =
UIColor *value =
BOOL didScan = [self.scanner scanCharactersFromSet:letters intoString:&key] &&
[self.scanner scanString:@"=" intoString:NULL] &&
[self scanColor:&value];
result[key] =
[self.scanner scanCharactersFromSet:[NSCharacterSet newlineCharacterSet]
intoString:NULL];
符号化处理&
先进星扫描,使用NSScanner来解析这个表达式
myView.left = otherView.right * 2 + 10
viewController.view.centerX + myConstant
NSScanner *scanner = [NSScanner scannerWithString:contents];
NSMutableArray *tokens = [NSMutableArray array];
while (![scanner isAtEnd]) {
for (NSString *operator in @[@"=", @"+", @"*", @"&=", @"&=", @"."]) {
if ([scanner scanString:operator intoString:NULL]) {
[tokens addObject:operator];
//接下来识别非符号的只包含字母的string
NSString *result =
if ([scanner scanCharactersFromSet:[NSCharacterSet letterCharacterSet]
intoString:&result]) {
[tokens addObject:result];
//NSScanner有scanDouble:来扫描double
double doubleResult = 0;
if ([scanner scanDouble:&doubleResult]) {
[tokens addObject:@(doubleResult)];
//完成后用将需要解析的表达式放入试试
NSString* example = @"myConstant = 100
myView.left = otherView.right * 2 + 10
@"viewController.view.centerX + myConstant &= self.view.centerX";
NSArray *result = [self.scanner tokenize:example];
NSArray *expected = @[@"myConstant", @"=", @100, @"myView", @".", @"left",
@"=", @"otherView", @".", @"right", @"*", @2, @"+",
@10, @"viewController", @".", @"view", @".",
@"centerX", @"+", @"myConstant", @"&=", @"self",
@".", @"view", @".", @"centerX"];
XCTAssertEqualObjects(result, expected);
进行语法解析,需要语法分析库描述我们的语言。下面代码就是为那个布局约束语言写的解析语法,用的扩展的巴科斯范式&&&写法:&
constraint = expression comparator expression
comparator = "=" | "&=" | "&="
expression = keyPath "." attribute addMultiplier addConstant
keyPath = identifier | identifier "." keyPath
attribute = "left" | "right" | "top" | "bottom" | "leading" | "trailing" | "width" | "height" | "centerX" | "centerY" | "baseline"
addMultiplier = "*" atom
addConstant = "+" atom
atom = number | identifier
还有很多Objective-C的语法解析,更多的可以在CocoaPods上找到:&&&。比较好的就是CoreParse,地址:&&&,但是需要使用它支持的语法。下面就是CoreParse支持的格式:&
NSString* grammarString = [@[
@"Atom ::= num@‘Number‘ | ident@‘Identifier‘;",
@"Constant ::= name@‘Identifier‘ ‘=‘ value@
一个规则匹配后解析器就找到同样名称的类
- (id)parser:(CPParser *)parser didProduceSyntaxTree:(CPSyntaxTree *)syntaxTree
NSString *ruleName = syntaxTree.rule.
if ([ruleName isEqualToString:@"Attribute"]) {
return self.layoutAttributes[[[syntaxTree childAtIndex:0] keyword]];
完整的解析器代码在:&&&。里面有个解析类可以用来解析复杂的布局约束,如下:&
viewController.view.centerX + 20
可以得到如下结果,方便转换成NSLayoutConstraint对象
字符串的渲染&
label默认显示一行,如果设置numberOfLines为大于1的话可以显示指定行数,如果设置为0,则多少行都显示
attributedText属性可以显示富文本
label的font,textColor,textAlignment,shadowColor和shadowOffset属性可以改变外观。
改变程序内所有Label的风格,可以使用[UILabel appearance]方法
UITextField&
text field只限于单行
UITextfield实现了UITextInputTraits协议,这个协议需要指定键盘外观和操作等细节。比如显示什么键盘和返回按键响应等
可以通过设置左右辅助视图,或者设置背景来自定义输入框风格了。
UITextView&
相比较UITextField,它能够处理多行文本
可以使用定制Text Kit,官方文档:&&
可以为layout manager,text container或text storage自定义行为或替换自定义子类。
在iOS7之前是基于WebKit的功能少,之后会有很多不同具体可以参考这两篇文章:Peter的&&&,Brent的&&
TableView中显示动态文本&
Table view的Delegate有个方法用来计算高度:tableView:heightForRowAtIndexPath:。自定义一个UITableViewCell的子类
- (void)layoutSubviews
[super layoutSubviews];
self.textLabel.frame = CGRectInset(self.bounds,
MyTableViewCellInset,
MyTableViewCellInset);
计算真实高度需要使用boundingRectWithSize:options:context: 这个方法
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
CGFloat labelWidth = self.tableView.bounds.size.width - MyTableViewCellInset*2;
NSAttributedString *text = [self attributedBodyTextAtIndexPath:indexPath];
NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin |
NSStringDrawingUsesFontL
CGRect boundingRect = [text boundingRectWithSize:CGSizeMake(labelWidth, CGFLOAT_MAX)
options:options
context:nil];
return (CGFloat) (ceil(boundingRect.size.height) + MyTableViewCellInset*2);
使用Text Kit和NSAttributedString进行布局&
先设置attributes
CGFloat const fontSize = 15;
NSMutableDictionary *body1stAttributes = [NSMutableDictionary dictionary];
body1stAttributes[NSFontAttributeName] = [UIFont fontWithName:@"BodoniSvtyTwoITCTT-Book"
size:fontSize];
NSMutableParagraphStyle *body1stParagraph = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
body1stParagraph.alignment = NSTextAlignmentJ
body1stParagraph.minimumLineHeight = fontSize + 3;
body1stParagraph.maximumLineHeight = body1stParagraph.minimumLineH
body1stParagraph.hyphenationFactor = 0.97;
body1stAttributes[NSParagraphStyleAttributeName] = body1stParag
这里字体为BodoniSvtyTwoITCTT,如果需要查看更多字体可以使用 +[UIFont familyNames]这个方法。为了得到字体的名字,可以使用 +[UIFont fontNamesForFamilyName:]。接下来创建段落的属性
NSMutableDictionary *bodyAttributes = [body1stAttributes mutableCopy];
NSMutableParagraphStyle *bodyParagraph =
[bodyAttributes[NSParagraphStyleAttributeName] mutableCopy];
bodyParagraph.firstLineHeadIndent = fontS
bodyAttributes[NSParagraphStyleAttributeName] = bodyP
装饰段落风格,使用装饰字体将文本居中对齐,装饰字符的前后加上空白段落
NSMutableDictionary *ornamentAttributes = [NSMutableDictionary dictionary];
ornamentAttributes[NSFontAttributeName] = [UIFont fontWithName:@"BodoniOrnamentsITCTT"
NSMutableParagraphStyle *ornamentParagraph = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
ornamentParagraph.alignment = NSTextAlignmentC
ornamentParagraph.paragraphSpacingBefore = fontS
ornamentParagraph.paragraphSpacing = fontS
ornamentAttributes[NSParagraphStyleAttributeName] = ornamentP
显示数字表格table,表格布局示例
NSCharacterSet *decimalTerminator = [NSCharacterSet
characterSetWithCharactersInString:decimalFormatter.decimalSeparator];
NSTextTab *decimalTab = [[NSTextTab alloc]
initWithTextAlignment:NSTextAlignmentCenter
location:100
options:@{NSTabColumnTerminatorsAttributeName:decimalTerminator}];
NSTextTab *percentTab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentRight
location:200
options:nil];
NSMutableParagraphStyle *tableParagraphStyle =
[[NSParagraphStyle defaultParagraphStyle] mutableCopy];
tableParagraphStyle.tabStops = @[decimalTab, percentTab];
显示列表的属性设置如下
NSMutableDictionary *listAttributes = [bodyAttributes mutableCopy];
NSMutableParagraphStyle *listParagraph =
[listAttributes[NSParagraphStyleAttributeName] mutableCopy];
listParagraph.headIndent = fontSize * 3;
listParagraph.firstLineHeadIndent = fontS
NSTextTab *listTab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentNatural
location:fontSize * 3
options:nil];
listParagraph.tabStops = @[listTab];
listAttributes[NSParagraphStyleAttributeName] = listP标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&& &&&&&&
&& &&&&&&&&&&
版权所有 鲁ICP备号-4
打开技术之扣,分享程序人生!

我要回帖

更多关于 invalid blob length 的文章

 

随机推荐