Objective-C官方文档 值和集合
版权声明:原创作品,谢绝转载!否则将追究法律责任。
尽管Objective-c是一个面向对象的语言,是C语言的超集,这意味着你可以用任何标准的C标量(非对象)像int,float,和char在Objective-c里面。也有一些额外的标量类型在cocoa和cocoaTouch应用像NSIterger,NSUInterger和CGFloat,他们取决去不同的框架会有不同的定义。
常量类型一般都是用在不需要对象表达值的时候。在字符串的通常用NSString表示,数值通常存储在本地变量或者属性的标量中。
你可能定义一个C风格的数组在Objective-c中,但是在Objective-c有集合用来表示例如NSArray或者NSDictionay。这些类用来收集Objective-c的对象,这意味着你在添加到集合之前需要创建一些类的实例像NSValue,NSNumber,NSString来表达值。
在前面的章节我们用到了NSString的类并且也用到了他的初始化和工厂方法以及Objective-c的@“string”字面值,这提供了简洁的语法来创建NSString的实例,这章阐述怎么创建NSValue和NSNumber对象,用方法调用或者字面值语法。
基本的C的原始类型在Objective-c还是有效的:
标准的C的基本数据类型在Objective-c还是有效的:
int someInteger = 42;
float someFloatingPointNumber = 3.1415;
double someDoublePrecisionFloatingPointNumber = 6.02214199e23;
还有C的运算符:
int someInteger = 42;
someInteger++; // someInteger == 43
int anotherInteger = 64;
anotherInteger--; // anotherInteger == 63
anotherInteger *= 2; // anotherInteger == 126
你可以给基本数据类型声明一个Objective-c类型的属性像这样:
@interface XYZCalculator : NSObject
@property double currentValue;
@end
你也可以用C的运算符在属性里当用点语法访问他的值的时候像这样:
@implementation XYZCalculator
- (void)increment {
self.currentValue++;
}
- (void)decrement {
self.currentValue--;
}
- (void)multiplyBy:(double)factor {
self.currentValue *= factor;
}
@end
点语法纯粹是一个访问器方法调用的包装器。在这个例子中的操作第一次是用访问器方法得到值,然后执行操作,然后用设置器来给结果设置值。
Objective-c定义额外的原始类型:
这个BOOL类型定义在Objective-C中用来持有一个布尔值,定义为YES和NO。正如你所料,YES在逻辑上等于true和1,NO在逻辑上是false和0.
一些方法参数也用一些特殊的基本类型像NSIterger或者CGFloat。
例如UITableViewDataSource协议有方法请求显示的行数。
@protocol NSTableViewDataSource <NSObject>
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView;
...
@end
有一些类型像NSIterger或者NSUIterger,根据目标架构会定义不同,当在32位环境下编译时候(像IOS)那是32位有符号和无符号的整数。当在64位环境下编译时候(例如OS X操作系统运行时)他们被定义为64位的有符号和无符号的整数。
这是最好的练习用这些平台的特定类型如果你可以跨边界传值(不管是内部还是导出的API)例如如果参数或者返回值的方法或者函数调用在你的应用程序代码和一个框架。
对局部变量,例如在循环里的一个计数器,如果你知道他的值的标准限制你可以用C的基本类型。
C结构体可以保持私有值:
一些API用C结构体保持他们的值。例如询问一个字符串对象的子串就像这个。
NSString *mainString = @"This is a long string";
NSRange substringRange = [mainString rangeOfString:@"long"];
一个NSRange结构体含有位置和长度的信息。在这个例子中subStringRange将要含有一个{10,4}的范围,这个”l“是long的开头是字符串@“mainString”在以0开头的索引的第10位置,并且@“long”是4个字符的长度。
相似的,如果你需要写自定义的绘制代码,你需要和Quartz交互,会需要CGFloat的结构体类型还有NSPoint和NSSize。再说一次CGFloat的定义不同取决于目标结构。
更多信息在 Quartz 2D绘制引擎,参考Quartz 2D Programming Guide.
对象可以表达原始值:
如果你需要表示一个标量值给一个对象。例如当你使用集合类在下一节描述。你可以使用cocoa或者cocoatouch的基本值类。
NSString的类的实例表示字符串
就像你看到的在前一章节,NSString是用来表达字符串的就像@”hello world“ 。有很多方法来创建字符串对象,包括标准的初始化和工厂方法或者字面值语法:
NSString *firstString = [[NSString alloc] initWithCString:"Hello World!"
encoding:NSUTF8StringEncoding];
NSString *secondString = [NSString stringWithCString:"Hello World!"
encoding:NSUTF8StringEncoding];
NSString *thirdString = @"Hello World!";
他们的每一个例子有效的完成同样的事情-创建一个字符串对象来表示他们提供的字符串。
这个基本的NSString类是不可变的,意味着他的内容被创建时候设置不能稍后被改变。如果你需要表示不同的字符串。你需要创建一个新的字符串对象,像这样:
NSString *name = @"John";
name = [name stringByAppendingString:@"ny"];//返回一个新的字符串对象
这个NSMutableString 类是可变的继承与NSString,允许你在运行时改变字符串的内容用appendString:appendFormat:的方法像这样:
NSMutableString *name = [NSMutableString stringWithString:@"John"];
[name appendString:@"ny"]; // 同样的对象但是现在是“johnny”
格式字符串是从其他的对象或者值构建而来的
如果你需要构建一个字符串包含变量值,你需要用format string,这个允许你使用格式说明符指示值是如何插入的:
int magicNumber = ...
NSString *magicString = [NSString stringWithFormat:@"The magic number is %i", magicNumber];
有效的格式化说明符被 “String Format Specifiers”描述,更多一般的信息关于字符串的看String Programming Guide.文档
NSNumber类的实例表示数值:
这个NSNumber类是用来表示任何基本的C数据类型:包括char double float int long short 并且他们都有有无符号的变形以及Objective-c的布尔类型。
和NSString相比,你创建NSNumber实例有很多选择,包括初始化或者工厂方法:
NSNumber *magicNumber = [[NSNumber alloc] initWithInt:42];
NSNumber *unsignedNumber = [[NSNumber alloc] initWithUnsignedInt:42u];
NSNumber *longNumber = [[NSNumber alloc] initWithLong:42l];
NSNumber *boolNumber = [[NSNumber alloc] initWithBOOL:YES];
NSNumber *simpleFloat = [NSNumber numberWithFloat:3.14f];
NSNumber *betterDouble = [NSNumber numberWithDouble:3.1415926535];
NSNumber *someChar = [NSNumber numberWithChar:'T'];
你也可以用字面值语法创建:
NSNumber *magicNumber = @42;
NSNumber *unsignedNumber = @42u;
NSNumber *longNumber = @42l;
NSNumber *boolNumber = @YES;
NSNumber *simpleFloat = @3.14f;
NSNumber *betterDouble = @3.1415926535;
NSNumber *someChar = @'T';
这些例子是用NSNumber类的工厂方法创建的。
一旦你创建了一个NSNumber的实例你可能请求一个值用访问器方法:
int scalarMagic = [magicNumber intValue];
unsigned int scalarUnsigned = [unsignedNumber unsignedIntValue];
long scalarLong = [longNumber longValue];
BOOL scalarBool = [boolNumber boolValue];
float scalarSimpleFloat = [simpleFloat floatValue];
double scalarBetterDouble = [betterDouble doubleValue];
char scalarChar = [someChar charValue];
这个NSNumber类提供额外的Objective-c原始类型。如果你创建一个对象表示NSIterger或者NSUIterger类型你需要正确方法:
NSInteger anInteger = 64;
NSUInteger anUnsignedInteger = 100;
NSNumber *firstInteger = [[NSNumber alloc] initWithInteger:anInteger];
NSNumber *secondInteger = [NSNumber numberWithUnsignedInteger:anUnsignedInteger];
NSInteger integerCheck = [firstInteger integerValue];
NSUInteger unsignedCheck = [secondInteger unsignedIntegerValue]
所有的NSNumber实例都是不可变的,没有可变的子类,你需要创建不同的NSNumber,简单的用另一个NSNumber实例。
注意:NSNumber实际上是一个类簇。这意味着在运行时当你创建一个实例,你将要得到一个合适的具体子类所提供的价值,只是把你创建的对象作为NSNumber的一个实例。
用NSValue表示其他的值:
这个NSNumber是NSValue的一个子类,他提供了包装一个单值或者数据项的一个对象。除了基本的C数据类型,NSValue可以被用来表示指针和结构体。
这个NSValue类提供多样化的工厂方法来创建值用给定的标准结构,让他更容易的创建一个实例来表示值,例如,一个NSRange,之前提到的:
NSString *mainString = @"This is a long string";
NSRange substringRange = [mainString rangeOfString:@"long"];
NSValue *rangeValue = [NSValue valueWithRange:substringRange];
他也可能创建NSValue对象表示自定义结构体。如果你需要一个特定的C结构(而不是一个Objective-c的对象)来存储信息。像这样:
typedef struct {
int i;
float f;
} MyIntegerFloatStruct;
你可以创建一个NSValue的实例提供一个指针指向结构体以及用Objective-c的编码类型。这个@encode()编译指令被用来创建正确的Objective-c的类型,像这样:
struct MyIntegerFloatStruct aStruct;
aStruct.i = 42;
aStruct.f = 3.14;
NSValue *structValue = [NSValue value:&aStruct
withObjCType:@encode(MyIntegerFloatStruct)];
&符号是用来提供aStruct地址的值参数。
大多数集合都是对象:
尽管可以使用标准的C数组来存储标量的值,甚至对象指针,很多集合在Objective-c代码都是cocoa或者cocoaTouch集合类的实例,像NSArray,NSSet和NSDictionay。
这些类用来管理对象组,这意味着你添加到集合的条目都是Objective-c类的对象。如果你需要添加基本数据类型那么你必须先创建NSNumber或者NSValue的实例来包装他们。这个集合类用强引用来跟踪他们的内容,而不是以某种方式为他们的集合元素维护一个单独的副本。这说明在集合的元素你添加的将要保持存活只要集合存活着之前在管理对象图的关系和责任介绍到了。
集合除了跟踪他们的内容,每个集合类都很容易来执行某些方法,例如枚举,访问特定的元素,或者有没有特定的元素在集合里面。
NSArray,NSSet 和NSDictionay类都是不可变的,这意味着内容在创建的时候被设置。但是他们都有可变的子类版本来提供添加删除元素。更多信息关于不同集合类的参考Collections Programming Topics.
数组是有序的集合:
一个数组用来表示有序的集合对象。唯一的要求是数组元素里的每个元素必须是Objective-c的对象,对与每个对象是不是同一个类的实例没做要求。
为了维持数组的顺序,每个元素存储在从零开始的索引如下图:
创建数组:
正如之前描述的值类型,你可以创建数组通过初始化方法,工厂方法,还有字面语法。
有很多创建数组的初始化方法和工厂方法,都需要一些对象参数:
+ (id)arrayWithObject:(id)anObject;
+ (id)arrayWithObjects:(id)firstObject, ...;
- (id)initWithObjects:(id)firstObject, ...;
arrayWithObject:和initWithObjects:方法都以nil结尾,一些可变的参数个数,这意味着你必须以nil作为最后一个值像这样:
NSArray *someArray =
[NSArray arrayWithObjects:someObject, someString, someNumber, someValue, nil];
这个例子创建一个数组,第一个对象,someObject,他在这个数组索引为0,someValue他这个数组的索引为3。
如果数组里的一个元素提供了nil那么这个列表就会被无意中截断就像这样:
id firstObject = @"someString";
id secondObject = nil;
id thirdObject = @"anotherString";
NSArray *someArray =
[NSArray arrayWithObjects:firstObject, secondObject, thirdObject, nil];
在这个例子当中,someArray 紧紧只有firstObject因为第二个元素是nil所以第二个元素打断数组元素的列表作为最后一个元素。
字面语法:
我们也可以这样创建数组:
NSArray *someArray = @[firstObject, secondObject, thirdObject];
当用字面语法创建数组的时候不应该以nil结尾,实际上nil是无效值,如果执行一下代码你会得到一个异常:
id firstObject = @"someString";
id secondObject = nil;
NSArray *someArray = @[firstObject, secondObject];
// exception: "attempt to insert nil object"
在集合类里面你不需要表达一个nil值,你应该用NSNull单例类具体描述参考“Represent nil with NSNull.”
查询数组对象:
一旦你创建一个数组,你可能查询里面的元素个数,或者查询集合是不是包含这个元素:
NSUInteger numberOfItems = [someArray count];
if ([someArray containsObject:someString]) {
...
}
你也可以查询数组元素根据索引,如果你在运行时请求一个无效的索引你会得到一个数组越界的异常,因此你应该一直检查数组元素的数值:
if ([someArray count] > 0) {
NSLog(@"First item is: %@", [someArray objectAtIndex:0]);
}
这个例子检查数组的元素个数是不是大于0,如果大于0,那么你就打印索引为0的第一个元素。
数组下标:
除了objectAtIndex我们还提供了下标语法,就像C语言数组的语法。之前的那段代码也可以这样写:
if ([someArray count] > 0) {
NSLog(@"First item is: %@", someArray[0]);
}
排序数组对象
这个NSArray也提供了很多的方法来给集合对象排序。因为数组是不可变的,每个方法都返回一个排序后的新数组:例如你可以用compare:方法给字符串数组排序像这样:
NSArray *unsortedStrings = @[@"gammaString", @"alphaString", @"betaString"];
NSArray *sortedStrings =
[unsortedStrings sortedArrayUsingSelector:@selector(compare:)];
集合可变性:
虽然NSArray本身是不可变的,这个集合元素没有关系。如果你添加一个可变的字符串到不可变的数组,例如像这样:
NSMutableString *mutableString = [NSMutableString stringWithString:@"Hello"];
NSArray *immutableArray = @[mutableString];
没有什么能阻止可变的字符串:
if ([immutableArray count] > 0) {
id string = immutableArray[0];
if ([string isKindOfClass:[NSMutableString class]]) {
[string appendString:@" World!"];
}
}
如果你需要在数组创建后添加或者删除数组元素,你需要用NSMutableArray,有很多方法可以添加删除或者替换一个或者多个对象:
NSMutableArray *mutableArray = [NSMutableArray array];
[mutableArray addObject:@"gamma"];
[mutableArray addObject:@"alpha"];
[mutableArray addObject:@"beta"];
[mutableArray replaceObjectAtIndex:0 withObject:@"epsilon"];
这个例子创建一个数组最终结果是: @"epsilon", @"alpha", @"beta",你可以排序一个可变数组而不要再创建一个数组:
[mutableArray sortUsingSelector:@selector(caseInsensitiveCompare:)];
这个例子包括的数组元素将按照升序排序,不分大小写@"alpha", @"beta", @"epsilon"
Sets是无序的集合:
一个NSSet就像数组,但是他包含的是无序的不同对象。如下:
因为Sets是无序的,他们提高了性能比数组当测试会员的时候。
这个NSSet是不可变的,创建的时候必须指定元素,用初始化方法或者工厂方法就像:
NSSet *simpleSet =
[NSSet setWithObjects:@"Hello, World!", @42, aValue, anObject, nil];
就像数组一样,这个initWithObjects:和setWithObjects:方法都有以nil结尾,可变的参数个数,还有可变的子类NSMutableSet。
对于添加相同的对象到NSset:
NSNumber *number = @42;
NSSet *numberSet =
[NSSet setWithObjects:number, number, number, number, nil];
// numberSet only contains one object
更多信息参考“Sets: Unordered Collections of Objects”.
字典集合键值对:
一个NSDictionay存储对象给定键值,而不是无序或者有序的集合,以便于后来进行检索。如下图:
我们可能用其他对象作为键,但是重要的说明是你的字典的键会被复制,因此你必须支持NSCopying协议。
如果你希望用键值编码然后在 Key-Value Coding Programming Guide描述,你的字典键必须是字符串。
创建字典:
你可以创建字典用初始化方法或者工厂方法像这样:
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
someObject, @"anObject",
@"Hello, World!", @"helloString",
@42, @"magicNumber",
someValue, @"aValue",
nil];
注意:dictionaryWithObjectsAndKeys:和initWithObjectsAndKeys:方法每个对象都有键相对最后以nil结尾。
字面语法:
Objective-c也提供字面语法创建字典:
NSDictionary *dictionary = @{
@"anObject" : someObject,
@"helloString" : @"Hello, World!",
@"magicNumber" : @42,
@"aValue" : someValue
};
注意的是字典的字面常量也不能以nil结束。
查询字典:
一旦你创建字典,你可以通过键值访问他的对象值像这样:
NSNumber *storedNumber = [dictionary objectForKey:@"magicNumber"];
如果这个对象没有找到这个objectForKey:就会返回nil。
我们同样可以用下标语法访问字典代替objectForKey:像这样:
NSNumber *storedNumber = dictionary[@"magicNumber"];
字典的可变版本:
当你创建字典的时候你移除或者添加字典的元素你需要NSMutableDictionary像这样:
[dictionary setObject:@"another string" forKey:@"secondString"];
[dictionary removeObjectForKey:@"anObject"];
我们用
NSNull代替空
你不可能添加nil到集合类里面,因为nil在集合里面意味着没有对象,如果你需要表示没有对象在集合里面,你可以用NSNUll。
NSArray *array = @[ @"string", @42, [NSNull null] ];
NSNull是一个单例类,意味着null方法将要返回同样的实例,这意味着你需要在数组里面检查元素是不是等译NSNull的实例。
for (id object in array) {
if (object == [NSNull null]) {
NSLog(@"Found a null object");
}
}
用集合来保持你的对象图:
这个NSArray和NSDictionay类使把你的内容写进硬盘变的方面就像这样:
NSURL *fileURL = ...
NSArray *array = @[@"first", @"second", @"third"];
BOOL success = [array writeToURL:fileURL atomically:YES];
if (!success) {
// an error occured...
}
集合里的每个元素都是属性列表中的类型(NSArray, NSDictionary, NSString, NSData, NSDate and NSNumber并且还可以从硬盘再读取回来:
NSURL *fileURL = ...
NSArray *array = [NSArray arrayWithContentsOfURL:fileURL];
if (!array) {
// an error occurred...
}
更多信息参考属性列表:Property List Programming Guide.
如果你坚持用其他类型对象而不仅仅是标准的属性列表里的类型,你可以用archiver 对象,例如NSKeyedArchiver为集合对象创建归档。
创建归档唯一的要求的是每个对象支持 NSCoding 协议。这意味着每个对象都必须知道如果编码本身到存档(通过实现encodeWithCoder:方法)和解码本身当从现有的存档阅读的时候(initWithCoder:方法)。
这个NSArray和NSSet和NSDictionay类都有可变的子类,也都支持NSCoding协议,意味着你可以继续用负责的对象结构用归档。如果你用IB来设计你的界面。例如这个nib文件就是一个存储对象层次结构的归档。在运行时候,nib文件用相关的类解档出一个复杂的对象结构。
更多归档信息参考Archives and Serializations Programming Guide.
集合的快速枚举:
Objective-c里面提供了很多方法列举集合的元素,例如for循环像这样:
int count = [array count];
for (int index = 0; index < count; index++) {
id eachObject = [array objectAtIndex:index];
...
}
快速枚举使列举集合元素更方便:
一些集合类符合NSFastEnumeration协议包括NSArray和NSDictionay。这意味着你可以使用快速枚举,一个Objective-C语言特性。
快速枚举的语法怎么快速枚举数组里的元素像这样:
for (<Type> <variable> in <collection>) {
...
}
这个例子你可以使用快速枚举快速打印出数据的元素像这样:
for (id eachObject in array) {
NSLog(@"Object: %@", eachObject);
}
这个eachObject变量是被自动设置当前循环经过的对象因此会打印出每个对象。
如果你在字典里用快速枚举,你可以迭代字典的keys像这样:
for (NSString *eachKey in dictionary) {
id object = dictionary[eachKey];
NSLog(@"Object: %@ for key: %@", object, eachKey);
}
快速枚举像C语言的for循环,因此你可以用break打断这个迭代或者继续前进到下一个元素。
如果你快速枚举一个有序的集合,快速枚举也是有序的。对于一个NSArray,这意味着第一个通过的对象索引是0,第二个对象的索引是1等。如果你跟踪他们当前的索引,简单计算迭代的发生:
int index = 0;
for (id eachObject in array) {
NSLog(@"Object at index %i is: %@", index, eachObject);
index++;
}
你不能改变一个集合在快速枚举时候,尽管集合是可变的,如果你在循环中试图添加删除几个对象,你将要生成一个异常在运行时候。
大多数集合支持枚举对象:
我们可以用NSEnumerator 对象来枚举出很多集合。例如objectEnumerator 或者reverseObjectEnumerator得到一个NSArray。我们可以用这些对象进行快速枚举像这样:
for (id eachObject in [array reverseObjectEnumerator]) {
...
}
在这个例子中,这个循环迭代出的集合对象变成相反的顺序,因此这个最后一个对象讲是第一个,等等。
我们可以用枚举器的nextObject方法来遍历集合元素像这样:
id eachObject;
while ( (eachObject = [enumerator nextObject]) ) {
NSLog(@"Current object is: %@", eachObject);
}
在这个例子中一个while循环通过循环用于设置eachObject的下一个对象变量当没有更多的对象在的时候,nextObject方法返回nil对象,用来评估逻辑值为false因此循环结束。
注意:因为这是一个常见的编码错误使用C语言赋值运算符(=)当你想用(==)编译器会提醒你如果设置一个变量在一个条件分支或者循环像这样:
if (someVariable = YES) {
...
}
如果你真想重新分配变量(左边是统一分配的逻辑值)你可以表明这将分配在括号里。
if ( (someVariable = YES) ) {
...
}
作为快速枚举,你不能在枚举的时候改变集合。并且如你收集的名字。快速枚举比手动使用计数器枚举要快。
许多集合支持块的枚举:
NSArray,Dictionary,NSSet用块语法做枚举。对于块的详情在下章介绍。