0%

《Objective-C 编程》16.NSString

NSString

概述

NSString 类及其可变子类 NSMutableString 提供了一组用于处理字符串的 API,包括用于比较,搜索和修改字符串的方法。 NSString 对象在整个 Foundation 和其他 Cocoa 框架中使用,作为平台上所有文本和语言功能的基础。

Unicode 与 NSString

  • Unicode 标准为世界上几乎所有的书写系统里所使用的每一个字符或符号定义了一个唯一的数字。
  • NeXT 在 1994 年推出的 Foundation Kit 是所有编程语言中最先基于 Unicode 的标准库之一。
  • 最初,Unicode 编码是被设计为 16 位的,提供了 65,536 个字符的空间。后来,Unicode 编码扩展到了 21 位
  • NSString 代表的是用 UTF-16 编码的文本,长度、索引和范围都基于 UTF-16 的码元。

方法

创建 NSString 实例

  • 使用 字面量语法:**@”…”** ,根据给定的字符串创建一个 NSString 对象;
// 声明一个指向 NSString 实例的指针变量,它保存了 NSString 实例在内存中的地址
NSString *str1 = @"Hello";
// 指针变量指向了新的内存地址
str1 = @"World";
  • init
NSString *string = [[NSString alloc] init];
  • - initWithFormat:
// 创建动态字符串,字符串的内容需要等到程序运行时才知道
NSString *str2 = [[NSString alloc] initWithFormat:@"The string is %@",str1];
  • + stringWithFormat:
NSString *str3 = [NSString stringWithFormat:@"The date is %@",str1];
  • + stringWithString:
// 通过复制另一个给定字符串来创建字符串。
NSString *str2 = [NSString stringWithString:str1];

格式化字符串

同 C 语言中的 sprintf 函数(ANSI C89 中的一个函数)类似,Objective C 中的 NSString 类也有如下的 3 个方法:

  • -initWithFormat:
  • -initWithFormat:arguments:
  • +stringWithFormat:

需要注意这些格式化方法都是 ** 非本地化 ** 的。所以这些方法得到的字符串是不能直接拿来显示在用户界面上的。如果需要本地化,那我们需要使用下面这些方法:

  • -initWithFormat:locale:
  • -initWithFormat:locale:arguments:
  • +localizedStringWithFormat:

*** 传入特定参数,来设置最小字段宽度和最小数字位数(默认两者都是 0)

int m = 42;
NSString *s = [NSString stringWithFormat:@"'%4d' '%-4d' '%+4d' '%4.3d' '%04d'", m, m, m, m, m];
// "[  42] [42  ] [ +42] [ 042] [0042]"
m = -42;
NSString *s = [NSString stringWithFormat:@"'%4d' '%-4d' '%+4d' '%4.3d' '%04d'", m, m, m, m, m];
// "[ -42] [-42 ] [ -42] [-042] [-042]"

字符串转换

  • 将 C 的字符串,转换成 OC 的字符串
// 1. stringWithCString:
char *S = "\xc3\x8a";
NSString *str1 = [NSString stringWithCString:S encoding:NSUTF8StringEncoding];
NSLog(@"%@",str1);

// 2. stringWithUTF8String:
//返回一个UTF—8编码的C字符串,参数为 char * 类型
NSString *str2 = [NSString stringWithUTF8String:"长破折号:\xe2\x80\x94"];
NSLog(@"%@",str2);

输出结果:

2017-02-10 11:00:41.522750 16NSString[823:24097] Ê

2017-02-10 11:00:41.523131 16NSString [823:24097] ** 长破折号: **—

另外,关于 stringWithUTF8String:

官方文档描述:Returns a string created by copying the data from a given C array of UTF8-encoded bytes.

通过复制一个给定的 UTF-8 编码的 C 数组来返回字符串。

stackoverflow:What does stringWithUTF8String: do and how it works

  • 字符串转换为基本数据类型
NSString *str = @"3.14";

// double
double d = [str doubleValue];
NSLog(@"double:%lf",d);

// float
float f = [str floatValue];
NSLog(@"float:%f",f);

// int
int i = [str intValue];
NSLog(@"int:%d",i);

// NSInteger
NSInteger integer = [str integerValue];
NSLog(@"NSInteger:%ld",integer);

// long long
long long l = [str longLongValue];
NSLog(@"long long:%lld",l);

