第8章 Foundation Kit介绍

2023-04-25,,

本文转载至  http://blog.csdn.net/mouyong/article/details/16947321

Objective-C是一门非常精巧实用的语言,目前我们还没有研究完它提供的全部功能。不过现在,我们先探索另一个方向,快速了解一下Cocoa中的Foundation框架。尽管Foundation框架只是Cocoa的一部分,没有内置于Objective-C的语言中,但是它依然十分重要,在本书中有必要对它进行讲解。
通过第2章我们知道,Cocoa实际上是由许多个不同的框架组成的,其中最常用于桌同端应用程序的是Foundation和Application Kit。它包含了iOS应用程序所需要的所有界面对象。
稳固的Foundation
Foundation,就是两类UI框架的基础,因为它不包含UI对象,所以它的对象可以在iOS或OS X应用程序中兼容。
Foundation框架中有很多有用的、面向数据的简单类和数据类型,我们将会讨论其中的一部分,例如NSString、NSArray、NSEnumerator和NSNumber。Foundation框架拥有100多个类,所有的类都可以在Xcode安装文档中找到。你可以在Xcode的Organizer窗口选择Documentation选项卡,来查看这些文档。
Foundation框架是以另一个框架CoreFoundation为基础创建的。CoreFoundation框架是用纯C语言写的,你愿意的话也可以使用它,不过本书不予讨论。在介绍名称相似的框架时不要混淆。如果函数或变量名以CF开头,那它们就是CoreFoundation框架中的。其中很多都可以在Foundation框架中找到相对应的,它们之间的转换也非常简单。
使用项目样本代码
在继续学习之前,有一点需要注意,即在本章及以后章节将提到的项目中,我们仍会创建基于Foundation模板的项目,不过,以下默认的样本代码都会保留。
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{

@autoreleasepool {
        
        // insert code here...
        NSLog(@"Hello, World!");
        
    }
    return 0;
}
来看一下这段代码。main()函数后面首先是关键字@autoreleasepool,并且所有代码都写在了关键字和return语句之间的两个大括号中。这只是Cocoa内存管理的冰山一角,下一章会详细介绍。所以现在,你只要一笑而过,与此关键字相关的内容先放一边。当然,你不使用它也不至于引发问题,只不过在程序运行时会冒出一些奇怪的信息。
一些有用的数据类型
在深入研究Cocoa的类之前,先看看Cocoa为我们提供的一些结构体(struct)。
范围
第一个结构体是NSRange。
typedef struct _NSRange
{
    unsigned int location;
    unsigned int length;
} NSRange
这个结构体用来表示相关事物的范(位置和长度),通常都是字符串里某个字符的位置或者数组里的元素位置location字段存放该范围的起始位置,而length字段则是该范围内所含元素的个数。在字符串“Objective-C is a cool language”中,单词cool可以用location为17,length为4的范围来表示。location还可以用NSNotFound这个值来表示没有范围,比如变量没有初始化。
创建新的NSRange有三种方式。第一种,直接给字段赋值:
NSRange range;
range.location=17;
range.length=4;
第二种,应用C语言的聚合结构赋值机制
NSRange range={17,4};
第三种方式是Cocoa提供的一个快捷函数NSMakeRange():
NSRange range=NSMakeRange(17,4);
几何数据类型
之后你会经常看到用来处理集合图形的数据类型,它们的名称都带有CG前缀,如CGPoint和CGSize。这些类型是由Core Graphics框架提供的,用来进行2D渲染。Core Graphics是用C语言所写的,因此可以在代码中使用C语言的数据类型。CGPoint表示的是笛卡尔平面中的一个坐标(x,y)。
struct CGPoint{
    float x;
    float y;
};
CGSize用来存储长度和宽度:
struct CGSize
{
    float width;
    float height;
};
在之前Shapes相关的程序中,我们本可以使用一个CGPoint和一个CGSize而不是自定义的表示矩形的struct来表示形状,不过当时我们想让程序尽可能简单。Cocoa提供了一个矩形数据类型,它由坐标和大小复合而成。
struct CGRect
{
    CGPoint origin;
    CGSize size;
}
Cocoa也为我们提供了创建这些数据类型的快捷函数CGPointMake()、CGSizeMake()和CGRectMake()。
说明:为什么这些数据类型是C的struct结构体而不是对象呢?原因就在于性能。程序(尤其是GUI程序)会用到许多临时的坐标、大小和矩形区域来完成工作。记住,所有的Objective-C对象都是动态分配的,而动态分配是一个代价较大的操作,它会消耗大量的时间。所以将这些结构体创建成第一级的对象会在使用过程中大大增加系统开销。
字符串
我们要介绍的第一个真正的类是NSString,也就是Cocoa中用来处理字符串的类。字符串其实就是一组人类可读的字符序列。由于计算机与人类进行交互,因此最好让它们可以有一个可以存储和处理人类可读文本的方式。我们之前已经见过NSString型数据了,它们是特殊的NSString字面量,其标志为双引号内字符串前面的@符号,例如@”Hi!”。这些字面量字符串与你在编程过程中创建的NSString并无差别。
假如你曾经写过C语言的字符串处理,那么你一定会明白其中的痛苦。C语言将字符串作为简单的字符数组进行处理,并且在数组最后添加尾部的零字节作为结束标志。而Cocoa中的NSString则有很多内置方法,简化了字符串的处理。
创建字符串
我们知道printf()和NSLog()之类的函数会接受格式字符串和一些参数来输出格式化的结果。NSString的stringWithFormat:方法就是这样通过格式字符串和参数来创建NSString的。
+(id) stringWithFormat:(NSString *)format,...;
你可以按如下方式创建一个新的字符串:
NSString *height;
height=[NSString stringWithFormat:@”Your height is %d feet, %d inches”,5,11];
得到的字符串是”Your height is 5 feet, 11 inches”。
类方法
stringWithFormat:的声明中有两个值得注意的地方。第一个是定义最后的省略号(...),它告诉我们和编译器这个方法可以接受多个以逗号隔开的其他参数,就像printf()和NSLog()一样。
而另一个古怪的地方是声明语句中有一个非常特别的起始字符:加号。Objective-C运行时生成一个类的时候,会创建一个代表该类的类对象。类对象包含了指向超类、类名和类方法列表的指针,还包含一个long类型的数据,为新创建的实例对象指定大小。
如果你在声明方法时添加了加号,就是把这个方法定义为类方法。这个方法属于类对象,通常用于创建新的实例。我们称这种用来创建新对象的类方法为工厂方法。
stringWithFormat:就是一个工厂方法,它根据你提供的参数创建新对象。用stringWithFormat:来创建字符串比创建字符串然后生成所有元素要容易得多。
类方法也可以用来访问全局数据。AppKit(基于OS X平台)中的NSColor类和UIKit(基于iOS平台)中的UIColor类都拥有以各种颜色命名的类方法,比如redColor和blueColor。要用蓝色绘图,可以像下面这样编写代码。
NSColor *haveTheBlues=[NSColor blueColor];

