ios-本地数据存储有哪几种方式?
1.NSKeyedArchiver
在iOS中,对象的序列化和反序列化分别使用NSKeyedArchiver和NSKeyedUnarchiver两个类,我们可以把一个类对象进行序列化(把对象转化成字节流)然后保存到文件中(保存的文件都是加密的),使用时再读取文件,把内容反序列化(把字节流转化成对象)。这个过程通常也被称为对象的编码(归档)和解码(解归档)。
需要注意的是,NSKeyedArchiver和NSKeyedUnarchiver是继承于NSCoder这个抽象类的,所以我们在归档自定义类的时候需要实现NSCoding协议,并且实现协议方法。
缺点:只能一次性归档保存以及一次性解压。所以只能针对小量数据,对数据操作比较笨拙,如果想改动数据的某一小部分,需要解压或归档整个数据。
---归档Foundation框架的对象
NSDictionary *infoDiC = @{@"name":@"张三",@"sex":@"男"};
NSString *filePath = [self getFileName:@"info_dic"];
//字典归档
if ([NSKeyedArchiver archiveRootObject:infoDiC toFile:filePath]) {
NSLog(@"字典归档成功,路径%@",filePath);
}
//数组归档
NSArray *infoArray = @[@"C",@"Swift",@"Python"];
NSString *filePath_Array = [self getFileName:@"info_array"];
if ([NSKeyedArchiver archiveRootObject:infoArray toFile:filePath_Array]) {
NSLog(@"数组归档成功,路径%@",filePath_Array);
}
//nsstring 归档
NSString *infoStr = @"我爱编程";
NSString *filePath_str = [self getFileName:@"info_str"];
if ([NSKeyedArchiver archiveRootObject:infoStr toFile:filePath_str]) {
NSLog(@"字符串归档成功,路径%@",filePath_str);
}
//nsnumber 归档
NSNumber *infoNumber = [NSNumber numberWithInt:100];
NSString *filePath_Number = [self getFileName:@"info_number"];
if ([NSKeyedArchiver archiveRootObject:infoNumber toFile:filePath_Number]) {
NSLog(@"NSNumber归档成功,路径%@",filePath_Number);
}
//解归档
NSDictionary *uninfoDic = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"%@",uninfoDic);
- (NSString *)getFileName:(NSString *)name{
// 因为归档后的文件是加密的,这里的文件后缀可以随便写
name = [NSString stringWithFormat:@"%@.tfs_archiver",name];
return [NSString stringWithFormat:@"%@",[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:name]];
}
---归档自定义类对象
//loginModel.h
#import <Foundation/Foundation.h>
@interface loginModel : NSObject <NSCoding>//实现NSCoding协议
@property(nonatomic,copy)NSString *userName;
@property(nonatomic,copy)NSString *nickName;
@end
//loginModel.m
#import "loginModel.h"
@implementation loginModel
//下面是实现NSCoding协议的两个方法
//归档时调用
- (void)encodeWithCoder:(NSCoder *)aCoder{
// 每次归档对象时,都会调用这个方法。一般在这个方法里面指定如何归档对象中的每个实例变量,可以使用encodeObject:forKey:方法归档实例变量
[aCoder encodeObject:_userName forKey:@"userName"];
[aCoder encodeObject:_nickName forKey:@"nickName"];
}
//解归档时调用
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder {
// 每次从文件中恢复(解码)对象时,都会调用这个方法。一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用decodeObject:forKey方法解码实例变量
self = [super init];
if (self) {
self.userName = [aDecoder decodeObjectForKey:@"userName"];
self.nickName = [aDecoder decodeObjectForKey:@"nickName"];
}
return self;
}
@end
loginModel *log = [[loginModel alloc]init];
log.userName = @"大黄";
log.nickName = @"点点";
NSString *filePath_log = [self getFileName:@"info_login"];
if ([NSKeyedArchiver archiveRootObject:log toFile:filePath_log]) {
NSLog(@"自定义对象归档成功,路径%@",filePath_log);
}
//解归档
loginModel *unlog = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath_log];
NSLog(@"%@,%@",unlog.userName,unlog.nickName);
---对多个对象进行归档
archiveRootObject: toFile: 方法只能对一个对象进行归档,当我们需要对多个对象进行归档时可以使用如下操作。
//1.使用NSdata存放归档数据
NSMutableData *infoData = [NSMutableData data];
//2.归档文件存储路径
NSString *filePath_data = [self getFileName:@"info_data"];
//3.根据data初始化归档对象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:infoData];
//4.添加归档内容(设置键值对)
[archiver encodeObject:infoArray forKey:@"Array"];
[archiver encodeObject:infoDiC forKey:@"Dictionary"];
[archiver encodeInt:20 forKey:@"age"];
//5.完成归档
[archiver finishEncoding];
//6.存储归档信息
[infoData writeToFile:filePath_data atomically:YES];
//解归档
//1、从磁盘读取文件,生成NSData实例
NSData *unarchiverData = [NSData dataWithContentsOfFile: filePath_data];
//2.根据data初始化反归档对象
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData: unarchiverData];
//3.根据key访问值
NSLog(@"%@",[unarchiver decodeObjectForKey:@"Array"]);
NSLog(@"%@",[unarchiver decodeObjectForKey:@"Dictionary"]);
NSLog(@"%d",[unarchiver decodeIntForKey:@"age"]);
//4.完成解归档
[unarchiver finishDecoding];
归档成功保存的文件
2.NSUserDefaults
以key-value的形式存储在应用包的plist文件中,用来保存应用程序设置和属性、用户保存的数据。用户再次打开程序或关机后这些数据任然存在。NSUserDefaults存储的数据类型包括,NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。
注意:
- NSUserDefaults是一个单例,在整个程序中只有一个实例对象
- 对相同的key赋值会覆盖上次的值,所以请保证key的唯一性
- NSUserDefaults存储的对象全是不可变的,就算你存的时候是可变的,最后都会被转成不可变的
- NSUserDefaults存储的路径是Library/Preferences
- NSUserDefaults存储的数据是以明文显示的
//存储对象
[[NSUserDefaults standardUserDefaults] setObject:@"我爱编程" forKey:@"user_key"];
[[NSUserDefaults standardUserDefaults] synchronize];
//删除对象
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"user_key"];
NSUserDefaults存储的数据
3.SQLite
采用SQLite数据库来存储数据,SQLite作为一中小型数据库,应用在iOS中,跟前2种相比,相对比较复杂一些。
4.CoreData
CoreData是苹果提供的原生的用于对象化管理数据并且持久化的框架。CoreData本质上是将底层数据库封装成对象进行管理。
优点:
- CoreData操作数据不需要使用SQLite代码
- CoreData把数据用面向对象方式进行管理,操作数据库更方便
缺点:
写代码之前要掌握的 Core Data 相关成员对象:
-
1.NSManagedObject
被管理的数据记录 Managed Object Model 是描述应用程序的数据模型,这个模型包含 实体(Entity),特性(Property),读取请求(Fetch Request)等 -
2.NSManagedObjectContext
管理对象上下⽂文,持久性存储模型对象,参与对数据对象进⾏行各种操作的全过程,并监测 数据对象的变化,以提供对 undo/redo 的支持及更新绑定到数据的 UI。 -
3.NSPersistentStoreCoordinator
连接数据库的 Persistent Store Coordinator 相当于数据文件管理器,处理底层的对数据 文件的读取与写入。⼀般我们⽆无需与它打交道。 -
4.NSManagedObjectModel
被管理的数据模型,数据结构 -
5.NSFetchRequest
数据请求 -
6.NSEntityDescription
表格实体结构 -
7. NSPersistentContainer
ios10新增的类,封装了应用程序中的CoreData Stack(核心数据栈堆)
CoreData 手动创建使用流程
- 1.创建模型文件 [相当于一个数据库]
- 2.添加实体 [一张表]
- 3.创建实体类 [相当模型--表结构]
- 4.生成上下文 关联模型文件生成数据库
如果在已经创建好的项目里使用core data,此时我们需要手动创建,下面我们手动创建core data并 完成增、删、改、查操作。
1.创建模型文件
添加实体
2.添加实体 Student 实体
3.创建实体类 创建实体类.png
选择Core Data
生成的实体类
4.生成上下文关联模型文件生成数据库
ios 10以后我们可以直接使用NSPersistentContainer生成。
AppDelegate.h
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic,strong)NSPersistentContainer *persistentContainer;//CoreData Stack容器
//保存上下文
- (void)saveContext;
@end
AppDelegate.m
- (NSPersistentContainer *)persistentContainer{
if (!_persistentContainer) {
_persistentContainer = [[NSPersistentContainer alloc]initWithName:@"CoreData"];
[_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
if (error != nil) {
NSLog(@"Unresolved error %@, %@", error, error.userInfo);
}
}];
}
return _persistentContainer;
}
- (void)saveContext {
NSManagedObjectContext *context = self.persistentContainer.viewContext;
NSError *error = nil;
if ([context hasChanges] && ![context save:&error]) {
NSLog(@"Unresolved error %@, %@", error, error.userInfo);
}
}
插入数据
- (void)insertCoreData{
//创建一个实体 Entity 相当于数据库中的一个表,它描述一种抽象数据类型,其对应的类为 NSManagedObject 或其子类
Student *student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.persistentContainer.viewContext];
student.name = @"ZhangSan";
student.age = 28;
//保存上下文
[self saveContext];
}
在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法中调用 insertCoreData
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[self insertCoreData];
return YES;
}
运行后在项目的沙盒目录下生成了如下文件:
使用终端查看CoreData.sqlite(也可以使用其他sqlite客户端打开)
删除数据
- (void)deleteCoreData{
//获取数据请求对象,指明实体进行删除操作
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
//通过创建谓词对象,然后过滤掉符合要求的对象,也就是要删除的对象
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@",@"ZhangSan"];
request.predicate = predicate;
//获取所有实体对象
NSError *error = nil;
NSArray<Student *> *students = [self.persistentContainer.viewContext executeFetchRequest:request error:&error];
if (!error) {
//遍历对象 找到符合要求的对象 删除
[students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self.persistentContainer.viewContext deleteObject:obj];
}];
}else{
NSLog(@"Error:%@",error);
}
//保存上下文
[self saveContext];
}
修改数据
- (void)updateCoreData{
//获取数据请求对象,指明实体进行修改操作
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
//创建谓词对象,设置过滤条件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age like[cd] %@",@"28"];
request.predicate = predicate;
NSError *error = nil;
NSArray <Student*> *students = [self.persistentContainer.viewContext executeFetchRequest:request error:&error];
if (!error) {
[students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.name = @"王大锤";
}];
}else{
NSLog(@"Error:%@",error);
}
//保存上下文
[self saveContext];
}
查询数据
- (void)checkCoreData{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
NSError *error = nil;
NSArray <Student*> *students = [self.persistentContainer.viewContext executeFetchRequest:request error:&error];
if (!error) {
[students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@",obj.name);
NSLog(@"%d",obj.age);
}];
}else{
NSLog(@"Error:%@",error);
}
}
ps:
在创建实体类时如果遇到 duplicate symbol错误问题,
1.选中你的数据模型文件xcdatamodeld的某个实体
2.然后在XCode右侧栏中切换Data Model Inspector(第三栏)
3.在Codegen一栏中将Class Definition换成Manual/None,这里表示不自动生成实体类型定义 ,然后重新编译。
关于谓词的使用苹果的官方文档在这里
关于使用终端查看sqlite请看这里
好了ios本地数据存储介绍完了。以上内容是本人自己实践总结 如有错误请及时批评指正,谢谢!