Cocoa提供两个处理文件的通用类:属性列表和对象编码。
1.属性列表
在Cocoa中,有一个类名为属性列表(property list)的对象,通常简写为plist。这些列表包含 Cocoa知道如何操作的一组对象。具体来讲,Cocoa知道如何将它们保存到文件中并进行加载。属性列表类包括NSArray、NSDictionary、NSString、NSNumber、NSDate和NSData,以及它们的可修改形态变体(只要它们拥有前缀为Mutable的类)。
1.1 NSDate
NSDate是Cocoa中用于处理日期和时间的基础类。
1.1.1 获取当前日期和时间
可以使用[NSDate date]获取当前的日期和时间,它是一个自动释放的对象。因此,以下代码:
NSDate *currentDate = [NSDate date]; NSLog(@"crrent date:%@",currentDate);
将输出如下结果:
crrent date:2015-08-26 08:46:33 +0000
1.1.2 比较两个时间
可以使用一些方法比较两个日期,从而对列表进行排序。例如下面代码:
-(int)compareDate:(NSString*)date01 withDate:(NSString*)date02{ int ci; NSDateFormatter *df = [[NSDateFormatter alloc] init]; [df setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; NSDate *dt1 = [[NSDate alloc] init]; NSDate *dt2 = [[NSDate alloc] init]; dt1 = [df dateFromString:date01]; dt2 = [df dateFromString:date02]; NSComparisonResult result = [dt1 compare:dt2]; switch (result) { //date02比date01大 case NSOrderedAscending: ci=1; break; //date02比date01小 case NSOrderedDescending: ci=-1; break; //date02=date01 case NSOrderedSame: ci=0; break; default: NSLog(@"erorr dates %@, %@", dt2, dt1); break; } return ci; }
1.1.3 使用+dateWithTimeIntervalSinceNow方法获取特定时间
+dateWithTimeIntervalSinceNow: 接受一个NSTimeInterval参数,该参数是一个双精度值,表示以秒为单位的时间间隔。通过该参数可以指定时间偏移的方式:对于将来的时间,使用正的时间间隔。对于过去的时间,使用负的时间间隔。
使用:
可以使用该方法获取与当前时间间隔一定时差的日期。比如,获取24小时之前的确切时间:
NSDate *yesterday = [NSDate dateWithTimeIntervalSinceNow:-(24 * 60 *60)]; NSLog(@“yesterday is %@“,yesterday);
输出如下结果:
yesterday is 2015-08-25 08:51:29 +0000
如果想要设定输出结果的时间格式,使用NSDateFormatter类。
1.2 NSData
NSData包装了大量字节。你可以获得数据的长度和指向字节起始位置的指针。因为NSData是一个对象,适用于常规的内存管理行为。因此,如果将数据库传递给一个函数或方法,可以通过传递一个自动释放的NSData来实现,无需担心内存清楚问题。下面的NSData对象将保存一个普通的C字符串(一个字节序列),然后输出数据:
const char *string = "Hi there, this is a C string"; NSData *data = [NSData dataWithBytes:string length:strlen(string)+1]; NSLog(@"data is %@",data);
输出结果是:
data is <48692074 68657265 2c207468 69732069 73206120 43207374 72696e67 00>
说明:
-length方法给出字节数量,-byte方法给出指向字符串起始位置的指针。注意+dataWithBytes:调用中的+1,它用于包含C字符串所需的尾部的零字节。还要注意NSLog输出结果末尾的00.通过包含零字节,就可以使用%s格式的说明符输出字符串。
NSLog(@“%d byte string is %s”,[data length], [data bytes]);
输出结果如下:
30 byte stirng is “Hi there , there is a C string"
NSData对象是不可变更的,创建之后就不能改变。你可以使用它们,但是不能更改其中的内容。不过NSMutableData支持在数据内容中添加和删除字节。
1.3 属性列表的写入和读取
集合属性列表类(NSArray、NSDictionary)具有一个-writeToFile:atomically:方法,用具将属性列表写入文件。NSString和NSData也具有writeToFile:atomically:方法,但它只能写出字符串或数据块。
因此,可以将字符串存入一个数组,然后保存该数组:
NSArray *phrase = [NSArray arrayWithObjects:@“I”,@“seem”,@“to”,@“be”,@“a”,@“verb”,nil]; [phrase writeToFile:@“/tmp/verbiage.txt” atomically:YES];
该文件的内容为:
<? xml version = "1.0" encoding = "UTF-8" ?> |
2 |
<!DOCTYPE |
3 |
< plist version = "1.0" > |
4 |
< array > |
5 |
< string >I</ string > |
6 |
< string >seem</ string > |
7 |
< string >to</ string > |
8 |
< string >be</ string > |
9 |
< string >a</ string > |
10 |
< string >verb</ string > |
11 |
</ array > |
12 |
</ plist > |
Xcode还包含一个属性列表编辑器,所以可以查看plist文件并进行编辑。有些属性列表文件(特别是首选项文件)是以压缩的二进制格式存储的。通过使用plutil命令:plutil -convert xml1 filename.plist,可以将这些文件转换为可读的形式。
上述文件,可以使用 +arrayWithContentsOfFile:方法读取该文件。代码如下:
1 |
NSArray "/tmp/verbiage.txt" ]; |
2 |
NSLog(@ "%@" , |
- 对 writeToFile:方法中的atomically的解释
atomically:参数的值为BOOL类型,用于通知Cocoa是否应该首先将文件内容保存在临时文件中,当文件成功保存后,再将该临时文件和原始文件交换。这是一种安全机制:如果在保存过程中出现意外,不会破坏原始文件。但这种安全机制需要付出一定的代价:在保存过程中,由于原始文件仍然保存在磁盘上,所以需要使用双倍的磁盘空间。除非保存的文件非常大,否则应该自动保存文件。
-
NSData处理函数的缺点
它们不会返回任何错误信息。如果不能加载文件,只能从方法中得到 nil 指针,而不能确定出现了何种错误。
1.4 修改对象类型
需要注意,当你使用集合类从某个文件读取数据时,你无法修改数据的类型。一种解决方法是强制转换,遍历plist文件的内容并创建一个平行结构的可修改对象。
还有另外一个方法,用NSPropertyPlistSerialization类。正如文字所提示的,它可以为存储和加载属性列表的行为提那家很多你需要的设定项。
- propertyListFromData:mutabilityOption:format:errorDescription:方法
它能将plist数据返回给你,并且能在出现异常的时候提供错误信息。
以下是将plist数据内容以二进制形式写入文件的代码:
NSString *error = nil; NSData *encodedArray = [NSPropertyListSerialization dataFromPropertyList:capitols format:NSPropertyListBinaryFormat_v1_0 errorDescription:&error]; [encodedArray writeToFile:@“/tmp/capitols.txt” atomially:YES];
将数据读取回内存要多执行一步,即指定文件的类型。
NSPropertyListFormat propertyListFormat = NSPropertyListXMLFormat_v1_0; NSString *error = nil; NSMutableArray *capitols = [NSPropertyListSerialization propertyListFromData:data format:&propertyListFormat errorDescription:&error];
2.编码对象
Cocoa具备一种机制来将对象自身转换为某种格式并保存到磁盘中。
对象可以将它们的实例变量和其它数据编码为数据块,然后保存到磁盘中。以后这些数据还可以读回到内存中,并且还能基于保存的数据创建新对象。这个过程称为编码和解码(encoding and decoding),也可以称为序列化和反序列化(serialization
and deserialization)。
通过采用NSCoding协议,可以使自己的对象实现编码和解码。
该协议与下面的代码类似:
1 |
@protocol |
2 |
-( void ) |
3 |
-(id)initWithCoder:(NSCoder |
4 |
@end |
通过采用该协议,可以实现这两种方法。当对象需要保存自身时,-encodeWithCoder方法将被调用;当对象需要加载自身时,-initWithCoder:方法将被调用。
NSCoder是一个抽象类,定义一些有用的方法来在对象与NSData之间来回转换,完全不需要创建新的NSCoder。实际上我们要使用NSCoder的一些具体的子类来编码和解码对象,如其中的两个子类:NSKeyedArchiver
和 NSKeyedUnarchiver。
3.键值编码
许多人将键/值编码亲切的称为KVC,它是一种间接更改对象状态的方式,其实现方法是使用字符串描述要更改的对象状态部分。
3.1 KVC 简介
键/值编码中基本调用包括 -valueForKey: 和 -setValue:forKey:。以字符串的形式向对象发送消息,这个字符串是需要关注的属性的关键。
因此,请求car的名称示例如下:
1 |
NSString "name" ]; |
2 |
NSLog(@ "%@" , |
谨记这条规则
编译器都以下划线开头的形式保存实例变量名称。
以上代码将输出 Herbie.
valueForKey:的功能非常简单,它计算车的品牌并将其返回。其执行流程为:首先查找以键 -key或-isKey命名的getter方法。对于这两种调用,valueForKey:查找-name和-make。如果不存在getter方法,它将在对象内部查找名为_key或key的实例变量。如果没有通过@synthesize提供存取方法,valueForKey将会查找实例变量_name和name,或_make和make。
最后一点非常重要:-valueForKey:在Objective-C运行时中使用元数据打开对象并进入其中查找需要的信息。在C或C++语言中不能执行这种操作。通过使用KVC,可以获取不存在getter方法的对象值,无需通过对象指针直接访问实例变量。
可以对型号年份使用相同的技术:
1 |
NSLog(@ "Model , "modelYear" ]); |
注意,NSLog中的%@输出一个对象,但modelYear是一个int值,而不是对象。该如和处理?对于KVC,Cocoa自动放入和取出标量值。也就是说,当使用setValueForKey:时,它自动将标量值(int float struct)放入NSNumber或NSValue中;当使用-setValueForKey:时,它自动将标量值从动从对象中取出。仅KVC具有这种自动包装功能,常规方法调用和属性语法不具备该功能。
除用于检索值外,还可以使用-setValue:forKey:按名称设置值:
1 |
[car "Harold" forKey: "name" ]; |
这个方法的共组方式和-valueForKey:相同。它首先查找名称的setter方法,例如-setName,并使用参数@”Harold”调用它。如果不存在setter方法,它将再类中查找名为name或_name的实例变量,然后为它赋值。
谨记这条规则
编译器都以下划线开头的形式保存实例变量名称。
如果在调用 -setValue:forKey:之前设置一个标量值,需要将它包装起来:
1 |
[car "mileage" ]; |
同时,-setValue:forKey:在调用-setMileage:或更改mileage实例变量之前会取出该值。
3. 2 路径
键/值编码还支持指定键路径,就像文件系统路径一样,你可以遵循一系列关系来指定该路径。
键路径表示:可以指定圆点分割的不同属性名称。这样,通过查询car的“engine.horsepower”就能获取马力值。例如:
1 |
[car "engine.horsepower" ]; |
2 |
NSLog(@ "%@" , "engine.horsepower" ]); |
键路径的深度是任意的,具体取决于对象图(对象图是一种表示相关对象集合的有趣方式)的复杂度。在某种程度上,使用键路径比使用一系列嵌套方式调用更容易访问对象。
3.3 处理未定义的键
使用setValue:forUndefinedKey:方法更改未知键的值。
如果KVC机制无法找到处理方式,它会返回询问类如何处理,默认实现会取消操作。
示例:
首先创建一个可变字典:
1 |
@interface |
2 |
{ |
3 |
NSString |
4 |
NSMutableArray |
5 |
NSMutableDictionary |
6 |
} |
7 |
... |
8 |
@end |
接下来添加valueForUndefinedKey方法:
1 |
-( void )setValue:(id)value |
2 |
{ |
3 |
if (stuff |
4 |
stuff |
5 |
} |
6 |
[stuff |
7 |
} |
8 |
-(id)valueForUndefinedKey:(NSString |
9 |
id |
10 |
return (value); |
11 |
} |
并使用-dealloc释放字典。
与(null)的区别
是一种[NSNull null]对象,而(null)是一个真实存在的nil值。
4. NSPredicate
编写软件时,经常需要获取一个对象集合,并通过某些已知条件计算该集合的值。你需要保留复合某个条件的对象,删除那些不满足条件的对象,从而提供一些有意义的对象。Cocoa提供了一个名为NSPredicate的类,它用于指定过滤器的条件。可以创建NSPredicate对象,通过该对象准确地描述所需的条件,对每个对象通过谓词进行筛选,判断它们是否与条件相匹配。注意,这里的谓词通常用在数学和计算机科学概念中,表示计算真值或假值的函数。
4.1 创建谓词
在将NSPredicate应用于某个对象之前,首先需要创建它。可以通过两种基本方式来实现。第一种是创建许多对象,并将它们组合起来。这需要使用大量代码,如果正在构建通用用户接口来指定查询,采用这种方式比较简单。另一种方式是查询代码中的字符串。对初学者来说,这种方式比较简单。常见的面向字符串的API警告信息适用于查询字符串,特别适用于缺少编译器错误检查及有时出现奇怪的运行时错误等情况
谓词创建示例:
1 |
NSPredicate |
2 |
predicate "name ]; |
predicate是一个常用的Objective-C对象指针,它将指向NSPredicate对象。使用NSPredicate类方法+predicateWithFormat:来实际创建谓词。将某个字符串赋给谓词,+predicateWithFormat:使用该字符串在后台构建对象树,这些树将用来计算谓词的值。
这种谓词字符串看上去向是标准的C表达式。他的左侧是键路径name,随后是一个等于运算符“==”,右侧是一个引用字符串。如果位子字符串中的文本块未被引用,则该谓词字符串被看作是键路径;如果应用了文本块,则认为它是文本字符串。
计算谓词
通过以上步骤就可以得到一个谓词。接下来将通过某个对象计算谓词。
1 |
BOOL match |
2 |
NSLog(@ "%s" , "YES" : "NO" ); |
-evaluateWithObject:通知对象接收对象(谓词)根据指定的对象计算自身的值。在本例中,接收对象为car,使用name作为键路径,应用valueForKeyPath:方法获取名称。然后,它将自身的值(即名称)与“Herbie”相比较。如果相同,则返回YES,否则返回NO。
以下是另一个谓词:
1 |
predicate "engine.horsepower ]; |
2 |
match |
这里比较大小的,大于150的将被显示。
4.2 燃料过滤器
-filteredArrayUsingPredicate:是NSArray数组中的一种类别方法,它将玄幻过滤数组内容,根据谓词计算每个对象的值,并将值为YES的对象累计到将被返回的新数组中:
1 |
NSArray |
2 |
results |
3 |
NSLog(@ "%@" , |
4.3 格式说明符
如果需要知道那些汽车的马力高于200,稍后又需要知道那些汽车的马力高于50,可以使用谓词字符串。可以通过两种方式将不同的内容放入谓词格式字符串中:格式说明符和变量名。
1 |
predicate "engine.horsepower , |
新的格式字符串:通过NSPredicate字符串,可以使用 %k 指定键路径。该谓词和其他谓词相同,如:
1 |
predicate "%k , "name" , "Herbie" ]; |
另一种方式:江边两名放入字符串中,类似于环境变量:
1 |
NSPredicate "name ]; |
接下来,使用predicateWithSubstitutionVariables调用来构造新的专用谓词。创建一个键/值对字典,其中键是变量名(不含$),值是插入谓词的内容:
1 |
NSDictionary |
2 |
varDict "Herbie" , "NAME" , |
这里使用字符串“Herbie”作为键“NAME”的值。因此,构造以下形式的新谓词:
1 |
predicate |
可以使用不同的对象作为变量名称,如NSNumber.
4.4 运算符
4.1.1 比较运算符
> 大于
>=或=> 大于或等于
< 小于 <=或=< 小于护等于 !=或<> 不等于
谓词字符串语法还支持括号表达式和AND OR NOT逻辑运算符或者C样式的等效表达式 && !! !。
谓词字符串中的运算符不区分大小写。
4.4.2 数组运算符
使用某个运算符来查找介于两个值之间的数值,如下:
1 |
predicate "engine.horsepower ]; |
花括号表示数组,BETWEEN讲述组中的第一个元素看成是数组的下届,第二个元素看成是数组的上界。
可以使用%@格式说明符插入自己的NSArray对象,也可以使用变量。
还可以使用 IN 运算符查找数组中是否含有某个特定值,如 IN {‘A’,’B’,’C’}。
4.5.SELF 足够了
某些时候,可能需要将谓词应用于简单的值(如那些纯文本老式字符串),而并非那些可以通过键路径进行操作的对象。假设有一个汽车名称数组,并且需要应用前面相同的过滤器,从NSString对象中查询name时,将不能起到预期效果,那么,用什么来代替name呢?
用SELF来解决!SELF可以引用用于谓词计算的对象。事实上,可以将谓词中所有的键路径表示成对应的SELF。此谓词和前面的谓词完全相同,代码如下所示:
1 |
predicate "SELF.name ]; |
4.6 字符串运算符
BEGINSWITH 检查某个字符串是否以另一个字符串开头,如name BEGINSWITH ‘Bad’
ENDSWITH 检查某个字符串是否以另一个字符串结尾
CONTAINS 检查某个字符串是否在另一个字符串内部
上述匹配是区分大小写的,可以通过修饰符 [d] [cd]来控制
c 表示不区分大小写
d 不区分发音符号(即没有重音符)
cd 上述合二为一
如 name BEGINSWITH[cd] ‘HERB’
4.7 LIKE 运算符
? 表示于一个字符匹配
* 表示与任意个字符匹配
示例:
name LIKE ‘??er*’
LIKE 同样可以使用修饰符 c d cd
如果你热衷于正则表达式,可以使用 MATCHES 运算符
版权声明:本文为博主原创文章,转载请注明出处。