应同学之约,抽个时间,写篇关于 NSUserDefaults 实现保存数据的文章。
确实在项目中,虽然我们数据都是通过 API ,从后台获取,但是很多时候,我们需要做数据的本地缓保,以便实现我们在没有网络的时候可以读取本地数据,而不致于说获取不到数据,整个页面都空白的尴尬。还有如果是已经有本地数据缓保,那么就不用每次都从通过网络从后台获取,可以提高用户体验,也不至于浪费太多用户的流量。我们就先讨论这两种简单的情况,其他特殊情况先排除开来。
那么一般我们接触到的本地存储数据就几种:
1,归档写入沙盒
[str writeToFile:strFilePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
2,数据库
可以借助 FMDB 等第三方库去实现
3,NSUserDefaults
当然大神如果有不同见解,欢迎提出。
这三种方法也是各有利弊,至于有什么不同,跟优缺点,大家就自行百度、google吧,这里就不讲太多。
今天我们就主要讲一下第3种,最简单粗暴的方法。
NSUserDefaults是一个单例,在整个程序中只有一个实例对象,而且不同于我们自己定义的单例,因为他不会随着这个程序的关闭而清除掉保存的数据,下次你开启程序,你之前保存的数据还是存在的,所以他可以用于本地数据的持久化,而且简单实用,我们也可以用来做为页面间的传值。
NSUserDefaults支持的数据类型有:NSNumber(NSInteger、float、double),NSString,NSDate,NSArray,NSDictionary,BOOL.
其实他的用法非常简单,跟我们平时使用字典基本一样。一个key,一个Value。
就比如说,我们在做登陆功能的时候,基本上都要记住用户的用户名,下次用户再打开APP的时候,登陆的时候就不用再输入用户名了。保存代码如下:
//将NSString 对象存储到 NSUserDefaults 中
NSString *userName = @"Jimmy.Lin";
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:userName forKey:@"saveUserName"];
那么下次打开登陆页面的时候,就可以直接从NSUserDefaults中取出我们所保存的用户名,直接使用了。取出保存的数据代码如下:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// 这里用到的 key 就是上次我们保存的时候所使用的 key
NSString *userName = [ defaults objectForKey:@"saveUserName"];
这里需要注意一点,就是如果你不想数据被覆盖掉,那就需要使用不同的key,换句话讲,就是你现在里面已经用"saveUserName"这个 key 保存了这一次的用户名,如果你再使用同样的这一个 key 保存数据的话,那第二次的 value 会覆盖掉第一次的 value,就是你再次取值的时候,取出来的就是第二次所保存的 value 了。
恩,讲到这里,也许你会在使用过程中,出现你写了保存的代码,但是你再次取值的时候却取不出你之前保存的值,心里估计在骂娘 ,尼玛,苹果这么垃圾,自己的方法都出错,还让我们用。
不用慌,NSUserDefaults偶尔不工作,无法保存改动的数据的原因是,NSUserDefaults的机制是,过一段时间,会自动调用自己的函数synchronize去同步数据的。
而如果你是遇到和我此处类似的,改动数据后,就退出程序了,即在NSUserDefaults还没来得及synchronize之前就退出程序,就需要手动调用synchronize去保存数据了。
也就是说我们可以手动调用NSUserDefaults去执行同步synchronize的动作,以及时保存(修改了的)数据。
那么为了确保万无一失,我们把上面的保存代码完善一下:
//将NSString 对象存储到 NSUserDefaults 中
NSString *userName = @"Jimmy.Lin";
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:userName forKey:@"saveUserName"];
// 手动调用NSUserDefaults去执行同步synchronize的动作,以及时保存(修改了的)数据
[defaults synchronize];
OK,再也不会出现坑爹的保存了数据取不出来值的情况了。
上面讲的是简单的保存简单的字符串,但是一般我们用后台获取到的数据都是 json ,那么我们转化完之后,就是字典或者数组了。那同样的,这些数据也是一样的保存。
// 保存数组
NSArray *arr = @[@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9"];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:arr forKey:@"saveArray"];
[defaults synchronize];
// 读取我们保存的数组
NSArray *getArr = [defaults objectForKey:@"saveArray"];
// 打印出来看看是否正确
for (NSString *number in getArr) {
NSLog(@"number = %@",number);
}
打印结果,正确,perfect
保存字典的用法一样,这里就不重复了。
这里同样有一点是需要特别注意的,就是NSUserDefaults保存的数据全是不可变的!!!这一点是非常重要的,弄错的话会有 bug 。到时候别说我是照着这方法保存的,拿出来用,却崩了。真的不是这个方法有问题!
当然如果你只是保存的话是没什么问题的,也不会出现程序崩的情况:
按上面的代码来的话,运行完发现也能拿到我们要的结果,一切正常,那是不是觉得我们拿到的就是我们保存的可变数组?
那我们来验证一下,如果真的是可变数据,那就可以添加删除里面的元素,再次运行,发现崩了。
看到这,很好理解吧。我们拿出来的是一个不可变数组!所以你添加元素的话肯定就崩啦!
那如果我就是非要往拿出来的数组里面添加元素怎么办呢?很简单,先转成可变的,再添加,然后再用同样的 key 保存进去覆盖掉之前的数据就搞定了
恩。好像用法就是这么简单!看到这里是不是觉得这货不就是一个字典吗?所以你会用字典,这货你也肯定会用 ,so easy !
但是问题又来了,数据拿到的时候是 json ,但是我们基本上都是转换成 model ,然后放进数组里面,这样方便后期处理跟使用。看一下上面讲的 NSUserDefaults 能保存的数据类型,model 明显是不行的。但是别忘了 iOS 强大的数据转化,看一下,NSUserDefaults可以保存 NSData 的数据类型,那么就好办了,只不过是需要多绕一个圈而已。那么怎么来实现这些自定义 model 的数据保存呢?
还是让我们一步一步来吧,首先,那先定义一个 model 类,里面就简单的加几个属性吧,
@interface AlertModel : NSObject
/***事件ID**/
@property (nonatomic , strong) NSString *eventid;
/***动作事件ID**/
@property (nonatomic , strong) NSString *actionEventid;
/***问题的高**/
@property (nonatomic , strong) NSString *problomHeight1;
@property (nonatomic , strong) NSString *problomHeight2;
@property (nonatomic , strong) NSString *problomHeight3;
/***主机名称**/
@property (nonatomic , strong) NSString *host;
/***问题描述**/
@property (nonatomic , strong) NSString *descriptionI;
/***问题发生时间**/
@property (nonatomic , strong) NSString *clock;
@end
那我们要实现将 model 类转换成 NSData 类型的,就必须归档,这里要实现 在.h 文件中遵循 NSCoding 协议,再 在 .m 中实现 encodeWithCoder 方法 和
initWithCoder 方法就可以了 在 .m 文件里实现我们上面自定义的初始化方法,跟 NSCoding 协议方法:
@implementation AlertModel
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.problomHeight1 = [aDecoder decodeObjectForKey:@"problomHeight1"];
self.problomHeight2 = [aDecoder decodeObjectForKey:@"problomHeight2"];
self.problomHeight3 = [aDecoder decodeObjectForKey:@"problomHeight3"];
self.host = [aDecoder decodeObjectForKey:@"host"];
self.descriptionI = [aDecoder decodeObjectForKey:@"descriptionI"];
self.clock = [aDecoder decodeObjectForKey:@"clock"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.problomHeight1 forKey:@"problomHeight1"];
[aCoder encodeObject:self.problomHeight2 forKey:@"problomHeight2"];
[aCoder encodeObject:self.problomHeight3 forKey:@"problomHeight3"];
[aCoder encodeObject:self.host forKey:@"host"];
[aCoder encodeObject:self.descriptionI forKey:@"descriptionI"];
[aCoder encodeObject:self.clock forKey:@"clock"];
}
@end
这样做就可以将我们自定义的 model 类型转变为 NSData 类型了。
直接上代码吧:
存数据:
NSData *modelDataArr = [NSKeyedArchiver archivedDataWithRootObject:self.dataArray];
[[NSUserDefaults standardUserDefaults] setObject:modelDataArr forKey:@"modelDataArray"];
[[NSUserDefaults standardUserDefaults] synchronize];
取数据:
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"modelDataArray"]) {
NSData *getModelData = [[NSUserDefaults standardUserDefaults] objectForKey:@"modelDataArray"];
NSArray *getArray = [NSKeyedUnarchiver unarchiveObjectWithData:getModelData];
[self.dataArray removeAllObjects];
[self.dataArray addObjectsFromArray:getArray];
[_tmpTableView reloadData];
[_tmpTableView.mj_header endRefreshing];
}
好了,到这基本上也没什么好讲的了,都是很简单的用法,至少比第1种,第2种方法使用起来简单太多了!
不过用 NSUserDefaults 保存数据的话,要考虑的问题就是安全性!至于你要用加密还是什么别的方法的话,我在这就不多加讨论了,因为每个公司每个人处理的方法都不一样。