String Programming Guide 3 (完)

Character Sets

NSCharacterSet, NSMutableCharacterSet

Character Set Basics

NSString 和 NSScanner 都有通过 NSCharacterSet 来实现其查找排序等功能

NSString *myString = @"some text in an NSString ...";
NSCharacterSet *characterSet = [NSCharacterSet uppercaseLetterCharacterSet];
NSRange letterRange = [myString rangeOfCharacterFromSet:characterSet];
NSLog(@"%lu,%lu",letterRange.location,letterRange.length);
//OUTPUT: 2015-06-11 13:38:41.306 XYZPersonTest[920:59408] 16,1

Creating Character Sets

使用[NSCharacterSet ...CharacterSet]等方法创建的 NSCharacterSet 实例是不可变的, 即使赋值给一个 NSMutableCharacterSet 对象指针,但是可以使用 mutableCopy 方法来复制一份给新的指针:

NSMutableCharacterSet *workingSet = [[NSCharacterSet alphanumericCharacterSet]mutableCopy];
[workingSet addCharactersInString:@";:,."];
NSCharacterSet *finalSet = [workingSet copy];

还可以使用 Unicode 码通过 NSString 创建 NSCharacterSet

UniChar chars[]  ={0x000C, 0x2028};
NSString *string = [[NSString alloc]initWithCharacters:chars length:sizeof(chars)/sizeof(UniChar)];
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:string];

Performance considerations

  • 尽量少使用 NSMutableCharacterSet

  • 经常使用的可以考虑缓存起来(使用全局变量等)

  • 如果一个 NSMutableCharacterSet 已经修改好了, 使用copy 创建一个 NSCharacterSet

  • 尽量不要归档\压缩, 考虑使用单独的文件保存

Creating a character set file

NSData *charSetRep = [finalSet bitmapRepresentation];
NSURL *dataURL;
NSError *error;
BOOL result = [charSetRep writeToURL:dataURL options:NSDataWritingAtomic error:&error];

读取: characterSetWithContentsOfFile:

Standard Character Sets and Unicode Definitions

暂时用到:

letterCharacterSet

lowercaseLetterCharacterSet

uppercaseLetterCharacterSet

alphanumericCharacterSet


Scanner

Creating a Scanner

[NSScanner scannerWithString:aString]

[NSScanner localizedScannerWithString:aString]

创建之后的Scanner开始位置就在 string 的开头

NSString *aString = @"137.3 small cases of bananas";
float aFloat;
NSScanner *theScanner = [NSScanner scannerWithString:aString];
while ([theScanner isAtEnd]==NO) {
    [theScanner scanFloat:&aFloat];
    //implementation continues...
}

使用 SetCaseSensitive: 方法可以设置是否区分大小写,默认是不区分大小写的

Using a Scanner

调用一次 scanner 之后,指针将会停留在结果值的后一位(如果有的话)

例如上面的例子,scanner 在第一次被调用后指针停留在137.3后面一位的空格

也可以修改指针的位置,使用 setScanLocation: ,可以跳过一段长度的字符(往前往后都可以)

 

NSString *banana = @"137 small cases of bananas";
NSString *separatorString =@" of";
NSScanner *aScanner = [NSScanner scannerWithString:banana];

NSInteger anInteger;
[aScanner scanInteger:&anInteger];
NSString *container;
[aScanner scanUpToString:separatorString intoString:&container];

NSLog(@"%ld,%@",anInteger,container);
//OUTPUT: 2015-06-11 15:42:12.470 XYZPersonTest[1048:82680] 137,small cases

[aScanner scanString:separatorString intoString:NULL];
NSString *product;
product = [[aScanner string]substringFromIndex:[aScanner scanLocation]];

NSLog(@"%@",product);
//OUTPUT: 2015-06-11 15:46:48.905 XYZPersonTest[1069:83584]  of bananas


Example

