Cocoa 是由两个不同的框架组成的:Foundation Kit 和 Application Kit。

  • Foundation Kit 提供了很多有用的、面向数据的低级类和数据类型。Foundation 框架拥有 100 多个类。查看 Xcode 自带的文档可以了解它们。这些文档存放在 /Developer/ADC Reference Library/documentation/index.html 中。
  • Application Kit (简称 AppKit )包含了所有的用户接口对象和高级类。

范围(NSRange)

1
2
3
4
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 。

第一种,直接给字符复制:

1
2
3
NSRange range;
range.location = 17;
range.length = 4;

第二种,应用 C 语言的聚合结构赋值机制:

1
NSRange range = { 17, 4 };

第三种方式是 Cocoa 提供的一个快捷函数 NSMakeRange()

1
NSRange range = NSMakeRange (17, 4);

使用 NSMakeRange() 的好处是你可以在任何能够使用函数的地方使用它,例如在方法调用中,将其当成参数传递:

1
[andObject flarbulateWithRange: NSMakeRange (13, 15)];

几何数据类型

NSPoint

NSPoint 代表的是笛卡尔平面中的一个点 (x,y)。

1
2
3
4
typedef struct _NSPoint {
float x;
float y;
} NSPoint;

NSSize

NSSize 用来存储长度和宽度:

1
2
3
4
typedef struct _NSSize {
float width;
float height;
} NSSize;

NSRect

NSRect 表示一个矩形,它是由点和大小复合而成的:

1
2
3
4
typedef struct _NSRect {
NSPoint origin;
NSSize size;
} NSRect;

Cocoa 也为我们提供了创建这些数据类型的快捷函数:NSMakePoint()NSMakeSize()NSMakeRect()

字符串(NSString)

  • NSString:不可变字符串。
  • NSMutableString:可变字符串。

创建

可以使用 stringWithFormat: 方法通过格式化字符串和参数来创建 NSString 。

1
+ (id) stringWithFormat: (NSString *) format, ...;

示例:

1
2
3
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 中的 NSColor 类有一些以不同颜色命名的类方法,如 redColor 和 blueColor。要用蓝色绘图,可以像这样编写代码:

1
NSColor *haveTheBlues = [NSColor blueColor];

你所创建的大部分方法都是实例方法,要用前导减号(-)来开始声明。这些方法将会在某个对象实例中运行,比如获取一个 Circle 的颜色或者一个 Tire 的气压。如果某方法用来实现常规功能,比如创建一个实例对象或者访问一些全局类数据,那么最好使用前导符号(+)将它声明为类方法。

求长度

length 方法返回字符串中的字符个数。

1
- (unsigned int) length;

示例:

1
unsigned int length = [height length];

也可以在表达式中使用它,如下所示:

1
2
3
if ([height length] > 35) {
nslog (@"wow, you're really tall!");
}

比较

nsstring 提供了几个用于比较的方法。

字面值相等判断

isequaltostring: 可以用来比较接收方(接收消息的对象)和当做参数传递来的字符串。isequaltostring: 返回一个 bool (yes 或 no) 型数据来表示两个字符串的内容是否相同。它的声明如下:

1
- (bool) isequaltostring: (nsstring *) astring;

下面是它的使用方法:

1
2
3
4
5
6
7
nsstring *thing1 = @"hello 5";
nsstring *thing2;
thing2 = [nsstring stringwithformat: @"hello %d", 5];
if ([thing1 isequaltostring: thing2]) {
nslog (@"they are the same!");
}

比较两个字符串是否相等时,应该用 isEqualToString: ,而不能仅仅只是比较字符串的指针值。例如

if ([thing1 isequaltostring: thing2]) {
  nslog (@"they are the same!");
}

if (thing1 == thing2) {
  nslog (@"they are the same!");
}

是不同的。这是因为 == 运算符只判断 thing1 和 thing2 的指针数值,而不是它们所指的对象。由于 thing1 和 thing2 是不同的字符串,所以第二种方式会认为它们是不同的。

总之:

  • == 可以用来判断两者是否同一个对象。
  • isEqualToString: 可以用来判断两者是否等价。

字典序比较

要比较两个字符串,可以使用 compare: 方法,声明如下:

1
- (nscomparisonresult) compare: (nsstring *) string;

compare: 将接收对象和传递来的字符串逐个字符地进行比较,它返回一个 nscomparison-result(就是一个 enum 型数据)来显示比较结果:

1
2
3
4
5
typedef enum _nscomparisonresult {
nsorderedascending = -1,
nsorderedsame,
nsordereddescending
} nscomparisonresult;