// BOOL
NSString *string = @"YES";
BOOL b = [string boolValue]; 
  • 大小写转换
// uppercaseString:全部大写
NSString *string1 = @"hEllo";
NSLog(@"upper:%@",[string1 uppercaseString]);

// lowercaseString:全部小写
NSLog(@"lower:%@",[string1 lowercaseString]);

// capitalizedString: 首字母大写
NSLog(@"capital:%@",[string1 capitalizedString]);

一定不要使用 NSString-uppercaseString 或者 -lowercaseString 的方法来处理 UI 显示的字符串,而应该使用 -uppercaseStringWithLocale 来代替, 比如:

NSString *name = @"Tómas";
cell.text = [name uppercaseStringWithLocale:[NSLocale currentLocale]];

追加拼接字符串

NSString *string1 = @"Android";
NSString *string2 = @"ios";

// stringWithFormat: 类方法创建,=》Android & ios
NSString *string11 = [NSString stringWithFormat:@"%@ & %@",string1,string2]

// stringByAppendingString: 追加字符串 =>Androidios
NSString *string22 = [string1 stringByAppendingString:string2];  

// stringByAppendingFormat: 格式化追加字符串 => Android追加字符串ios
NSString *string33 =[string1 stringByAppendingFormat:@“追加字符串%@”,string2];

字符串的截取

NSString *string16 = @"andywhm@163.com";
// substringToIndex:从起始位置截取到指定位置(不包含指定位置)
NSString *substring1 = [string16 substringToIndex:7];

// substringFromIndex:从指定位置截取到字符串的末尾(包含指定位置)
NSString *substring2 = [string16 substringFromIndex:8];

// substringWithRange:截取中间部分的字符串 @163
// NSRange range = {location,length};
// NSRange range = {7,4};
NSRange range = NSMakeRange(7, 4);
NSString *substring3 = [string16 substringWithRange:range];
  • 截取字符串示例代码
    <!–hexoPostRenderEscape:
     // 日期区间 201709----201709
    NSString *string = @"201709----201710";
    NSUInteger strLength = string.length;
    NSLog(@"%lu",(unsigned long)strLength); // 16

NSString *beginDateStr = [string substringToIndex:6];
NSLog(@"beginData = %@",beginDateStr); // beginData = 201709

NSString *endDateStr = [string substringFromIndex:strLength-6];
NSLog(@"endData = %@",endDateStr); // endData = 201710
:hexoPostRenderEscape–>
💡提示:字符串截取之前最好判断字符串长度是否满足要求或者正则表达式校验字符串内容,否则很容易引起越界异常。

字符串的查找并替换

NSString *email = @"andywhm@163.com";
NSString *searchString = @"@163";

// rangeOfString: 查找并返回接收器中给定字符串第一次出现的范围。
NSRange range = [email rangeOfString:searchString];
NSLog(@"被查找字符串在接受字符串中的起始点:%lu",range.location);
NSLog(@"被查找字符串在接受字符串中的长度:%lu",range.length);

if (range.location == NSNotFound) {
    NSLog(@"字符串没有找到!");
}else {
    // 将指定范围内的字符串替换为新的字符串
    NSString *newString = [email stringByReplacingCharactersInRange:range withString:@"@qq"];
    NSLog(@"替换后的字符串为:%@",newString);
}
  • 正则表达式查找字符串
// 待匹配字符串
NSString *string = @"201709----201710";
// 返回匹配到的第一个字符串范围
NSRange range = [string rangeOfString:@"\\d+" options:NSRegularExpressionSearch];
if (range.location != NSNotFound) {
    NSLog(@"%@",[string substringWithRange:range]); // 201709
}
  • 在一个字符串中搜索子字符串,最灵活的方式是:
- (NSRange)rangeOfString:(NSString *)aString
                 options:(NSStringCompareOptions)mask
                   range:(NSRange)searchRange
                  locale:(NSLocale *)locale;

字符串替换

  • stringByReplacingCharactersInRange:withString:

  • stringByReplacingOccurrencesOfString:withString: 接收器中目标字符串的所有出现的都将被另一个给定字符串替换。

    NSString *email = @"andywhm@163.com";
    NSString *newString2 = [email stringByReplacingOccurrencesOfString:@"163.com" withString:@"qq.com"];
    NSLog(@"替换后的字符串为:%@",newString2);
  • 拓展:使用下面这个方法整体替换字符串还可以设置替换的区域。 