NSString *aString = @"Product: Acme Potato Peeler; Cost: 0.98 73";
NSString *bString = @"Product: Chef Pierre Pasta Fork; Cost: 0.75 19";
NSString *cString = @"Product: Chef Pierre Colander; Cost: 1.27 2";
NSString *exampleString = [NSString stringWithFormat:@"%@\n%@\n%@",aString,bString,cString];
NSCharacterSet *semicolonSet;
NSScanner *theScanner;
NSString *PRODUCT = @"Product:";
NSString *COST = @"Cost:";
NSString *productName;
float productCost;
NSInteger productSold;
semicolonSet = [NSCharacterSet characterSetWithCharactersInString:@";"];
theScanner = [NSScanner scannerWithString:exampleString];
while ([theScanner isAtEnd]==NO) {
    if ([theScanner scanString:PRODUCT intoString:NULL] &&
        [theScanner scanUpToCharactersFromSet:semicolonSet intoString:&productName] &&
        [theScanner scanString:@";" intoString:NULL] &&
        [theScanner scanString:COST intoString:NULL] &&
        [theScanner scanFloat:&productCost] &&
        [theScanner scanInteger:&productSold]
        ){
        NSLog(@"Sales of %@: $%1.2f",productName,productCost*productSold);
    }
}
/**
 OUTPUT:
 2015-06-11 16:01:10.672 XYZPersonTest[1081:86284] Sales of Acme Potato Peeler: $71.54
 2015-06-11 16:01:10.673 XYZPersonTest[1081:86284] Sales of Chef Pierre Pasta Fork: $14.25
 2015-06-11 16:01:10.673 XYZPersonTest[1081:86284] Sales of Chef Pierre Colander: $2.54
 
 */

Localization

scanner 内部的一些行为与区域有关, 如果程序涉及到这些处理, 需要改用[NSScanner localizedScannerWithString:aString]来创建 Scanner

或者使用 setLocale: 来设置区域


String Representations of File Paths

NSString 提供了很多用来处理文件系统路径的方法, 可以展开一个路径字符串(目录,文件名,文件拓展名,"~me"这种不知道叫啥,创建用户目录,清除符号链接,清除多余的斜杠,清除上级和当前目录".","..")

如果可以的话尽量使用 NSURL 来代替 NSString 来表示路径, 效率更加高


Reoresenting a Path

一般来说,'/'是目录分隔符,'.'是文件拓展名分隔符,绝对路径开头是'/',用户路径开头'~用户名/'

使用 stringByStandardizingPath 创建一个路径字符串,他还可以顺便帮你处理:

  • 展开用户路径符号'~用户名';

  • 清除没啥用的空白和多余的斜杠(如'//','/./');

  • 自动处理路径字符串中的上级目录'../'符号;

NSString *path = @"/user/bin/./grep";
NSString *standardizedPath = [path stringByStandardizingPath];
NSLog(@"%@",standardizedPath);
//OUTPUT: 2015-06-12 10:25:51.590 XYZPersonTest[651:19821] /user/bin/grep
path = @"~adolsai";//当前用户存在才能自动转成绝对路劲?
standardizedPath = [path stringByStandardizingPath];
NSLog(@"%@",standardizedPath);
//OUTPUT: 2015-06-12 10:28:55.388 XYZPersonTest[668:20344] /Users/adolsai
path = @"~me";//这个就不行了
standardizedPath = [path stringByStandardizingPath];
NSLog(@"%@",standardizedPath);
//OUTPUT: 2015-06-12 10:30:52.339 XYZPersonTest[677:20978] ~me
path = @"/user/include/objc/..";
standardizedPath = [path stringByStandardizingPath];
NSLog(@"%@",standardizedPath);
//OUTPUT: 2015-06-12 10:32:16.096 XYZPersonTest[685:21428] /user/include
path = @"/private/usr/include";//这个不行,为啥捏
standardizedPath = [path stringByStandardizingPath];
NSLog(@"%@",standardizedPath);
//OUTPUT: 2015-06-12 10:33:08.933 XYZPersonTest[693:21630] /private/usr/include

User Directories

使用stringByExpandTildePath展开用户目录