如果结果是:

  • NSOrderedAscending :左侧的数值小于右侧的数值,例如 [@"aardvark" compare @"zygote"]
  • NSOrderedAscending :左侧的数值大于右侧的数值,例如 [@"zoinks" compare @"jinkies"]
  • NSOrderedSame :左侧的数值等于右侧的数值,例如 [@"fnord" compare @"fnord"]

不区分大小写的比较

compare: 进行的是区分大小写的比较。还有一个方法 compare:options: ,它能给我们更多的控制权:

1
2
- (NSComparisonResult) compare: (NSString *)string
options: (unsigned) mask;

options 参数是一个位掩码。你可以使用位或(bitwise-OR)运算符(|)来添加选项标记,一些常用的选项如下:

  • NSCaseInsensitiveSearch :不区分大小写字符。
  • NSLiteralSearch:进行完全比较,区分大小写。
  • NSNumericSearch:比较字符串的字符个数,而不是字符值。如果没有这个选项,“100”会排在“99”的前面,这会让人觉得奇怪,甚至可能是错误的排序。

例如,进行字符串比较,忽略大小且按字符的个数的多少正确排序:

1
2
3
4
5
if ([thing1 compare: thing2
options: NSCaseInsensitiveSearch | NSNumericSearch]
== NSOrderedSame) {
NSLog (@"They match!");
}

子字符串判断

  • 判断字符串是否以另一个字符串开头:

1
- (BOOL) hasPrefix: (NSString *) aString;

  • 判断字符串是否以另一个字符串结尾:

1
- (BOOL) hasSuffix: (NSString *) aString;

示例:

1
2
3
4
5
6
7
8
NSString *filename = @"draft-chapter pages";
if ([fileName hasPrefix: @"draft") {
// this is a draft
}
if ([fileName hasSuffix: @".mov") {
// this is a movie
}

  • 判断字符串内的某处是否包含另一字符串:

1
- (NSRange) rangeOfString: (NSString *) aString;

示例:

1
2
NSRange range;
range = [fileName rangeOfString: @"chapter"];

返回的 range.start 为 6,range.length 为 7。如果传递的参数在接收字符串中没有找到,那么 range.start 则等于 NSNotFound。

可变字符串

NSString 是不可变的(immutable)。Cocoa 提供了一个 NSString 的子类,叫做 NSMutableString。如果你想改变字符串,请使用这个子类。

创建

你可以使用类方法 stringWithCapacity: 来创建一个新的 NSMutableString ,声明如下:

1
+ (id) stringWithCapacity: (unsigned) capacity;

这个容量只是给 NSMutableString 的一个建议。字符串的大小并不仅限于所提供的容量,这个容量只是个最优值。例如,如果你知道你要创建一个大小为 40 MB 的字符串,那么 NSMutableString 可以预分配一块内存来存储它,这样后续操作的速度就会快很多。可按如下方式创建一个新的可变字符串:

1
2
NSMutableString *string;
string = [NSMutableString stringWithCapacity: 42];

追加

一旦有了一个可变字符串,就可以对它执行各种操作了。一种常见的操作是通过 appendString:appendFormat: 来附加字符串。

1
2
- (void) appendString: (NSString *) aString;
- (void) appendFormat: (NSString *) format, ...;

示例:

1
2
3
4
NSMutableString *string;
string = [NSMutableString stringWithCapacity: 50];
[string appendString: @"Hello there "];
[string appendFormat: @"human %d!", 39];

这段代码最后的结果是 string 被赋值为 “Hello there human 39!” 。

删除

你可以用 deleteCharactersInRange: 方法删除字符串中的字符:

1
- (void) deleteCharactersInRange: (NSRange) range;

示例:

从朋友列表中删除 Jack 的名单——

1
2
3
4
5
6
7
8
9
10
11
12
// 创建朋友列表
NSMutableString *friends;
friends = [NSMutableString stringWithCapacity: 50];
[friends appendString: @"James BethLynn Jack Evan"];
// 找到Jack的范围
NSRange jackRange;
jackRange = [friends rangeOfString: @"Jack"];
jackRange.length++; // 把后面跟着的空格也一同干掉
// 删除Jack
[friends deleteCharactersInRange: jackRange];

数组

  • NSArray: 用来存储对象的有序不可变数组。
  • NSMutableArray:与 NSArray 类似,但是是可变数组。

NSArray 的限制

在学习使用 NSArray 前,先要了解它有两个限制:

  1. 只能存储 Objective-C 的对象,而不能存储 C 语言中的基本数据类型,如 int、float、enum、struct 或者 NSArray 中的随机指针。
  2. 也不能存储 nil (对象的零值或NULL值)。

有很多方法可以避开这些限制。

创建

使用 arrayWithObjects: 创建一个 NSArray 。发送一个以逗号分割的对象列表,在列表结尾添加 nil 代表列表结束(因此 NSArray 中不能存储 nil)。

1
2
3
NSArray *array;
array = [NSArray arrayWithObjects:
@"one", @"two", @"three", nil];

获取元素个数

1
- (unsigned) count

访问元素

1
- (id) objectAtIndex: (unsigned int) index;

切分和合并

切分

使用 -componentsSeparatedByString: 来将字符串切分成 NSArray (类似于 Python 的 split)。

示例:

1
2
NSString *string = @"oop:ack:bork:greeble:ponies";
NSArray *chunks = [string componentsSeparatedByString: @":"];

合并

用 componentsJoinedByString: 来合并 NSArray 中的元素并创建字符串。

示例:

1
string = [chunks componentsJoinedByString: @" :-) "];

将创建一个内容为 “oop:-)ack:-)bork:-)greeble:-)ponies” 的字符串。

