在iOS学习过程中,有时候需要保持用户数据,比如登录信息、用户的设置选项等,这时候就需要学习数据持久化操作,本节主要学习iOS数据持久化相关的知识。
数据持久化的方式有四种:
1).写入plist文件(属性列表)
2).偏好设置
3).归档(NSKeyedArchiver)
4).NSData
下面分别举例说明四种方式的适用场合以及用法。
1. 写入plist文件(属性列表)
1.1 plist可以存储哪些数据
属性列表是一种XML格式的文件,拓展名为plist
如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,就可以使用writeToFile:atomically:方法直接将对象写到属性列表文件中。
由于plist文件的root只有Array和Dictionary两种类型,所以最好只保持它们对应数据类型的数据。
比如当你存储字符串类型的数据的时候,Type就为空了。
1.2 如何存储
#pragma mark - 存储数据
- (IBAction)btnSaveData_Click:(UIButton *)sender
{
NSString *str = @"hello";
NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"test_str.plist"];
BOOL result = [str writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
if (result)
{
NSLog(@"存储数据成功");
}
else
{
NSLog(@"存储数据失败!");
}
}
程序运行结果如下:
自动生成的文件:
注意:plist不能存储自定义对象类型!
<span style="color:#000000;">#pragma mark - 存储数据
- (IBAction)btnSaveData_Click:(UIButton *)sender
{
// 文件的沙河路径
NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"test_obj.plist"];
// 创建Student对象
CYStudent *stu = [[CYStudent alloc]init];
stu.name = @"zhangsan";
stu.age = 18;
NSArray *arrTmp = @[stu];
BOOL result = [arrTmp writeToFile:filePath atomically:YES];
if (result)
{
NSLog(@"存储数据成功");
}
else
{
NSLog(@"存储数据失败!");
}
}</span>
程序运行结果如下:
1.3 plist文件的存储与读取过程
2. 偏好设置
2.1 使用场景
很多iOS应用都支持偏好设置,比如保存用户名、密码、字体大小等设置,iOS提供了一套标准的解决方案来为应用加入偏好设置功能
每个应用都有个NSUserDefaults实例,通过它来存取偏好设置。比如,保存用户名、字体大小、是否自动登录。
2.2 如何使用
登录成功后保存数据:
[[NSUserDefaults standardUserDefaults]setObject:self.txtAccount.text forKey:@"account"];
[[NSUserDefaults standardUserDefaults]setObject:self.txtPWD.text forKey:@"pwd"];
[[NSUserDefaults standardUserDefaults]setBool:self.swchRememberPWD.on forKey:@"isRememberPWD"];
[[NSUserDefaults standardUserDefaults]setBool:self.swchAutoLogin.on forKey:@"isAutoLogin"];
再次登录时读取数据:
// 获取偏好设置中的数据
self.txtAccount.text = [[NSUserDefaults standardUserDefaults]objectForKey:@"account"];
self.swchAutoLogin.on = [[NSUserDefaults standardUserDefaults]boolForKey:@"isAutoLogin"];
self.swchRememberPWD.on = [[NSUserDefaults standardUserDefaults]boolForKey:@"isRememberPWD"];
self.txtPWD.text = [[NSUserDefaults standardUserDefaults]objectForKey:@"pwd"];
注意:UserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize方法强制写入
[defaults synchornize];
3. 归档(NSKeyedArchiver)
3.1 使用场合
如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复
不是所有的对象都可以直接用这种方法进行归档,只有遵守了NSCoding协议的对象才可以
NSCoding协议有2个方法:
1)encodeWithCoder:
每次归档对象时,都会调用这个方法。一般在这个方法里面指定如何归档对象中的每个实例变量,可以使用encodeObject:forKey:方法归档实例变量
2)initWithCoder:
每次从文件中恢复(解码)对象时,都会调用这个方法。一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用decodeObject:forKey方法解码实例变量
3.2 如何使用
1> 自定义实体类
@interface CYContact : NSObject
/** 姓名 */
@property (nonatomic,copy) NSString *name;
/** 电话 */
@property (nonatomic,copy) NSString *phone;
@end
2> 实现NSCoding协议方法
#pragma mark - NSCoding协议方法
/*
Encodes the receiverusing a given archiver
通过一个给定的archiver把消息接收者进行编码。
当接收到encodeObject消息的时候,类终端encodeWithCoder方法被调用。
*/
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_name forKey:CYNameKey];
[aCoder encodeObject:_phone forKey:CYPhoneKey];
}
/*
Returns an objectinitialized from data in a given unarchiver. (required)
从一个给定unarchiver的数据中返回一个初始化对象。
*/
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init])
{
_name = [aDecoder decodeObjectForKey:CYNameKey];
_phone = [aDecoder decodeObjectForKey:CYPhoneKey];
}
return self;
}
/*
Returnsa new instance that’s a copy of the receiver
返回消息接收者的一个复制的新实例。
*/
- (id)copyWithZone:(NSZone *)zone
{
CYContact *copy = [[[self class] allocWithZone:zone] init];
copy.name = [self.name copyWithZone:zone];
copy.phone = self.phone;
return copy;
}
3> 归档
[NSKeyedArchiver archiveRootObject:self.contacts toFile:CYFilePath];
4> 接档
self.contacts = [NSKeyedUnarchiver unarchiveObjectWithFile:CYFilePath];
5> 注意点
如果父类也遵守了NSCoding协议,请注意:
• 应该在encodeWithCoder:方法中加上一句[super encodeWithCode:encode];确保继承的实例变量也能被编码,即也能被归档
• 应该在initWithCoder:方法中加上一句self = [super initWithCoder:decoder];确保继承的实例变量也能被解码,即也能被恢复
4. NSData
4.1 使用场合
使用archiveRootObject:toFile:方法可以将一个对象直接写入到一个文件中,但有时候可能想将多个对象写入到同一个文件中,那么 就要使用NSData来进行归档对象
NSData可以为一些数据提供临时存储空间,以便随后写入文件,或者存放从磁盘读取的文件内容。可以使用[NSMutableData data]创建 可变数据空间
注:黑色箭头表示将对象归档到文件中,红色箭头表示从文件中恢复对象
4.2 如何使用
#pragma mark - 存储数据
- (IBAction)btnSaveData_Click:(UIButton *)sender
{
// NSData-归档2个Person对象到同一文件中
// 实例化对象
CYStudent *stu1 = [CYStudent studentWithName:@"zhangsan" age:18];
CYStudent *stu2 = [CYStudent studentWithName:@"lisi" age:20];
// 新建一块可变数据区
NSMutableData *data = [NSMutableData data];
// 将数据区连接到一个NSKeyedArchiver对象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
// 开始存档对象,存档的数据都会存储到NSMutableData中
[archiver encodeObject:stu1 forKey:@"stu1"];
[archiver encodeObject:stu2 forKey:@"stu2"];
// 存档完毕(一定要调用这个方法)
[archiver finishEncoding];
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"datas.data"];
// 将存档的数据写入文件
BOOL result = [data writeToFile:path atomically:YES];
if (result)
{
NSLog(@"存储数据成功");
}
else
{
NSLog(@"存储数据失败!");
}
}
#pragma mark - 读取数据
- (IBAction)btnReadData_Click:(UIButton *)sender
{
// NSData-从同一文件中恢复2个Person对象
// 从文件中读取数据
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"datas.data"];
NSData *data = [NSData dataWithContentsOfFile:path];
// 根据数据,解析成一个NSKeyedUnarchiver对象
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
CYStudent *stu1 = [unarchiver decodeObjectForKey:@"stu1"];
CYStudent *stu2 = [unarchiver decodeObjectForKey:@"stu2"];
// 恢复完毕
[unarchiver finishDecoding];
NSLog(@"%@",stu1);
NSLog(@"%@",stu2);
}
#pragma mark - 利用归档实现深复制
- (IBAction)btnDeepCopy_Click:(UIButton *)sender
{
// 比如对一个CYStudent对象进行深复制
// 临时存储stu1的数据
CYStudent *stu1 = [CYStudent studentWithName:@"zhangsan" age:18];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:stu1];
// 解析data,生成一个新的Person对象
CYStudent *stu2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
// 分别打印内存地址
NSLog(@"stu1:%p", stu1); // stu1:0x7bdb32b0
NSLog(@"stu2:%p", stu2); // stu2:0x7bdb6b80
}