UIColor *blueMan=[UIColor blueColor];
你所创建的大部分方法都是实例方法,要用减号(-)作为前缀来进行声明。这些方法将会在指定的对象实例中起作用,比如获取一个Circle的颜色或者一个Tire的压强。如果某个方法所实现的是很通用的功能,比如创建一个实例对象或者访问一些全局数据,那么最好使用加号(+)作为前缀将它声明为类方法(默然说话:感觉加号修饰的应该是Java里的静态方法,减号修饰的应该是Java里的公有方法,不知道这样的理解对不对了,接着往后看吧。)。
关于大小
NSString中另一个好用的方法是length,它返回的是字符串中的字符个数。
-(NSUInteger) length;
可以这样使用它:
NSUInteger length=[height length];
也可以在表达中使用它,如下所示:
if([height length] > 35){
    NSLog(@”wow, you’re really tall!”);
}
说明  NSString的length方法能够精确无误地处理各种语言的字符串,如含有俄文、中文或者日文字符的字符串,以及使用Unicode国际字符标准的字符串。在C语言中处理这些国际字符串是件令人头疼的事情,因为一个字符占用的空间可能多于1个字节,这就意味着如strlen()之类只计算字节数的函数会返回错误的数值。
字符串比较
比较是字符串之间常见的操作。有时你会想知道两个字符串是否相等(比如用户名是否为wmalik),而有时你也会想要看看两个字符串可以怎样排列,以便给姓名列表排序。NSString提供了几个用于比较的方法。
isEqualToString:可以用来比较接收方(receiver,接收消息的对象)和作为参数传递过来的字符串。isEqualToString:返回一个BOOL值(YES或NO)来表示两个字符串的内容是否相同。它的声明如下:
-(BOOL) isEqualToString:(NSString *)aString;
下面是它的使用方法。
NSString *thing1=@”hello 5”;
NSString *thing2=[NSString stringWithFormat:@”hello %d”,5];
if([things1 isEqualToString: thing2]){
    NSLog(@”它们一样!”);
}
要比较两个字符串大小(比如在字符串排序的时候,我们需要知道谁在前谁在后,这时就有比较的问题),可以使用compare:方法,其声明如下。
-(NSComparisonResult)compare:(NSString *) aString;
compare:将接收对象和传递过来的字符串逐个进行比较,它返回一个NSComparisionResult(也就是一个enum型枚举)来显示比较结果。
enum{
    NSOrderedAscending=-1,
    NSOrderedSame,
    NSOrderedDescending
};
typedef NSInteger NSComparisonResult;

