Cocoa 是由两个不同的框架组成的:Foundation Kit 和 Application Kit。
1 | typedef struct _NSRange { |
这个结构体用来表示相关事物的范围,通常是字符串里的字符范围或者数组里的元素范围。location 字段存放该范围的起始位置,而 length 字段则是该范围内所包含元素的个数。在字符串 “Objective-C is a cool language” 中,单词 “cool” 可以用 location 为 17,length 为 4 的范围表示。也许由于 location 字段未被初始化,所以它的值可以使 NSNotFound ,用来表示无意义的范围。
有三种方式创建 NSRange 。
第一种,直接给字符复制:
1 | NSRange range; |
第二种,应用 C 语言的聚合结构赋值机制:
1 | NSRange range = { 17, 4 }; |
第三种方式是 Cocoa 提供的一个快捷函数 NSMakeRange()
:
1 | NSRange range = NSMakeRange (17, 4); |
使用 NSMakeRange()
的好处是你可以在任何能够使用函数的地方使用它,例如在方法调用中,将其当成参数传递:
1 | [andObject flarbulateWithRange: NSMakeRange (13, 15)]; |
NSPoint 代表的是笛卡尔平面中的一个点 (x,y)。
1 | typedef struct _NSPoint { |
NSSize 用来存储长度和宽度:
1 | typedef struct _NSSize { |
NSRect 表示一个矩形,它是由点和大小复合而成的:
1 | typedef struct _NSRect { |
Cocoa 也为我们提供了创建这些数据类型的快捷函数:NSMakePoint()
、NSMakeSize()
和 NSMakeRect()
。
可以使用 stringWithFormat:
方法通过格式化字符串和参数来创建 NSString 。
1 | + (id) stringWithFormat: (NSString *) format, ...; |
示例:
1 | NSString *height; |
得到的字符串是 “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 | if ([height length] > 35) { |
nsstring 提供了几个用于比较的方法。
isequaltostring:
可以用来比较接收方(接收消息的对象)和当做参数传递来的字符串。isequaltostring:
返回一个 bool (yes 或 no) 型数据来表示两个字符串的内容是否相同。它的声明如下:
1 | - (bool) isequaltostring: (nsstring *) astring; |
下面是它的使用方法:
1 | nsstring *thing1 = @"hello 5"; |
isEqualToString:
,而不能仅仅只是比较字符串的指针值。例如
1 | if ([thing1 isequaltostring: thing2]) { |
和
1 | if (thing1 == thing2) { |
是不同的。这是因为 ==
运算符只判断 thing1 和 thing2 的指针数值,而不是它们所指的对象。由于 thing1 和 thing2 是不同的字符串,所以第二种方式会认为它们是不同的。
总之:
==
可以用来判断两者是否同一个对象。isEqualToString:
可以用来判断两者是否等价。要比较两个字符串,可以使用 compare:
方法,声明如下:
1 | - (nscomparisonresult) compare: (nsstring *) string; |
compare:
将接收对象和传递来的字符串逐个字符地进行比较,它返回一个 nscomparison-result(就是一个 enum 型数据)来显示比较结果:
1 | typedef enum _nscomparisonresult { |
如果结果是:
[@"aardvark" compare @"zygote"]
。[@"zoinks" compare @"jinkies"]
。[@"fnord" compare @"fnord"]
。compare:
进行的是区分大小写的比较。还有一个方法 compare:options:
,它能给我们更多的控制权:
1 | - (NSComparisonResult) compare: (NSString *)string |
options 参数是一个位掩码。你可以使用位或(bitwise-OR)运算符(|
)来添加选项标记,一些常用的选项如下:
例如,进行字符串比较,忽略大小且按字符的个数的多少正确排序:
1 | if ([thing1 compare: thing2 |
1 | - (BOOL) hasPrefix: (NSString *) aString; |
1 | - (BOOL) hasSuffix: (NSString *) aString; |
示例:
1 | NSString *filename = @"draft-chapter pages"; |
1 | - (NSRange) rangeOfString: (NSString *) aString; |
示例:
1 | NSRange range; |
返回的 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 | NSMutableString *string; |
一旦有了一个可变字符串,就可以对它执行各种操作了。一种常见的操作是通过 appendString:
或 appendFormat:
来附加字符串。
1 | - (void) appendString: (NSString *) aString; |
示例:
1 | NSMutableString *string; |
这段代码最后的结果是 string 被赋值为 “Hello there human 39!” 。
你可以用 deleteCharactersInRange:
方法删除字符串中的字符:
1 | - (void) deleteCharactersInRange: (NSRange) range; |
示例:
从朋友列表中删除 Jack 的名单——
1 | // 创建朋友列表 |
在学习使用 NSArray 前,先要了解它有两个限制:
有很多方法可以避开这些限制。
使用 arrayWithObjects:
创建一个 NSArray 。发送一个以逗号分割的对象列表,在列表结尾添加 nil 代表列表结束(因此 NSArray 中不能存储 nil)。
1 | NSArray *array; |
1 | - (unsigned) count |
1 | - (id) objectAtIndex: (unsigned int) index; |
使用 -componentsSeparatedByString:
来将字符串切分成 NSArray (类似于 Python 的 split
)。
示例:
1 | NSString *string = @"oop:ack:bork:greeble:ponies"; |
用 componentsJoinedByString: 来合并 NSArray 中的元素并创建字符串。
示例:
1 | string = [chunks componentsJoinedByString: @" :-) "]; |
将创建一个内容为 “oop:-)ack:-)bork:-)greeble:-)ponies” 的字符串。
NSMutableArray 是可变数组。
1 | + (id) arrayWithCapacity: (unsigned) numitems; |
示例:
1 | NSMutableArray *array; |
与 NSMutableString 中的 stringWithCapacity: 一样,数组容量也只是数组最终大小的一个参考。容量数值之所以存在,是为了 Cocoa 能够对代码做一些优化(有点像 C++ 中 vector 的空间分配策略)。
1 | - (void) addObject: (id) anObject; |
示例:
1 | for (i = 0; i < 4; i++) { |
1 | - (void) removeObjectAtIndex: (unsigned) index; |
示例:
1 | [array removeObjectAtIndex: 1]; |
结合 count
和 objectAtIndex:
两个操作就可以输出数组的全部内容:
1 | int i; |
更好的作法是使用枚举。
类似于 C++ 的迭代器,Objective-C 提供了 NSEnumerator 枚举器,来描述集合迭代运算的方式。要想使用 NSEnumerator ,需通过 objectEnumerator 向数组请求枚举器:
1 | - (NSEnumerator *) objectEnumerator; |
示例:
1 | NSEnumerator *enumerator; |
如果你想要从后向前遍历集合,还有一个方法 reverseObjectEnumerator
可以使用。
在获得枚举器之后,可以开始一个 while 循环,每次循环都向这个枚举器请求它的 nextObject :
1 | - (id) nextObject; |
nexetObject 返回 nil 值时,循环结束。整个循环代码如下所示:
1 | NSEnumartor *enumerator; |
对可变数组进行枚举操作时,有一点要注意:你不能通过添加或删除对象这类方式来改变数组容器。如果你这么做了,枚举器就会困惑,而你将会得到未定义的结果。
快速枚举是 Objective-C 2.0 的新特性,因此它不能再 Tiger (Mac OS X 10.4)系统上使用。
另一种枚举的方法称为“快速枚举”,它的语法与脚本语言的类似。
示例:
1 | for (NSString *string in array) { |
1 | + (id) dictionaryWithObjectsAndKeys: |
该方法接受对象和关键字交替存储的系列,以 nil 值作为终止符号。
示例:
1 | Tire *t1 = [Tire new]; |
使用方法 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 | NSMutableDictionary *tires; |
1 | - (void) removeObjectForKey: (id) aKey; |
示例:
1 | [tires removeObjectForKey: @"back-left"]; |
NSArray 和 NSDictionary 只能存储对象,而不能直接存储任何基本类型的数据,如 int 、float 或 struct。但是你可以用对象来封装基本数值。通常将一个基本类型的数据包装成对象叫做装箱(boxing),从对象中提取基本类型的数据叫做取消装箱(unboxing)。
NSNumber 类可以包装基本数据类型。
1 | + (NSNumber *) numberWithChar: (char) value; |
还有许多这种创建方法,包括无符号版本的和各种long型数据及long long整型数据,详见 NSNumber Class Reference 。
创建 NSNumber 之后,你可以把它放入一个字典或数组中:
1 | NSNumber *number; |
可以通过下面的实例方法重新获得 NSNumber 所封装的数值:
1 | - (char) charValue; |
NSNumber 实际上是 NSValue 的子类, NSValue 可以包装任意值。你可以用 NSValue 将结构放入 NSArray 和 NSDictionary 中。
1 | + (NSValue *) valueWithBytes: (const void *) value |
传递的参数是你想要包装的数值的地址(如一个 NSSize 或你自己的 struct)。通常,得到的是你想要存储的变量的地址(在C语言中使用操作符&)。你也可以提供一个用来描述这个数据类型的字符串,通常用来说明 struct 中实体的类型和大小。你不用自己写代码来生成这个字符串,@encode编译器指令可以接受数据类型的名称并为你生成合适的字符串。
示例:
1 | NSRect rect = NSMakeRect (1, 2, 30, 40); |
可以使用方法 getValue:
来提取数值:
1 | - (void) getValue: (void *) value; |
调用 getValue:
时,要传递的是要存储这个数值的变量的地址。
示例:
1 | value = [array objectAtIndex: 0]; |
Cocoa 提供了将常用的 struct 型数据转换成 NSValue 的便捷方法,如下所示:
1 | + (NSValue *) valueWithPoint: (NSPoint) point; |
可按下面的方式在 NSArray 中存储和检索 NSRect:
1 | value = [NSValue valueWithRect: rect]; |
前面提到集合中不能放入 nil 值,因为在 NSArray 和 NSDictionary 中 nil 有特殊的含义,但有时我们确实需要存储一个表示“这里什么都没有”的值。解决方法是使用封装了 nil 的 NSNull 类。
1 | + (NSNull *) null; |
你可以按照下面的方式将它添加到集合中:
1 | [contact setObject: [NSNull null] |
访问它的方法如下所示:
1 | id homefax; |
[NSNull null]
总是返回一样的数值,所以你可以使用运算符 ==
将该值与其他值进行比较。