NSString *meHome = [@"~adolsai" stringByExpandingTildeInPath];
NSLog(@"%@",meHome);
//OUTPUT: 2015-06-12 10:55:30.329 XYZPersonTest[716:24666] /Users/adolsai
NSString *mePublic = [@"~adolsai/Public" stringByExpandingTildeInPath];
NSLog(@"%@",mePublic);
//OUTPUT: 2015-06-12 11:06:53.504 XYZPersonTest[745:27716] /Users/adolsai/Public

访问用户目录:

//尽量不要这样进入标准用户文件夹,这里使用用户目录下的"Documents"文件夹
NSString *documentsDirectory = [NSHomeDirectory() stringByAppendingString:@"/Documents"];
NSLog(@"%@",documentsDirectory);
//OUTPUT: 2015-06-12 12:30:37.967 XYZPersonTest[819:43322] /Users/adolsai/Documents
//应该直接用 NSSearchPathForDirectoriesInDomains来定位当前用户的标准文件夹
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES);
if ([paths count]>0){
    documentsDirectory = [paths objectAtIndex:0];
}
NSLog(@"%@",documentsDirectory);
//OUTPUT: 2015-06-12 12:30:37.967 XYZPersonTest[819:43322] /Users/adolsai/Library/Documentation

 Paths Components

三个比较常用的 NSString 中截取路径的方法:

NSString *documentPath = @"~adolsai/Public/Demo/readme.txt";
NSString *documentDirectory = [documentPath stringByDeletingLastPathComponent];
NSLog(@"%@",documentDirectory);
//OUTPUT: 2015-06-12 12:41:05.655 XYZPersonTest[855:46465] ~adolsai/Public/Demo

NSString *documentFilename = [documentPath lastPathComponent];
NSLog(@"%@",documentFilename);
//OUTPUT: 2015-06-12 12:41:05.656 XYZPersonTest[855:46465] readme.txt

NSString *documentExtension = [documentPath pathExtension];
NSLog(@"%@",documentExtension);
//OUTPUT: 2015-06-12 12:41:05.656 XYZPersonTest[855:46465] txt

File Name Completion

在某个目录下匹配文件用的

//"/Users/adolsai/Public/Demo/" 目录下有"ReadMe.txtreadme.rtfreadme.txt1recondite.txttext.txt
文件
//这里不知道为什么不能用用户路径,我用~adolsai/Public/Demo/r的话会找不到文件....
NSString *partialPath = @"/Users/adolsai/Public/Demo/r";
NSString *longestCompletion;
NSArray *outputArray;
unsigned long allMatches = [partialPath completePathIntoString:&longestCompletion caseSensitive:NO matchesIntoArray:&outputArray filterTypes:nil];
NSLog(@"%ld,%@,%@",allMatches,longestCompletion,outputArray);
/**
 OUTPUT:
 2015-06-12 12:53:53.429 XYZPersonTest[954:50337] 4,/Users/adolsai/Public/Demo/re,(
 "/Users/adolsai/Public/Demo/readme.rtf",
 "/Users/adolsai/Public/Demo/ReadMe.txt",
 "/Users/adolsai/Public/Demo/readme.txt1",
 "/Users/adolsai/Public/Demo/recondite.txt"
 )
 */

选择指定的文件类型:

NSArray *filterTypes = @[@"txt",@"rtf"];
allMatches = [partialPath completePathIntoString:&longestCompletion caseSensitive:NO matchesIntoArray:&outputArray filterTypes:filterTypes];
NSLog(@"%ld,%@,%@",allMatches,longestCompletion,outputArray);
/*
 OUTPUT
 2015-06-12 14:21:49.484 XYZPersonTest[1140:66258] 3,/Users/adolsai/Public/Demo/re,(
 "/Users/adolsai/Public/Demo/readme.rtf",
 "/Users/adolsai/Public/Demo/ReadMe.txt",
 "/Users/adolsai/Public/Demo/recondite.txt"
 )
 */


Drawing Strings

可以直接使用 NSView 的drawAtPoint:withAttributes:函数来绘制 NSString 到 NSView 里面

这里的 attributes 可以参考 NSAttributesString

也可以使用 NSLayoutManager 来绘制,参考Cocoa Text Architeure Guide

标签:ios, object-c, nsstring