stringByReplacingOccurrencesOfString:(NSString *)withString:(NSString *) options:(NSStringCompareOptions) range:(NSRange)

字符串的隐藏显示

// 截取字符串,再用*填充
NSString *string = @"123456";
NSUInteger length = string.length;
string = [string substringToIndex:3];
string = [string stringByPaddingToLength:length withString:@"*" startingAtIndex:0];
NSLog(@"string = %@",string);
// string = 123***

// 隐藏身份号码,显示前5位和后2位
string = [string stringByReplacingCharactersInRange:NSMakeRange(6, 8) withString:@"********"];
// string = 33030********80

字符串的删除

NSMutableString *email = [NSMutableString stringWithString:@"andywhm@163.com"];
NSRange range = [email rangeOfString:@"@163"];
// NSRange range2 = NSMakeRange(7, 4);
[email deleteCharactersInRange:range];

字符串的插入

NSMutableString *email = [NSMutableString stringWithString:@"andywhm@163.com"];
[email insertString:@"12345" atIndex:2];
NSLog(@"new email:%@",email);

输出结果:

new email:an12345dywhm@163.com

字符串拷贝

NSMutableString *mutableStr1 = [NSMutableString stringWithString:@"Hello"];
NSMutableString *mutableStr2;

//  两个指针同时指向同一块内存
mutableStr2 = mutableStr1;
[mutableStr2 appendString:@" World"];

NSLog(@"mutableStr1:%@",mutableStr1);
NSLog(@"mutableStr2:%@",mutableStr2);

输出结果:

mutableStr1:Hello World

mutableStr2:Hello World

字符串的获取及遍历

  • 获取字符串长度:length
//length : 获取字符串的字符个数
NSString *string1 = @"abcdefg";
NSUInteger length = [string1 length]; //length = 7

<!–hexoPostRenderEscape:

* 基本多文种平面外的字符:记住,BMP 里所有的字符在 UTF-16 里都可以用一个码元表示。所有其余的字符都需要两个码元(一个代理对)。基本上所有现代使用的字符都在 BMP 里,因此在实际中很难遇到代理对。然而,几年前随着 emoji 被引入 Unicode(在 1 号平面),这种情况已经有所变化。

  • 组合字符序列:如果字母 é 是以分解形式(e + ´)编码的,算作两个码元。

  • 变体序列:它们和分解形式的组合字符序列的工作方式一样,因此变体选择符也算作单独的字符。
    ——摘自 ObjC中国-NSString 与 Unicode

  • 获取特定字符:``characterAtIndex:``
    :hexoPostRenderEscape–>
    //characterAtIndex : 通过下标获取字符串中的特定字符
    NSString *string1 = @”abcdefg”;
    unichar result = [string1 characterAtIndex:2];
    NSLog(@”result = %c”,result); //result = c
    <!–hexoPostRenderEscape:

  • 遍历字符串: ```characterAtIndex: ```
    :hexoPostRenderEscape–>
    // 结合以上两个方法,通过循环遍历字符串
    NSString *str8 = @”Communication”;

// 遍历字符串中的每一个字符
for (int i = 0; i<str8.length; i++) {
unichar c = [str8 characterAtIndex:i];
NSLog(@”index:%d,value:%c”,i,c);
}


> 用 ```characterAtIndex: ``` 方法以索引方式直接访问 unichar 可能会有问题。字符串可能会包含组合字符序列、代理对或变体序列。幸运的是,**NSString** 有更好地方式:```enumerateSubstringsInRange:options:usingBlock: ```方法。

    NSString *s = @"The weather on \U0001F30D is \U0001F31E today.";
    // The weather on 🌍 is 🌞 today.
    NSRange fullRange = NSMakeRange(0, [s length]);
    [s enumerateSubstringsInRange:fullRange
                          options:NSStringEnumerationByComposedCharacterSequences
                       usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop)
    {
        NSLog(@"%@ %@", substring, NSStringFromRange(substringRange));
    }];



###  字符串的比较

* 判断字符串是否相等:``isEqualToString:``
```isEqual:``` 和 ```isEqualToString:``` 这两个方法都是一个字节一个字节地比较的。