如果你曾经用过C语言中的函数qsort()或bsearch(),那么对此应该比较熟悉。如果compare:返回的结果是NSOrderedAscending,那么左侧的数值就小于右侧的数值,即比较的目标在字母表中的排序位置比传递进来的字符串更靠前。比如,[@”aardvark” compare:@”zygote”]将会返回NSOrderedAscending:。
同样,[@”zoinks” compare: @”jinkies”]将会返回NSOrderedDescending。当然,[@”fnord” compare:@”fnord”]返回的是NSOrderedSame。
不区分大小写的比较
compare:进行的是区分大小写的比较。也就是说,@”Bork”和@”bork”的比较是不会返回NSOrderedSame的。我们还有一个方法compare:options:,它能给我们更多的选择权。
-(NSComparisonResult)compare:(NSString *) aString
     options:(NSStringCompareOptions) mask;
options参数是一个掩位码。你可以使用位或bitwise-OR运算符(|)来添加选项标记。一些常用的选项如下。
NSCaseInsensitiveSearch:不区分大小写字符。
NSLiteralSearch:进行完全比较,区分大小写字符。
NSNumbericSearch:比较字符串的字符个数,而不是字符串值。如果没有这个选项,100会排在99的前面,程序员以外的人会觉得奇怪,甚至会觉得它是错的。
假如你想比较字符串,需要忽略大小写并按字符个数进行排序,那么应该这么做:
if([thing1 compare: thing2 options: NSCaseInsensitiveSearch | NSNumbericSearch]==NSOrderedSame){
    NSLog(@”匹配!”);
}
字符串内是否还包含别的字符串
有时你可能想看看一个字符串内是否还包含另一个字符串。例如,你也许想知道某个文件名的后缀名是否是.mov,这样你就知道能否用QuickTime Player打开它;你想要查看文件名是否以draft开头以判断它是否是文档的草稿。有两个方法能帮助你判断:检查字符串是否以另一个字符串开头,判断字符串是否以另一个字符串结尾。
-(BOOL) hasPrefix:(NSString *) aString;
-(BOOL) hasPrefix:(NSString *) aString;
你可以按如下方式使用这两个方法:
NSString *fileName =@”draft-chapter.pages”;
if([fileName hasPrefix: @”draft”]){
    //this is a draft
}
if([fileName hasSuffix: @”.mov”]){
    //this is a movie
}
于是,draft-chapters.pages会被识别为文档的草稿版本(因为它以draft开头),但是不会将它识别为电影(它的结尾是.pages而不是.mov)。
如果你想知道字符串内的某处是否包含其他字符串,请使用rangeOfString:。
-(NSRange)rangeOfString:(NSString *)aString;
将rangeOfString:发送给一个NSString对象时,传递的参数是要查找的字符串。它会返回一个NSRange结构体,告诉你与这个字符串相匹配的部分在哪里以及能够匹配上的字符个数。所以下面的示例
NSRange range=[fileName rangeOfSTring:@”chapter”];
返回的range.location为6,range.length为7.如果传递的参数在接收字符串中没有找到,那么range.location则等于NSNotFound。
可变性
NSString是不可变(immutable)的,这并不意味着你不能操作它。不可变的意思是NSString一旦被创建,便不能改变。你可以对它执行各种各样的操作,例如用它生成新的字符串、查找字符或者将它与其他字符串进行比较,但是你不能以删除字符或者添加字符的方式来改变它。
Cocoa提供了一个NSString的子类,叫做NSMutableString。如果你想改变字符串,请使用这个子类。
说明 Java程序员应该很熟悉这种区别。NSString就像Java中的String一样,而NSMutableString则与Java中的StringBuffer类似。你可以使用类方法stringWithCapacity:来创建一个新的NSMutableString,声明如下:
+(id)stringWithCapacity:(NSUInteger) capacity;
这个容量只是给NSMutableString一个建议,可以超过其大小,就像告诉青少年应几点回家一样。字符串的大小并不仅限于所提供的容量,这个容量仅是个最优值。例如,如果你要创建一个大小为40M的字符串,那么NSMutableString可以预分配一块内存来存储它,这样后续操作的速度就会快很多。可按如下方式创建一个新的可变字符串:
NSMutableString *string=[NSMutableString stringWithCapacity:42];
一旦有了一个可变字符串,就可以对它执行各种操作了。一种常见的操作是通过appendString:或appendFormat:来附加新字符串。
-(void) appendString:(NSString *) aString;
-(void) appendFormat:(NSString *) format, ...;
appendString:接收参数aString,然后将其复制到接收对象的末尾。appendFormat:的工作方式与stringWithFormat:类似,但并没有创建新的字符串,而是将格式化字符串附加在了接收字符串的末尾。例如:
NSMutableString *string=[NSMutableString stringWithCapacity:50];
[string appendString:@”Hello there ”];
[string appendFormat:@”human %d!”,39];
这段代码最后的结果是string被赋值为”Hello there human 39!”。
你也可以使用deleteCharactersInRange:方法删除字符串中的字符。
-(void) deleteCharactersInRange:(NSRange)aRange;
你将来或许会经常把deleteCharactersInRange:方法删除字符串中的字符。
-(void) deleteCharactersInRange: (NSRange) aRange;
NSMutableString是NSString的子类。凭借面向对象编程的优势,你也可以在NSMutableString中使用NSString的所有功能,包括rangeOfString:方法、字符串比较方法等。另外,我们也获得了两个特性。第一个就是任何使用NSString的地方,都可以用NSMutableString来代替。任何NSString可行的场合NSMutableString也能畅通无阻。程序员在使用字符串时完全不必担心它是否是可变的字符串。
另一个特性来自继承,与实例方法一样,继承对类方法也同样适用。查阅关于NSString和NSMutableString的文档,可以学习这两个类中所有方法的全部细节。
集合大家族
Cocoa提供了许多集合类,如NSArray和NSDictionary,它们的实例就是为了存储其他对象而存在的(默然说话:集合与数组的区别不知道大家清楚不?数组在声明的时候必须说清它的大小,且运行过程中数组的大小是不能改变的,这样就导致数组的运用受到一定的局限,比如,你可能必须事先知道你的数据倒底有多少个,否则,你声明出来的数组要么不够用,要么浪费内存。而集合则不必在声明时说明它的大小,因为它在运行时可以随时改变它的大小,这导致集合总有一个大小合适的内存分配。所以集合又有了动态数组的美誉)。
NSArray
NSArray是一个Cocoa类,用来存储对象的有序列表。你可以在NSArray中放入任意类型的对象。
只要拥有一个NSArray对象,就可以通过各种方式来操作它,比如让某个对象的实例变量指向这个数组,将该数组当作参数传递给方法或函数,获取数组中所存对象的个数,提取某个索引所对应的对象,查找数组中的对象,遍历数组等。
NSArray类有两个限制。首先,它只能存储Objective-C的对象,而不能存储原始的C语言基础数据类型,如int、float、enum、struct和NSArray中的随机指针。同时,你也不能在NSArray中存储nil。有很多种方法可以避开这些限制,你马上就会看到。
可以通过类方法arrayWithObjects: 创建一个新的NSArray。发送一个以逗号分隔的对象列表,在列表结尾添加nil代表列表结束,这也是NSArray不能保存nil的原因。
NSArray *array1=[NSArray arrayWithObjects:@”one”,@”two”,@”three”,nil];
这行代码创建了一个由NSString字面量对象组成的含3个元素的数组。你也可以使用数组字面量格式来创建一个数组,它与NSString字面量格式非常类似,区别是用方括号代替了引号,如下所示:
NSArray *array2=@[@”one”,@”two”,@”three”];
使用字面量语法时不必在结尾处特意补上nil。
只要有了一个数组,就可以获取它所包含的对象个数:
-(NSUInteger)count;
也可以获取特定索引处的对象:
-(id)objectAtIndex:(NSUInteger)index;
通过字面量访问数据的语法与C语言中访问数组项的语法类似。
id *myObject=array1[1];
你可以结合计数和取值功能来输出数组中的内容:
for(NSInteger i=0;i<[array1 count];i++){
    NSLog(@”下标 %d 是 %@”,i,[array1 objectAtIndex:i]);
}
你也可以使用数组字面量语法来写以上代码,如下所示。
for(NSInteger i=0;i<[array count];i++){
    NSLog(@”下标 %d 是 %@”,i,array1[i]);
}
可变数组
与NSString一样,NSArray创建的是不可变对象的数组。一旦你创建了一个包含特定数量的对象的数组,它就固定下来了:你既不能添加任何元素也不能删除任何元素。当然数组中包含的对象是可以改变的(比如Car在安全检查失败后可以获得一套新的Tire),但数组对象本身是一直都不会改变的。
为了弥补NSArray类的不足,便有了NSMutableArray这个可变数组类,这样就可以随意地添加或删除数组中的对象了。NSMutableArray通过类方法arrayWithCapacity来创建新的可变数组:
+(id)arrayWithCapacity:(NSUInteger)numItems;
与NSMutableString中的stringWithCapacity:一样,数组容量也只是数组最终大小的一个参考。容量数值之所以存在,是为了让Cocoa能够对代码进行一些优化。Cocoa既不会将对象预写入数组中,也不会用该容量值来限制数组的大小。你可以按照如下方式创建可变数组:
NSMutableArray *array=[NSMutableArray arrayWithCapacity:17];
使用addObject:在数组末尾添加对象:
-(void) addObject:(id) anObject;
还可以删除特定索引处的对象。
-(void) removeObjectAtIndex:(NSUInteger) index;
注意,NSArray中的对象是从零开始编制下标的,这与C数组的规范一样。
删除一个对象之后,数组中并没有留下漏洞。位于被删除对象后面的数组元素都被移来填补空缺了。
可变数组的其他方法也能够用来实现很多出色的操作,例如在特定索引处插入对象、替换对象、为数组排序,还包括了从NSArray所继承的大量好用的功能。
枚举
NSArray经常要对数组中的每个元素都执行同一个操作。比方说,你喜欢绿色,可以让数组中的所有几何形状都变成绿色。你可以编写一个从0到[array count]的循环来读取每个索引处的对象,也可以使用NSEnumerator,Cocoa可以用它来表示集合中迭代出的对象。要想使用NSEnumerator,需通过objectEnumerator向数组请求枚举器:
-(NSEnumerator *)objectEnumerator;
你可以使用这个方法:
NSEnmerator *enumerator=[array objectEnumerator];
如果你想要从后往前浏览某个集合,还有一个reverseObjectEnumerator方法可以使用。
在获得枚举器之后,便可以开始一个while循环,每次循环都向这个枚举器请求它的nextObject:
-(id) nextObject;
nextObject返回nil值时,循环结束。这也是不能在数组中存储nil值的另一个原因:我们没有办法判断nil是存储在数组中的数值还是代表循环结束的标志。
整个循环代码如下所示:
NSEnumerator *enumerator=[array objectEnumerator];
while(id thingie=[enumerator nextObject]){
    NSLog(@”我找到了%@”,thingie);
}
对可变数组进行枚举操作时,有一点需要注意:你不能通过添加或删除对象这类方式来改变数组的容量。如果这么做了,枚举器就会出现混乱,你也会获得未定义结果 (undefined result)。”未定义结果”可以代表任何意思,可以是”e,好象重复了”,可能是”哦,我程序崩溃了”,等等。
快速枚举
在Mac OS X10.5(Leopard)中,苹果公司对Objective-C引入了一些小改进,使语言版本升级到了2.0.我们要介绍的第一个改进叫做快速枚举(fast enumeration),它的语法与脚本语言类似,如下面的代码所示。
for(NSString *string in array){
    NSLog(@”我找到%@”,string);
}
这个循环体将会遍历数组中的每一个元素,并且用变量string来存储每个数组值。它比枚举器语法更加简洁快速。
与Objective-C2.0的其他新特性一样,快速枚举不能在旧的Mac(Mac OS X 10.5之前的)系统上运行。如果你或者你的用户需要在一台不支持Objective-C 2.0及更新版本的电脑上运行你的程序,就不能使用这个新语法。现在这个问题依然存在。
最新版本的苹果编译器(基于Clang和LLVM项目)为纯C语言添加了一个叫做代码块(block)的特性。我们将会在第14章讨论代码块。为了支持代码块功能,苹果公司添加了一个能在NSArray中通过代码块枚举对象的方法,代码如下:
-(void)enumerateObjectsUsingBlock:(void (^)(id obj,NSUInteger idx,BOOL *stop))block
还是使用前面介绍的字符串数组来进行遍历,写下来的代码将是下面这样的:
[array enumerateObjectsUsingBlock:^(NSString *string,NSUInteger index,BOOL *stop){
    NSLog(@”我找到了%@”,string);
}];
这个做法可以让徨操作并发执行,而通过快速枚举,执行操作要一项项地线性完成。
好了,现在我们有4种方法来遍历数组了:通过索引、使用NSEnumerator、使用快速枚举和最新的代码块方法。你会使用哪一种方法呢?
如果你使用的不是10.5版本之前的老爷机,那么建议使用快速枚举或代码块,因为它们更简洁快速。顺便提一下,代码块只在Apple LLVM编译器上才会有效。
如果你还要支持Mac OS X10.4或更早的系统,那就使用NSEnumerator。Xcode有重构功能,可以将代码转换成Objetive-C 2.0,它会自动将NSEnumerator循环转换成快速枚举。
只有在真的需要用索引访问数组时才应使用-objectAtIndex方法,例如跳跃地浏览数组时(每隔两个对象读取一次数组对象)或者同时遍历多个数组时。
NSDictionary
你肯定听说过字典,也许偶尔还会用到它。在编程中,字典是关键字及其定义的集合。Cocoa中有一个实现这种功能的集合类NSDictionary。NSDictionary能在给定的关键字(通常是NSString)下存储一个数值(可以是任何类型的Objective-C对象),然后你就可以用这个关键字来查找相应的数据。因此假如你有一个存储了某人所有联系方式的NSDictionary,那么你可以对这个字典说“给我关键字home-address下的值”或者“给我关键字email-address下的值”。
你可能已经猜到,NSDictionary就像NSString和NSArray一样是不可变的对象。但是NSMutableDictionary类允许你随意添加和删除字典元素。在创建新的NSDictionary时,就要提供该字典所存储的全部对象和关键字。
学习字典的最简单的方法就是用字典字面量语法,它与类方法dictionaryWithObjectsAndKeys:非常相似。
字面量语法即使用类似@{key:value,...}的方法来定义。需要注意它使用的是大括号而不是方括号。还要注意dictionaryWithObjectsAndKeys:后面的参数先是要存储的对象,然后才是关键字,而字面量的语法则是关键字在前,数值在后。关键字和数值之间以冒号分开,而每对键值之间则用逗号区分开。
+(id) dictionaryWithObjectsAndKeys: (id) firstObject, ...;
该方法接收对象和关键字交替出现的序列,以nil值作为终止符号(对,NSDictionary不能存储nil值)。咱们看个实例代码:
Tire *t1=[Tire new];
Tire *t2=[Tire new];
Tire *t3=[Tire new];
Tire *t4=[Tire new];
NSDictionary *tires=[NSDictionary dictionaryWithObjectsAndKeys: t1,@”front-left”,t2,@”front-right”,t3,@”back-left”,t4,@”back-right”,nil];
也可以这样写:
NSDictionary *tires=@{@”front-left”,t1,@”front-right”,t2,@”back-left”,t3,@”back-right”,t4};
使用objectForKey:方法并传递前面用来存储的关键字,就可以访问字典中的数值了:
objectForKey: (id)aKey;
或者是
Tire *tire=tires[@”back-right”];
如果字典里没有右后方的轮胎(假设它是一辆只有3个轮子的奇怪汽车),字典将会返回nil值。
向NSMutableDictionary类发送dictionary消息,便可以创建新的NSMutableDictionary对象,也可以使用dictionaryWithCapacity:方法来创建新的可变字典并告诉Cocoa该字典的最终大小。(有没有发现Cocoa的命名方式很有规律?)
+(id) dictionaryWithCapacity: (NSUInteger) numItems;
与之前提到过的NSMutableString和NSMutableArray一样,在这里,字典的容量也仅仅是一个建议,而不是对其大小的限制。你可以使用setObject:forKey:方法为字典添加元素:
(void) setObject:(id)anObject forKey:(id)aKey;
存储轮胎的字典还有一种创建方法:
NSMutableDictionary * tires=[NSMutableDictionary dictionary];
[tires setObject:t1 forKey:@”front-left”];
[tires setObject:t2 forKey:@”front-right”];
[tires setObject:t3 forKey:@”back-left”];
[tires setObject:t4 forKey:@”back-right”];
如果对字典中已有的关键字使用setObject:forKey:方法,那么这个方法将会用新值替换掉原有的数值。如果想在可变字典中删除一些关键字,可使用removeObjectForKey:方法:
-(void) removeObjectForKey:(id) aKey;
所以,如果想模拟一只轮胎脱落的场景,就可以把那只轮胎从字典中删除:
[tires removeObjectForKey:@”back-left”];
与NSArray一样,没有适用于NSMutableDictionary的字面量初始化语法。
请不要乱来
如果你很有创造力,并跃跃欲试地想要创建NSString、NSArray或NSDictionary的子类,请不要这么做。因为在Cocoa中,许多为实际上是以类簇的方式实现的,即它们是一群隐藏在能用接口之下的与实现相关的类。创建NSString对象时,实际上获得的可能是NSLiteralString、NSCFString、NSSimpleCString、NSBallOfString或者其他未写入文档的与实现相关的对象。
程序员在使用NSString和NSArray时不必在意系统内部到底用的是哪个类,但要给一个类簇创建子类是一件令人非常痛苦和沮丧的事。通常,可以将NSString或NSArray复合到你的某个类中或者使用类别(12章介绍)来解决这种编程中的问题。
其他数值
NSArray和NSDictionary只能存储对象,而不能直接存储任何基本类型的数据,如int、float和struct。不过你可以用对象来封装基本数值,比如将int型数据封装到一个对象中,就可以将这个对象放入NSArray或NSDictionary中了。
如果你想使用对象来处理基本类型,就可以使用NSInteger和NSUInteger。这些类型也要针对32位和64位处理器对数值进行统一。
NSNumber
Cocoa提供了NSNumber类来封装(warp)基本数据类型。可以使用以下类方法创建新的NSNumber:
+(NSNumber *)numberWithChar:(char) value;
+(NSNumber *)numberWithInt:(int)value;
+(NSNumber *)numberWithFloat:(float)value;
+(NSNumber *)numberWithBool:(BOOL)value;
还有许多这样的创建方法,以上只是最常用的方法。
也可以使用字面量语法来创建这些对象:
NSNumber *number;
number=@’X’;
number=@1234;//字符型
number=@1234ul//无符号整型
number=@1234ll//long long
number=@123.45f;//单精度浮点
number=@123.45;//双精度浮点
number=@YES;//布尔值
创建完NSNumber之后,可以把它放入一个字典或数组中:
NSNumber *number=@42;
[array adObject number];
[dictionary setObject: number forKey: @”Bork”];
在将一个基本类型数据封装到NSNumber中后,你可以通过下面的实例方法来重新获得它:
-(char) charValue;
-(int)intValue;
-(float) floatValue;
-(BOOL) boolValue;
-(NSString *)stringValue;
将创建方法和提取方法搭配在一起使用是完全可以的。例如,用numberWithFloat:创建的NSNumber对象可以用intValue方法来提取数值。NSNumber会对数据进行适当的转换。
说明 通常将一个基本类型的数据封装成对象的过程被称为装箱(boxing),从对象中提取基本类型的数据叫拆箱(unboxing)。有些语言有自动装箱的功能,可以自动封装基本类型的数据,也可以自动开箱。(默然说话:Java6.0之后,C#都有自动装箱和拆箱机制)Objective-C不支持自动装箱拆箱功能。
NSValue
NSNumber实际上是NSValue的子类,NSValue可以封装做任意值。你可以使用NSValue将结构体放入NSArray或NSDictionary中。使用下面的类方法便能创建新的NSValue对象。
+(NSValue *)valueWithBytes: (const void *)value objCtype:(const char *)type;
传递的参数是你想要封装的数值的地址(如一个NSSize或你自己的struct)。通常你得到的是想要存储的变量的地址(C语言要使用操作符&)。你也可以提供一个用来描述这个数据类型的字符串,通常用来说明struct中实体的类型和大小。你不用自己写代码来生成这个字符串,@encode编译器指令可以接收数据类型的名称并为你生成合适的字符串,所以按照如下方式把NSRect放入NSArray中吧。
NSRect rect=NSMakeRect(1,2,30,40);
NSValue *value=[NSValue valueWithBytes:&rect objCtype:@encode(NSRect)];
[array addObject:value];
可以使用方法getValue:来提取数值:
-(void)getValue:(void *)buffer;
调用getValue:时,需要传递存储这个数值的变量地址:
value=[array objectAtIndex:0];[value getValue:&rect];

说明:在上面的getValue:的例子中,你可以看到方法名中使用了get,表明我们提供的是一个指针,而指针所指向的空间则用来存储该方法生成的数据。
NSNull
我们提到过不能在集合中放入nil值,因为在NSArray和NSDictionary中nil有特殊的含义,但有时你确实需要存储一个表示”这里什么都没有”的值。使用NSNull就可以满足这个需要。
NSNull大概是Cocoa里最简单的类了,它只有一个方法:
+(NSNull *)null;
你可以按照下面的方式将它添加到集合中:
[contact setObject:[NSNull null] forKey:@”传真号码”];
示例:查找文件
好了,我们已经说了一大堆理论知识了。下面是一个实用的程序,它是用本章中介绍的类组成的。程序FileWalker可以翻查你在Mac电脑上的主目录以查找.jpg文件并打印到列表中。我们必须承认这个程序不算酷得惊人,但是它的确实现了一些功能。
FileWalker程序用到了NSString、NSArray、NSEnumerator和其他两个用来与文件系统交互的Foundation类。
这个示例还用到了NSFileManager,它允许你对文件系统进行操作,例如创建目录、删除文件、移动文件或者获取文件信息。在这个例子中,我们将会要求NSFileManager类创建一个NSDirectoryEnumerator对象来遍历文件的层次结构。
整个FIleWalker程序都存放在main()函数中,因为我们并没有创建任何属于我们自己的类。下面是main()函数的全部代码。
//
//  main.m
//  FileWalker
//
//  Created by mouyong on 13-11-21.
//  Copyright (c) 2013年 mouyong. All rights reserved.
//

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{

@autoreleasepool {
        
        NSFileManager * manager;
        manager=[NSFileManager defaultManager];
        
        NSString *home;
        home=[@"~" stringByExpandingTildeInPath];
        
        NSDirectoryEnumerator *direnum;
        direnum=[manager enumeratorAtPath:home];
        
        NSMutableArray *files;
        files=[NSMutableArray arrayWithCapacity:42];
        
        NSString *filename;
        while (filename=[direnum nextObject]) {
            if ([[filename pathExtension] isEqualTo:@"jpg"]) {
                [files addObject:filename];
            }
        }
        
        NSEnumerator *fileenum;
        fileenum=[files objectEnumerator];
        
        while (filename=[fileenum nextObject]) {
            NSLog(@"%@",filename);
        }
        
        
    }
    return 0;
}
现在让我们逐步了解这个程序。最上面是自动释放池(@autoreleasepool)的样本代码(第9章详细介绍)。
下一步是获取NSFileManager对象。NSFileManager中有一个叫做defaultManager的类方法,可以创建一个属于我们自己的NSFileManager对象。
NSFileManager *manager=[NSFileManager defaultManager];
在Cocoa中这种情况很常见,很多都支持单例架构(singleton architecture),也就是说你只需要一个实例就够了。文件管理器、字体管理器或图形上下文确实只需要一个就够了。这些类都提供了一个类方法让你访问唯一的共享对象,完成你的工作。
 在这个示例中,我们需要一个目录迭代器。但是在要求文件管理吕创建目录迭代器之前,需要先确定从文件系统中的什么位置开始查找文件,考虑查找所花的时间,我们从主目录里开始查找。
如何指定目录呢?可以使用绝对路径,如”Users/mouyong”(默然说话:mouyong是我所使用的用户名),如果我使用这样一个路径,估计在你的电脑上是找不到的,因为你不太可能建立一个和我名字一样的用户,所以也就不可能有一个相同的路径。不过Unix系统有一个代表主目录的符号~(默然说话:读作波浪号?我读作飘~)。如果我写~/Documents,在我电脑上就是Users/mouyong/Documents, 在你的电脑上就是你的文稿目录,这就是相对路径的好处。NSString提供了一个方法可以将~字符展开成为主目录的绝对路径,就是下面这一行代码:
        NSString *home;
        home=[@"~" stringByExpandingTildeInPath];
stringByExpandingTildeInPath方法将~替换成了当前用户的主目录。在我的计算机上,主目录就是USers/mouyong。接下来,我们将路径字符串传递给文件管理器。
NSDirectoryEnumerator *direnum=[manager enumeratorAtPath: home];
enumeratorAtPath:返回一个NSDictionaryEnumerator对象,它是NSEnumerator的子类。每次在这个枚举器对象中调用nextObject方法时,都会返回该目录中下一个文件的路径。
由于我们要查找.jpg文件(即路径名称以.jpg结尾)并输出它们的名称,所以需要存储这些名称的空间。可以在枚举中通过NSLog()把它们都打印出来,不过也许以后,我们会想要在程序的其它位置对所有这些文件执行一些操作。所以我们创建一个可变数组把查到的文件路径添加进去:
NSMutableArray *files;
files=[NSMutableArray arrayWithCapacity:42];
我们不知道到底会找到多少个.jpg文件,所以就选择了42,因为……你懂的(默然说话:其实我估计我们大多数人都不懂,不过恰好我看过《银河系漫游指南》,这本书里提到,为了找到生命、宇宙与万事万物的终极解答,宇宙人创造了一台超级计算机,其中银河系就是这台超级计算机的一部分,经过上亿年的运行之后,计算机给出了终极解答:42。想具体了解,推荐大家自己去阅读这本书,个人觉得这本书很精彩,故事相当的不可思议,真不知道作者的大脑是怎么长的)。由于我们采用的是可变的数组,所以这样的定义在任何情况下都是可以的。
最后就是程序的循环:
 NSString *filename;
        while (filename=[direnum nextObject]) {
目录枚举将会返回一个代表文件路径的NSString字符串。就像NSEnumerator一样,当枚举器结束时它会返回nil值,于是循环终止。
NSString提供了许多处理路径名称 和文件名的方法,比如说pathExtension方法能输出文件的扩展名(不包括点)。
我们使用嵌套的方法调用来获取路径扩展名并将获得的字符串传递给isEqualTo方法。如果结果是YES,则该文件名将会被添加到文件数组中,如下所示。
 if ([[filename pathExtension] isEqualTo:@"jpg"]) {
                [files addObject:filename];
            }
目录循环结束后,遍历文件数组,用NSLog()函数将数组内容输出。
  NSEnumerator *fileenum;
        fileenum=[files objectEnumerator];
        
        while (filename=[fileenum nextObject]) {
            NSLog(@"%@",filename);
        }

接下来,我们通知main()函数返回0来表示程序 成功退出。
这个程序执行起来较慢,可能得等上一段时间(默然说话:我是等了30多秒。漫长呀,感觉就象死机了似的,输出花的时间就更长了。将近1分钟吧)。
小结    
本章介绍了很多内容,包括三个新的语言特性:类方法,即由由类本身而不是某个示例来处理的方法;@encode()指令,它用于需要描述C语言基础类型的方法;快速枚举。
我们研究了很多有用的Cocoa类,花了大量的篇幅,但也只认识了Cocoa的冰山一角。

下一章将深入研究内存管理的奇妙之处,你将学到在系统垃圾生成后如何清理程序。

本人接受个人捐助,如果你觉得文章对你有用,并愿意对默然说话进行捐助,数额随意,默然说话将不胜感激。

第8章 Foundation Kit介绍的相关教程结束。

《第8章 Foundation Kit介绍.doc》

下载本文的Word格式文档,以方便收藏与打印。