可变数组

NSMutableArray 是可变数组。

创建

1
+ (id) arrayWithCapacity: (unsigned) numitems;

示例:

1
2
NSMutableArray *array;
array = [NSMutableArray arrayWithCapacity: 17];

与 NSMutableString 中的 stringWithCapacity: 一样,数组容量也只是数组最终大小的一个参考。容量数值之所以存在,是为了 Cocoa 能够对代码做一些优化(有点像 C++ 中 vector 的空间分配策略)。

追加

1
- (void) addObject: (id) anObject;

示例:

1
2
3
4
for (i = 0; i < 4; i++) {
Tire *tire = [Tire new];
[array addObject: tire];
}

删除

1
- (void) removeObjectAtIndex: (unsigned) index;

示例:

1
[array removeObjectAtIndex: 1];

遍历数组

直接遍历

结合 countobjectAtIndex: 两个操作就可以输出数组的全部内容:

1
2
3
4
5
int i;
for (i = 0; i < [array count]; i++) {
NSLog (@"index %d has %@.",
i, [array objectAtIndex: i]);
}

更好的作法是使用枚举。

枚举器

类似于 C++ 的迭代器,Objective-C 提供了 NSEnumerator 枚举器,来描述集合迭代运算的方式。要想使用 NSEnumerator ,需通过 objectEnumerator 向数组请求枚举器:

1
- (NSEnumerator *) objectEnumerator;

示例:

1
2
NSEnumerator *enumerator;
enumerator = [array objectEnumerator];

如果你想要从后向前遍历集合,还有一个方法 reverseObjectEnumerator 可以使用。

在获得枚举器之后,可以开始一个 while 循环,每次循环都向这个枚举器请求它的 nextObject :

1
- (id) nextObject;

nexetObject 返回 nil 值时,循环结束。整个循环代码如下所示:

1
2
3
4
5
6
7
NSEnumartor *enumerator;
enumerator = [array objectEnumerator];
id thingie;
while (thingie = [enumerator nextObject]) {
NSLog (@"I found %@", thingie);
}

对可变数组进行枚举操作时,有一点要注意:你不能通过添加或删除对象这类方式来改变数组容器。如果你这么做了,枚举器就会困惑,而你将会得到未定义的结果。

快速枚举

注意:

快速枚举是 Objective-C 2.0 的新特性,因此它不能再 Tiger (Mac OS X 10.4)系统上使用。

另一种枚举的方法称为“快速枚举”,它的语法与脚本语言的类似。

示例:

1
2
3
for (NSString *string in array) {
NSLog (@"I found %@", string);
}

字典

  • NSDictionary:不可变字典。
  • NSMutableDictionary:可变字典。

创建

1
2
+ (id) dictionaryWithObjectsAndKeys:
(id) firstObject, ...;

该方法接受对象和关键字交替存储的系列,以 nil 值作为终止符号。

示例:

1
2
3
4
5
6
7
8
9
10
Tire *t1 = [Tire new];
Tire *t2 = [Tire new];
Tire *t3 = [Tire new];
Tire *t4 = [Tire new];
NSDictionary *tires;
tires = [NSDictionary dictionaryWithObjectsAndKeys:
t1, @"front-left", t2, @"front-right",
t3, @"back-left", t4, @"back-right", nil];

访问元素

使用方法 objectForKey: 来获取字典中的值,向方法传递之前用来存储该值的关键字:

1
- (id) objectForKey: (id) aKey;

示例:

1
Tire *tire = [tires objectForKey: @"back-right"];

如果字典里没有键为 @“back-right” 的键值对,objectForKey: 会返回 nil 值。(这点和 C++ 不同)。

可变字典

创建

1
+ (id) dictionaryWithCapacity: (unsigned int) numitems;

添加/修改

1
- (void) setObject: (id) anObject forKey: (id) aKey;

示例:

1
2
3
4
5
6
7
NSMutableDictionary *tires;
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"];

删除

1
- (void) removeObjectForKey: (id) aKey;

示例:

1
[tires removeObjectForKey: @"back-left"];

封装数值

NSArray 和 NSDictionary 只能存储对象,而不能直接存储任何基本类型的数据,如 int 、float 或 struct。但是你可以用对象来封装基本数值。通常将一个基本类型的数据包装成对象叫做装箱(boxing),从对象中提取基本类型的数据叫做取消装箱(unboxing)。

NSNumber

NSNumber 类可以包装基本数据类型。

装箱

1
2
3
4
+ (NSNumber *) numberWithChar: (char) value;
+ (NSNumber *) numberWithInt: (int) value;
+ (NSNumber *) numberWithFloat: (float) value;
+ (NSNumber *) numberWithBool: (BOOL) value;

还有许多这种创建方法,包括无符号版本的和各种long型数据及long long整型数据,详见 NSNumber Class Reference

创建 NSNumber 之后,你可以把它放入一个字典或数组中:

1
2
3
4
NSNumber *number;
number = [NSNumber numberWithInt: 42];
[array addObject: number];
[dictionary setObject: number forKey: @"Bork"];

取消装箱

可以通过下面的实例方法重新获得 NSNumber 所封装的数值:

1
2
3
4
5
- (char) charValue;
- (int) intValue;
- (float) floatValue;
- (BOOL) boolValue;
- (NSString *) stringValue;

NSValue

NSNumber 实际上是 NSValue 的子类, NSValue 可以包装任意值。你可以用 NSValue 将结构放入 NSArray 和 NSDictionary 中。

装箱

1
2
+ (NSValue *) valueWithBytes: (const void *) value
objCType: (const char *) type;

传递的参数是你想要包装的数值的地址(如一个 NSSize 或你自己的 struct)。通常,得到的是你想要存储的变量的地址(在C语言中使用操作符&)。你也可以提供一个用来描述这个数据类型的字符串,通常用来说明 struct 中实体的类型和大小。你不用自己写代码来生成这个字符串,@encode编译器指令可以接受数据类型的名称并为你生成合适的字符串。

示例:

1
2
3
4
5
6
7
NSRect rect = NSMakeRect (1, 2, 30, 40);
NSValue *value;
value = [NSValue valueWithBytes: &rect
objCType: @encode(NSRect)];
[array addObject: value];

取消装箱

可以使用方法 getValue: 来提取数值:

1
- (void) getValue: (void *) value;

调用 getValue: 时,要传递的是要存储这个数值的变量的地址。

示例:

1
2
value = [array objectAtIndex: 0];
[value getValue: &rect];

几何数据类型的装/拆箱

Cocoa 提供了将常用的 struct 型数据转换成 NSValue 的便捷方法,如下所示:

1
2
3
4
5
6
7
+ (NSValue *) valueWithPoint: (NSPoint) point;
+ (NSValue *) valueWithSize: (NSSize) size;
+ (NSValue *) valueWithRect: (NSRect) rect;
- (NSPoint) pointValue;
- (NSSize) sizeValue;
- (NSRect) rectValue;

可按下面的方式在 NSArray 中存储和检索 NSRect:

1
2
3
4
value = [NSValue valueWithRect: rect];
[array addObject: value];
...
NSRect anotherRect = [value rectValue];

NSNull

前面提到集合中不能放入 nil 值,因为在 NSArray 和 NSDictionary 中 nil 有特殊的含义,但有时我们确实需要存储一个表示“这里什么都没有”的值。解决方法是使用封装了 nil 的 NSNull 类。

1
+ (NSNull *) null;

你可以按照下面的方式将它添加到集合中:

1
2
[contact setObject: [NSNull null]
forKey: @"home fax machine"];

访问它的方法如下所示:

1
2
3
4
5
6
id homefax;
homefax = [contact objectForKey: @"home fax machine"];
if (homefax = [NSNull null]) {
// ... no fax machine. rats.
}

[NSNull null] 总是返回一样的数值,所以你可以使用运算符 == 将该值与其他值进行比较。

Comments