iOS常见的数据持久化方式主要有以下几点:
- plist (属性列表)
- NSUserDefaults 偏好设置 单例
- writeToFile 写入文件
- SQLite 数据库
- CoreData
plist存储
通常叫做plist文件,用于存储在程序中不经常修改、数据量小的数据,不支持自定义对象存储,支持数据存储的类型为:Array,Dictionary,String,Number,Data,Date,Boolean,通常用来存放接口名、城市名、银行名称、表情名等极少修改的数据
直接创建.plist文件, 如果文件类型是Array就用数组获取,如果是dictionary就用字典获取
//获取plist文件路径
NSString *path = [[NSBundle mainBundle] pathForResource:@"testProperty" ofType:@"plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
// NSArray *array = [NSArray arrayWithContentsOfFile:path];
偏好设置(NSUserDefaults)
用于存储用户的偏好设置,同样适合于存储轻量级的用户数据,数据会自动保存在沙盒的Libarary/Preferences目录下,本质上就是一个plist文件,所以同样的不支持自定义对象存储,支持数据存储的类型为:Array,Dictionary,String,Number,Data,Date,Boolean,可以用做检查版本是否更新、是否启动引导页、自动登录、版本号等等,需要注意的是NSUserDefaults是定时的将缓存中的数据写入磁盘,并不是即时写入,为了防止在写完NSUserDefaults后,程序退出导致数据的丢失,可以在写入数据后使用synchronize强制立即将数据写入磁盘
//存
[[NSUserDefaults standardUserDefaults] setObject:phoneNumber forKey:@"key"];
[[NSUserDefaults standardUserDefaults] synchronize];
//取
[[NSUserDefaults standardUserDefaults] objectForKey:@"key"];
文件写入(writeToFile)
一 、沙盒路径
沙盒主路径:是程序运行期间系统会生成一个专属的沙盒路径,应用程序在使用期间非代码的文件都存储在当前的文件夹路径里面
Documents:用来存储永久性的数据的文件 程序运行时所需要的必要的文件都存储在这里(数据库)iTunes会自动备份这里面的文件
Library:用于保存程序运行期间生成的文件
Caches:文件夹位于Library中用于保存程序运行期间产生的缓存文件
Preferences:文件夹位于Library中,主要是保存一些用户偏好设置的信息,一般情况下,我们不直接打开这个文件夹 而是通过NSUserDefaults进行偏好设置的存储
tmp:临时文件夹---程序运行期间产生的临时岁骗会保存在这个文件夹中 通常文件下载完之后或者程序退出的灰自动清空此文件夹iTunes不会备份这里的数据。
//获取沙盒路径
NSString *homePath =NSHomeDirectory();
NSLog(@"%@",homePath);
//第一个参数:要查询的文件的路径
//第二个参数:要查询路径所属的用户 iOS是单用户
//第三个参数的意思 YES是绝对路径 NO是相对路径
//区别于OS-X系统 iOS应用文件夹中通常只有一个文件路径 由于OC同时支持的苹果系列产品的开发 在MacOS里面会同时存在很多软件 通常生成的路径放在一个数组里面
//iOS端一次只有一个应用 所以取数组唯一的一个元素即可
NSArray *documentArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);
NSLog(@"%@",documentArray);//打印的结果是 "~/Documents"
NSString *documentPath = [documentArray firstObject];
NSLog(@"documentPath = %@",documentPath);//结果是 ~/Documents
对比以上我们可以打印试着获取几个路径
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)firstObject];
NSLog(@"libraryPath = %@",libraryPath);
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)firstObject];
NSLog(@"ChchesPath = %@",cachesPath);
NSString *preferencePath =[libraryPath stringByAppendingString:@"/Preferences"];
NSLog(@"%@",preferencePath);
二、简单对象写入文件
文件的写入会替换调原有文件的内容
-(void)writeToFiled:(NSString*)appendPath data:(id)data{
//第一个参数:要查询的文件的路径
//第二个参数:要查询路径所属的用户 iOS是单用户
//第三个参数的意思 YES是绝对路径 NO是相对路径
//区别于OS-X系统 iOS应用文件夹中通常只有一个文件路径 由于OC同时支持的苹果系列产品的开发 在MacOS里面会同时存在很多软件 通常生成的路径放在一个数组里面
//iOS端一次只有一个应用 所以取数组唯一的一个元素即可
//writetofile 保存的数组和字典是以plist格式书写的
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask ,YES).firstObject;
NSString *filePath = [documentPath stringByAppendingString:appendPath];
if (![[NSFileManager defaultManager]fileExistsAtPath:filePath]) {
[[NSFileManager defaultManager]createFileAtPath:filePath contents:nil attributes:nil] ;
}
NSError *error ;
BOOL isSuccess = NO;
if ([data isKindOfClass:[NSString class]]) {
isSuccess = [data writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
// 取
// NSString *contentString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
}else if([data isKindOfClass:[NSArray class]] || [data isKindOfClass:[NSDictionary class]] || [data isKindOfClass:[NSData class]]){
isSuccess = [data writeToFile:filePath atomically:YES];
// 取
// NSArray *readArray = [NSArray arrayWithContentsOfFile:path];
// NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:path];
// NSData *data = [NSData dataWithContentsOfFile:path];
}
if (isSuccess && error == nil) {
NSLog(@"成功") ;
}
else{
NSLog(@"error == %@",error) ;
}
}
如果需要将数据持续写入,通过NSFileHandle进行操作
-(void)writeAddToFiled:(NSString*)appendPath data:(id)data{
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask ,YES).firstObject;
NSString *filePath = [documentPath stringByAppendingString:appendPath];
if (![[NSFileManager defaultManager]fileExistsAtPath:filePath]) {
[[NSFileManager defaultManager]createFileAtPath:filePath contents:nil attributes:nil] ;
}
NSData *rData = nil ;
if ([data isKindOfClass:[NSData class]]) {
rData = data ;
}else if ([data isKindOfClass:[NSArray class]] || [data isKindOfClass:[NSDictionary class]]) {
rData = [NSJSONSerialization dataWithJSONObject:data options:NSJSONWritingPrettyPrinted error:nil];
}else if ([data isKindOfClass:[NSString class]]){
rData = [data dataUsingEncoding:NSUTF8StringEncoding] ;
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath] ;
if (fileHandle) {
[fileHandle seekToEndOfFile];
[fileHandle writeData:rData] ;
[fileHandle closeFile];
}
}
二、复杂对象写入文件
在这里有一点是需要我们区分的,归档并不是数据持久化的方式 而是辅助复杂对象转化成简单对象的一种方式 其实真正实现数据持久化的仍然是写入文件writeToFile,归档后的文件是加密的,也更加的安全
创建Model类继承自NSObject ,遵守NSCoding或者NSSecureCoding协议,重写两个方法。 如果采用NSSecureCoding协议,必须重写supportsSecureCoding 方法并返回YES
@interface KLUserModel : NSObject<NSSecureCoding>
@property(nonatomic,strong)NSString *rName;
@property(nonatomic,assign)int rAge;
@end
@implementation KLUserModel
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
NSLog(@"encodeWithCoder");
[coder encodeObject:_rName forKey:@"name"];
[coder encodeInt:_rAge forKey:@"age"];
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
if (self = [super init]) {
NSLog(@"initWithCoder");
self.rName = [coder decodeObjectForKey:@"name"];
self.rAge = [coder decodeIntForKey:@"age"];
}
return self ;
}
+(BOOL)supportsSecureCoding{
return YES ;
}
@end
//取数据
-(void)pvt_getModel{
//会调用对象的initWithCoder方法
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES).firstObject ;
NSData *data = [NSData dataWithContentsOfFile:[documentPath stringByAppendingString:@"/user.text"]] ;
if (@available(iOS 11.0, *)) {
NSError *error ;
id content = [NSKeyedUnarchiver unarchivedObjectOfClass:[KLUserModel class] fromData:data error:&error];
} else {
//Fallback on earlier versions
// id content = [NSKeyedUnarchiver unarchiveObjectWithData:data];
id content = [NSKeyedUnarchiver unarchiveObjectWithFile:[documentPath stringByAppendingString:@"/user.text"]];
}
}
//存数据
-(void)pvt_saveModel {
//会调用对象的encodeWithCoder方法
KLUserModel *model = [KLUserModel new] ;
model.rName = @"the name";
model.rAge = 19;
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES).firstObject ;
if (@available(iOS 11.0, *)) {
NSError *error ;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:model requiringSecureCoding:YES error:&error] ;
if (error) {
return;
}
[data writeToFile:[documentPath stringByAppendingString:@"/user.text"] atomically:YES];
}else{
BOOL isSuccess = [NSKeyedArchiver archiveRootObject:model toFile:[documentPath stringByAppendingString:@"/user.text"]];
if (isSuccess) {
NSLog(@"Success") ;
}
}
}
CoreData 数据存储
1. 导入 CoreData 框架:
Build Phases -> Link Binary With Libraries -> 添加 CoreData.framework
2. 创建xcdatamodeld 文件
File -> New -> File -> iOS -> Core Data -> Data Model -> XXX.xcdatamodeld
3. 创建实体
XXX.xcdatamodeld -> Add Entity -> 添加对应的 Attributes
创建好实体对象XXX.xcdatamodeld之后,右侧属性栏Code Generation下面的Language默认为Swift,这里使用OC,就改成Objective-C;
- Codegen 默认为Class Definition,如果使用自动创建实体关联文件,则无需更改。通过Xcode的Build会自动生成对应的实体关联文件,但是这些文件不会在目录中显示出来,但对应的格式为:
//格式
实体名(表名)+CoreDataClass.h
实体名(表名)+CoreDataClass.m
实体名(表名)+CoreDataProperties.h
实体名(表名)+CoreDataProperties.m
//例如,实体名(表名)为Userinfo,对应的关联文件为:
Userinfo+CoreDataClass.h
Userinfo+CoreDataClass.m
Userinfo+CoreDataProperties.h
Userinfo+CoreDataProperties.m
- 如果使用手动创建实体关联文件,Codegen一定得设置为Manual/None,否则会报文件重复错误,选中实体,点击Editor,点击Create NSManagedObject Subclass…生成实体关联文件:
Userinfo+CoreDataClass.h
Userinfo+CoreDataClass.m
Userinfo+CoreDataProperties.h
Userinfo+CoreDataProperties.m
4. 创建数据库
/**
管理数据的对象
*/
@property (nonatomic, strong) NSManagedObjectContext *rContext;
-(void)pvt_createDB{
NSURL *pathURL = [[NSBundle mainBundle]URLForResource:@"KLCoreModel" withExtension:@"momd"];;
NSManagedObjectModel *mode = [[NSManagedObjectModel alloc]initWithContentsOfURL:pathURL]; ;
NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc]initWithManagedObjectModel:mode];
NSString *dpPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingString:@"/userInfo.sqlite"];
NSURL *url = [NSURL fileURLWithPath:dpPath];
NSError *error;
[coord addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error];
if (error == nil) {
NSLog(@"数据库添加成功");
} else {
NSLog(@"数据库添加失败%@",error);
}
_rContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSMainQueueConcurrencyType] ;
_rContext.persistentStoreCoordinator = coord ;
}
5. 插入数据
-(void)pvt_addData{
Userinfo *rperson = [NSEntityDescription insertNewObjectForEntityForName:@"Userinfo" inManagedObjectContext:self.rContext];
rperson.rName = @"ray";
rperson.rAge = 18;
[self.rContext save:nil];
}
6. 删除数据,修改数据,查询数据
-(void)pvt_changeData{
NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Userinfo"] ;
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"rAge=%d",18] ;
fetchRequest.predicate = predicate ;
NSError *error;
//查询数据,如不设置predicate 返回所有数据
NSArray *results = [self.rContext executeFetchRequest:fetchRequest error:&error] ;
if (results) {
for (Userinfo *user in results) {
//修改数据
user.rAge = 11 ;
//删除数据
// [_context deleteObject:user];
}
[self.rContext save:nil];
}
}
参考:https://www.jianshu.com/p/5b13960eefa2
https://www.jianshu.com/p/e41e73f4edec
https://www.jianshu.com/p/e98daf9fec78
https://www.jianshu.com/p/1cf3de935d21
网友评论