NSString *string1 = @”abcd”;
NSString *string2 = @”ABCD”;

// == 是比较两个字符串的指针地址,而不是比较字符串的内容
if (string1 == string2) {
NSLog (@” 两个指针指向同一块内存”);
}

//isEqualToString:比较指针变量所指的地址内容,区分大小写
BOOL isEqual = [string1 isEqualToString:string2];
if (isEqual) {
NSLog (@” 相等”);
}else {
NSLog (@” 不等”);
}


复杂的情况:如果希望字符串的合成和分解的形式相吻合,得先自己正规化:

NSString *s = @”\u00E9”; // é
NSString *t = @”e\u0301”; // e + ´
BOOL isEqual = [s isEqualToString:t];
NSLog(@”%@ is %@ to %@”, s, isEqual ? @”equal” : @”not equal”, t);
// => é is not equal to é

// Normalizing to form C
NSString *sNorm = [s precomposedStringWithCanonicalMapping];
NSString *tNorm = [t precomposedStringWithCanonicalMapping];
BOOL isEqualNorm = [sNorm isEqualToString:tNorm];
NSLog(@”%@ is %@ to %@”, sNorm, isEqualNorm ? @”equal” : @”not equal”, tNorm);
// => é is equal to é


> 另一个选择是使用 ```compare:``` 方法(或者它的其它变形方法,比如:```localizedCompare:```),这个方法返回一个和它相容等价的字符串。对此,苹果没有很好地写入文档。请注意,你常常还需要作标准等价的比较。```compare:``` 没法作这个比较。

NSString *s = @”ff”; // ff
NSString *t = @”\uFB00”; // ff ligature
NSComparisonResult result = [s localizedCompare:t];
NSLog(@”%@ is %@ to %@”, s, result == NSOrderedSame ? @”equal” : @”not equal”, t);
// => ff is equal to ff


* 字符串以开头比较:``hasPrefix:``

if ([string1 hasPrefix:@”A”]) {
NSLog (@” 字符串 string1 以 A 开头”);
}


* 字符串以结尾比较:``hasSuffix:``

if ([string1 hasSuffix:@”D”]) {
NSLog (@” 字符串 string1 以 D 结尾”);
}


* 区分大小写比较,3种返回值:``compare: ``

// NSOrderedSame: 判断两者内容是否相等
BOOL isSame = [string1 compare:string2] == NSOrderedSame;

// NSOrderedAscending: 判断两对象值的大小 (按字母顺序进行比较,string2 大于 string1 为真)
BOOL isAscending = [string1 compare:string2] == NSOrderedAscending;

// NSOrderedAscending: 判断两对象值的大小 (按字母顺序进行比较,string2 小于 string1 为真)
BOOL isDescending = [string1 compare:string2] == NSOrderedDescending;


* 不区分大小写比较,3种返回值:``caseInsensitiveCompare:``

