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文件

使用终端查看CoreData.sqlite(也可以使用其他sqlite客户端打开)

未命名.png

删除数据

- (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本地数据存储介绍完了。以上内容是本人自己实践总结 如有错误请及时批评指正,谢谢!

core data详细的教程可以去这里看看

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350