NSComparisonResult result = [string1 caseInsensitiveCompare:(string2)];
switch (result) {
case NSOrderedSame:
NSLog (@” 不区分大小写,两者相同”);
break;

case NSOrderedDescending:
    NSLog(@"降幂");
    break;

case NSOrderedAscending:
    NSLog(@"升幂");
    break;

default:
    NSLog(@"无法判断");
    break;

* 带参数的比较:``compare: options:``

// NSNumericSearch: 比较字符串的字符个数,而不是字符值
BOOL results = [string1 compare:string2 options:NSNumericSearch] == NSOrderedSame;


* 复杂情况下的比较:
  • (NSComparisonResult)compare:(NSString *)string
                    options:(NSStringCompareOptions)mask
                      range:(NSRange)rangeOfReceiverToCompare
                     locale:(id)locale;
    <!–hexoPostRenderEscape:

字符串的文件操作(读写)

> 纯文本文件,和文件格式或者网络协议应该选择 UTF-8 编码,除非有特别的需要只能用其它的编码。要向文件中写入文本,使用 ```writeToURL:atomically:encoding:error: ```这个方法。

> 这个方法会在 UTF-16UTF-32 编码的文件上自动加上字节顺序标记。它还会把文件的编码存储在名为 com.apple.TextEncoding 的扩展文件属性里。鉴于 ```initWithContentsOf…```: ```usedEncoding:error:``` 方法知道有这个属性,当你从文件里载入文本时,使用标准的 NSString 方法就能让确保使用正确的编码更加容易。

  • 从文件读取字符串:``initWithContentsOfFile:``
    :hexoPostRenderEscape–>
    NSString *path = @”string.text”;
    NSString *string = [[NSString alloc] initWithContentsOfFile:path];
    NSLog(@”string:%@”,string);
    [string release]; //string=nil;

    // 用于保存 NSError 对象地址的局部指针变量
    NSError *error;
    NSString *myEssay = [[NSString alloc] initWithContentsOfFile:@”/some/path/file”

                                                  encoding:NSUTF8StringEncoding 
                                                     error:&error];

    if (!myEssay) {
    NSLog(@”Error reading file:%@”,[error localizedDescription]);
    }
    <!–hexoPostRenderEscape:

  • 写字符串到文件:``writeToFile:``
    :hexoPostRenderEscape–>
    NSString *string = [[NSString alloc] initWithString:@”This is a String!”];
    NSString *path = @”string.text”;
    [string writeToFile: path atomically: YES];
    [string release]; //string=nil;
    <!–hexoPostRenderEscape:

    // 用于保存 NSError 对象地址的局部指针变量
    NSError *error;
    NSString *someString = @"Text Data";
    BOOL success = [someString writeToFile:@"some/path/file"

                              atomically:YES
                                encoding:NSUTF8StringEncoding
                                   error:&amp;error];

    if (!success) {

      NSLog(@&quot;Error writing file:%@&quot;,[error localizedDescription]);

    }

ios5 不支持release:iOS应用开发:什么是ARC?

  • 扩展路径
    :hexoPostRenderEscape–>
    NSString *Path = @”~/NSData.txt”;
    NSString *absolutePath = [Path stringByExpandingTildeInPath];
    NSLog(@”absolutePath:%@”,absolutePath);
    NSLog(@”Path:%@”,[absolutePath stringByAbbreviatingWithTildeInPath]);
    <!–hexoPostRenderEscape:
  • 文件扩展名
    :hexoPostRenderEscape–>
    NSString *Path = @”~/NSData.txt”;
    NSLog(@”Extension:%@”,[Path pathExtension]);
    <!–hexoPostRenderEscape:

    > 文件路径
    简单来说就是我们不应该使用 ```NSString``` 来描述文件路径。对于 OS X 10.7 和 iOS 5,```NSURL``` 更便于使用,而且更有效率,它还能缓存文件系统的属性。

>再者,```NSURL``` 有八个方法来访问被称为 resource values 的东西。这些方法提供了一个稳定的接口,使我们可以用来获取和设置文件与目录的各种属性,例如本地化文件名(```NSURLLocalizedNameKey```)、文件大小(```NSURLFileSizeKey```),以及创建日期(```NSURLCreationDateKey```),等等。

>尤其是在遍历目录内容时,使用 ```-[NSFileManager enumeratorAtURL:includingPropertiesForKeys:options:errorHandler:]```,并传入一个关键词(keys)列表,然后用 ```-getResourceValue:forKey:error:``` 检索它们,能带来显著的性能提升。

下面是一个简短的例子展示了如何将它们组合在一起:

````
NSError *error = nil;
NSFileManager *fm = [[NSFileManager alloc] init];
NSURL *documents = [fm URLForDirectory:NSDocumentationDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:&error];
NSArray *properties = @[NSURLLocalizedNameKey, NSURLCreationDateKey];
NSDirectoryEnumerator *dirEnumerator = [fm enumeratorAtURL:documents
includingPropertiesForKeys:properties
options:0
errorHandler:nil];
for (NSURL *fileURL in dirEnumerator) {
NSString *name = nil;
NSDate *creationDate = nil;
if ([fileURL getResourceValue:&name forKey:NSURLLocalizedNameKey error:NULL] &&
[fileURL getResourceValue:&creationDate forKey:NSURLCreationDateKey error:NULL])
{
NSLog(@"'%@' was created at %@", name, creationDate);
}
}
:hexoPostRenderEscape–>

我们把属性的键传给 -enumeratorAtURL:... 方法中,在遍历目录内容时,这个方法能确保用非常高效的方式获取它们。在循环中,调用 -getResourceValue:... 能简单地从 NSURL 得到已缓存的值,而不用去访问文件系统。

参考

欢迎关注我的其它发布渠道