ios 数据持久化存储

说到数据存储,我们不得不先了解下苹果的沙盒 、如何获取沙盒路径和沙盒目录下对应的文件:
一、沙盒(sandbox)

  • 每一个App都有一个存储空间。iOS系统为每个应用程序创建自己的目录,每个应用程序只能访问自己的目录,不能相互通信。
    沙盒主要包括下面几个文件:用模拟器运行 NSLog(@"%@",NSHomeDirectory()); 打印路径;
    进入该路径下回看到四个文件 Documents , Libraby , SystemData , tmp四个文件
    在这里插入图片描述

  • 各个文件的用途 :

  • 1、Documents
    获取路径

     NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    

    保存持久化数据,会备份。一般用来存储需要持久化的数据。
    一般我们在项目中,我们会吧一些用户的登录信息进行存储,以及搜索历史记录等等一些关键数据。

    2、Library 下面有两个文件 Caches 和 Preferences
    Caches: iTunes不会同步此文件夹,适合存储体积大,不需要备份的非重要数据。
    获取路径

    NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
    

    Preferences: iTunes同步该应用时会同步此文件夹中的内容,通常保存应用的设置信息。NSUserDefaults存放

    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    

    3、tmp
    iTunes不会同步此文件夹,系统可能在应用没运行时就删除该目录下的文件,所以此目录适合保存应用中的一些临时文件,用完就删除。
    获取路径

    NSString *path = NSTemporaryDirectory();
    

    在这里插入图片描述
    4、SystemData 这个是新加入的文件夹

二、 下面看下数据存储 包含: Preference 、文件存储、归档、数据库 、 CoreData
1、Preference(偏好设置): NSUserDefaults
可以存储 字典 数组 字符等系统自带的数据类型,自定义的对象无法存储,默认存放字 Library/Preferences 下 具体代码实现

    - (void)setDefault {
        //可以存储 字典 数组 字符等系统自带的数据类型,自定义的对象无法存储
        NSUserDefaults * def = [NSUserDefaults standardUserDefaults];
        [def setObject:@"aaaaaa" forKey:@"DEFAULT"];
        [def synchronize];
        NSLog(@"default ---- %@",[def objectForKey:@"DEFAULT"]);
    }
    - (void)delDefault {
        [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"DEFAULT"];
        NSLog(@"清空了default ---- %@",[[NSUserDefaults standardUserDefaults] objectForKey:@"DEFAULT"]);
    }

2 、文件存储
存储系统自带的数据类型,一般实际开发中存储字典、数组,自定义的模型无法进行存储
代码实习

   #define LvPath [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"lvData.plist"]
   #define LPath [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"lData.plist"]
   #define LhPath [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"lhData.plist"] 
- (void)createFile{
    //文件
    NSDictionary *dic = @{@"A":@"123"};
    NSArray * arr = @[@"Q",@"123"];
    NSString * string = @"aaaaaaaa";
    [dic writeToFile:LvPath atomically:YES];
    [arr writeToFile:LPath atomically:YES];
    [string writeToFile:LhPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
    UIButton * fileBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    fileBtn.frame = CGRectMake(10, 180, 80, 80);
    [fileBtn setTitle:@"file" forState:UIControlStateNormal];
    fileBtn.backgroundColor = [UIColor redColor];
    [fileBtn addTarget:self action:@selector(setFile) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:fileBtn];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)setFile {
    NSArray * arr = [NSArray arrayWithContentsOfFile:LPath];
    NSDictionary * dic = [NSDictionary dictionaryWithContentsOfFile:LvPath];
    NSString * string = [NSString stringWithContentsOfFile:LhPath encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"file :arr--%@ dict---%@  string--%@",arr,dic,string);
}

3、归档(又名序列化)
归档是把对象转为字节码,以文件的形式存储到磁盘上,程序运行过程中或者再次重新打开程序的时候,可以通过解归档(返序列化)还原这些对象。
归档的对象是Foundation框架中的对象
归档和解归档其中任意对象都需要归档和解归档整个文件
归档后的文件是加密的,所以归档文件的扩展名可以随意取
在带键的归档中,每个归档都有一个key值,解归档时key值要与归档时key值匹配
代码实习 下面这个方法是卸载app ,再次装值也是还是之前的
LHKeyChain.m 文件

#import "LHKeyChain.h"

@implementation LHKeyChain
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
    return [NSMutableDictionary dictionaryWithObjectsAndKeys:
            (id)kSecClassGenericPassword,(id)kSecClass,
            service, (id)kSecAttrService,
            service, (id)kSecAttrAccount,
            (id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
            nil];
}

+ (void)save:(NSString *)service data:(id)data {
    //Get search dictionary
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    //Delete old item before add new item
    SecItemDelete((CFDictionaryRef)keychainQuery);
    //Add new object to search dictionary(Attention:the data format)
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
    //Add item to keychain with the search dictionary
    SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}

+ (id)load:(NSString *)service {
    id ret = nil;
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    //Configure the search setting
    //Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue
    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
    [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
    CFDataRef keyData = NULL;
    if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
        @try {
            ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
        } @catch (NSException *e) {
            NSLog(@"Unarchive of %@ failed: %@", service, e);
        } @finally {
        }
    }
    if (keyData)
        CFRelease(keyData);
    return ret;
}

+ (void)deleteKeyData:(NSString *)service {
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    SecItemDelete((CFDictionaryRef)keychainQuery);
}

LHKeyChain.h

#import <Foundation/Foundation.h>

@interface LHKeyChain : NSObject
+ (void)save:(NSString *)service data:(id)data;
+ (id)load:(NSString *)service;
+ (void)deleteKeyData:(NSString *)service;
@end

调用 //归档

   - (void)setArchiver {
    NSString *IDFV = [LHKeyChain load:@"IDFV"];
    if ([IDFV isEqualToString:@""] || !IDFV) {
        IDFV = [UIDevice currentDevice].identifierForVendor.UUIDString;
        [LHKeyChain save:@"IDFV" data:IDFV];
    }
    NSLog(@"archiver---%@",[LHKeyChain load:@"IDFV"]);
    [LHKeyChain deleteKeyData:@"IDFV"];
    NSLog(@"archiver---%@",[LHKeyChain load:@"IDFV"]);
}

4 、数据库

SQLite数据库的几个特点:
1、基于C语言开发的轻型数据库
2、在iOS中需要使用C语言语法进行数据库操作、访问(无法使用ObjC直接访问,因为libqlite3框架基于C语言编写)
3、SQLite中采用的是动态数据类型,即使创建时定义了一种类型,在实际操作时也可以存储其他类型,但是推荐建库时使用合适的类型
4、建立连接后通常不需要关闭连接

SQLite 使用:

1、在iOS中使用SQLite3,首先要添加库文件libsqlite3.dylib和导入主头文件。如图
在这里插入图片描述
2、导入头文件,可以使用库中的函数

#import <sqlite3.h>

3、正好现在趁着数据持久化,讲下数据库增、删、改、查;有一个很好的第三方库 FMDB,这里不具体讲,详细使用可以点击了解详情 demo

1)创建并打开数据库
使用 sqlite3_open(<#const char *filename#>, <#sqlite3 **ppDb#>)函数的一些说明:把一个文件名称传递给他,它会自动检测这个文件是否存在,如果不存在的话,会自动创建相应的文件;

  • 参数说明它的第一个参数为文件的名称(需转换为C语言的),第二个参数是数据库的实例,sqlite3 *db;
  • 说明:sqlite3是一种类型,db是数据库的句柄,就是数据库的象征,如果要进行增删改查,就得操作db这个实例。
  • 返回值:它的返回值为int型的,根据函数的返回值可以知道,打开数据库文件是成功还是失败,如果返回值是SQLITE_OK则说明成功,否则为失败。

代码实现:

//数据库
- (void)createSqlite {
    //db是数据库的缩写
    sqlite3 * db;
    //这里面定义一个数据库存放路径,并获取到
    NSString * path = [NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES) lastObject];
    NSString * dbPath = [path stringByAppendingString:@"demo.sqlite"];
    //因为sqlite是c语言 所以下面需要将OC字符串转换为c语言的字符串
    const char * cdbPath = dbPath.UTF8String;
    //下面打开数据库 (如果数据库存在的话,会直接打开,反之数据库不存在的话,会自动创建数据库文文件)
    int result = sqlite3_open(cdbPath, &db);
    if (result == SQLITE_OK) {
        NSLog(@"成功打开数据库");
    } else {
        NSLog(@"数据库打开失败");
    }
    //数据库 现在有了,接下来创建表
}

运行 查看结果 :
在这里插入图片描述
沙盒中已经存在 如图
在这里插入图片描述
数据库有了 ,下面就该创建表了
2)语句 sqlite3_exec(<#sqlite3 *#>, <#const char *sql#>, <#int (*callback)(void *, int, char **, char **)#>, <#void *#>, <#char **errmsg#>)

  • 参数:第一个参数为数据库的句柄(db),第二个参数为sql语句,第三个参数为回调参数,是一个指向函数的指针,如果把callback前面的*改成^则就是一个block代码段,第四个参数可以写NULL,第五个参数为错误信息,用以代码调试。

代码实现

 //db是数据库的缩写
 sqlite3 * db;
 //这里面定义一个数据库存放路径,并获取到
 NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
 NSString * dbPath = [document stringByAppendingPathComponent:@"demo.sqlite"];
 //因为sqlite是c语言 所以下面需要将OC字符串转换为c语言的字符串
 const char * cdbPath = dbPath.UTF8String;
 //下面打开数据库 (如果数据库存在的话,会直接打开,反之数据库不存在的话,会自动创建数据库文文件)
 int result = sqlite3_open(cdbPath, &db);
 if (result == SQLITE_OK) {
      NSLog(@"成功打开数据库");
      //数据库 现在有了,接下来创建表
      const char * sql = "create table if not exists t_demo (id integer PRIMARY KEY AUTOINCREMENT,title text not null, content text not null)";
      char * errorMsg = NULL;
      result = sqlite3_exec(db, sql, NULL, NULL, &errorMsg);
      if (result == SQLITE_OK) {
            NSLog(@"表创建成功");
      } else {
            NSLog(@"表创建失败 %s",errorMsg);
      }
  } else {
      NSLog(@"数据库打开失败");
  }

运行结果
在这里插入图片描述
打开数据库 查看下成果:
在这里插入图片描述

现在表也有了 ,该对表进行操作了
3)插入数据
代码实现

  //1.拼接SQL语句
        NSString * title = [NSString stringWithFormat:@"title"];
        NSString * contet = [NSString stringWithFormat:@"content"];
        NSString *sql=[NSString stringWithFormat:@"INSERT INTO t_demo (title,content) VALUES ('%@','%@');",title,contet];
        //2.执行SQL语句
        char *errmsg=NULL;
        sqlite3_exec(db, sql.UTF8String, NULL, NULL, &errmsg);
        if (errmsg) {//如果有错误信息
            NSLog(@"插入数据失败--%s",errmsg);
        }else
        {
            NSLog(@"插入数据成功----%@",title);
        }

运行结果
在这里插入图片描述
查看数据库 如图
在这里插入图片描述
4) 下面看下修改数据

代码实现

//1.拼接SQL语句
    NSString * title = [NSString stringWithFormat:@"title--12"];
    NSString * contet = [NSString stringWithFormat:@"content--13"];
    NSString *sql=[NSString stringWithFormat:@"UPDATE t_demo set title =  '%@',content = '%@' where id = 2;",title,contet];
    //2.执行SQL语句
    char *errmsg=NULL;
    sqlite3_exec(db, sql.UTF8String, NULL, NULL, &errmsg);
    if (errmsg) {//如果有错误信息
        NSLog(@"更新数据失败--%s",errmsg);
    }else
    {
        NSLog(@"更新数据成功----%@",title);
    }

执行结果
在这里插入图片描述
在这里插入图片描述
5) 查询数据
语句:sqlite3_prepare_v2(<#sqlite3 *db#>, <#const char *zSql#>, <#int nByte#>, <#sqlite3_stmt **ppStmt#>, <#const char **pzTail#>)

  • 参数:第一个参数为数据库的句柄,第二个参数为sql语句,第三个参数为sql的长度(如果设置为-1,则代表系统会自动计算sql语句的长度),第四个参数用来取数据,第五个参数为尾部一般用不上可直接写NULL。

代码实现

 const char *sql="SELECT id,title,content FROM t_demo;";
    sqlite3_stmt *stmt=NULL;
    //进行查询前的准备工作
    if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL)==SQLITE_OK) {//SQL语句没有问题
        NSLog(@"查询语句没有问题");
        //每调用一次sqlite3_step函数,stmt就会指向下一条记录
        while (sqlite3_step(stmt)==SQLITE_ROW) {//找到一条记录
            //取出数据
            //(1)取出第0列字段的值(int类型的值)
            int ID = sqlite3_column_int(stmt, 0);
            //(2)取出第1列字段的值(text类型的值)
            const unsigned char * title = sqlite3_column_text(stmt, 1);
            //(3)取出第2列字段的值(int类型的值)
            const unsigned char * content = sqlite3_column_text(stmt, 2);
            //            NSLog(@"%d %s %d",ID,name,age);
            printf("%d %s %s\n",ID,title,content);
        }
    }else
    {
        NSLog(@"查询语句有问题");
    }

执行结果
在这里插入图片描述
6) 删除语句
代码实现

//1.拼接SQL语句
    NSString *sql=[NSString stringWithFormat:@"DELETE from  t_demo  where id = 2;"];
    //2.执行SQL语句
    char *errmsg=NULL;
    sqlite3_exec(db, sql.UTF8String, NULL, NULL, &errmsg);
    if (errmsg) {//如果有错误信息
        NSLog(@"删除数据失败--%s",errmsg);
    }else
    {
        NSLog(@"删除数据成功");
    }

执行结果 ,查看删除成功了
在这里插入图片描述
在这里插入图片描述

5、CoreData
Core Data是iOS5之后才出现的一个框架,它提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite数据库文件中,也可以使用其他方式,比如:数据库文件,XML,二进制文件,内存等。CoreData 提供了 对象-关系映射(ORM) 功能,能够将保存在数据库中的数据还原成OC对象。在此数据操作期间,我们不需要编写任何SQL语句;
本文 只讲简单的使用 下篇文字将学习CoreData具体的知识
代码实现 :
LHModel 类:
LHModel.h

#import <CoreData/CoreData.h>
@interface LHModel : NSManagedObject
@property (nonatomic, copy) NSString * title;
@property (nonatomic, copy) NSString * content;
@end

LJModel.m

#import "LHModel.h"
@implementation LHModel
@synthesize title;
@synthesize content;
@end

CoreDataViewController类 这个是我自己定义的类
CoreDataViewController.m

#import "CoreDataViewController.h"
#import <CoreData/CoreData.h>
#import "LHModel.h"
@interface CoreDataViewController ()
/**
 * 上下文  容器
 * 存放的是 所有从数据库中取出的转换成OC对象
 */
@property (strong, nonatomic) NSManagedObjectContext * managedObjectContext;

/* 读取解析 .momd文件中的内容 */
@property (strong, nonatomic) NSManagedObjectModel * managedObjectModel;

/* 连接的类,处理数据库数据和OC数据底层的相互转换 */
@property (strong, nonatomic) NSPersistentStoreCoordinator * persistentStoreCoordinator;
@end

@implementation CoreDataViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    NSLog(@"%@",self.managedObjectContext);
    
    
    //插入一条数据 (往LHModel表中插入一条数据)
    //NSEntityDescription 实体类
    //EntityForName 实体名称(表名)
    LHModel * model = [NSEntityDescription insertNewObjectForEntityForName:@"LHModel1" inManagedObjectContext:self.managedObjectContext];
    //赋值
    model.title = @"缓存1";
    model.content = @"缓存1内容";
    //同步操作  把context中的数据同步到数据库中
    [self saveContext];
    
    
    // 查询数据
    // 创建查询请求
    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"LHModel1"];
    // Context 执行请求(执行查询操作) 数组中存放的是oc类的对象(People类的对象)
    NSArray * array = [self.managedObjectContext executeFetchRequest:request error:nil];
    for (LHModel *lhModel in array)
    {
        NSLog(@"%@",lhModel.title);
    }
    
    
    //查询特定条件数据
    NSFetchRequest * request1 = [NSFetchRequest fetchRequestWithEntityName:@"LHModel1"];
    //使用谓词指定查询的判定条件
    NSString * title = @"缓存1";
//    NSString *predStr = [NSString stringWithFormat:@"%@ AND (%@ CONTAINS \"%@\")", kPredicateStr_MovieItem_MoviesInCatalog, titleForSearch, title];
    
    NSPredicate * predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:@"SELF.title == '%@'",title]];
    //关联判定条件
    [request1 setPredicate:predicate];
    //执行查询操作
    NSArray * array2 = [self.managedObjectContext executeFetchRequest:request1 error:nil];
    for (LHModel * lhModel in array2)
    {
        NSLog(@"%@",lhModel.title);
    }
    
    //更改数据
    //获取出要修改的数据
    LHModel * lhModel = [array lastObject];
    //修改属性
    lhModel.title = @"缓存2";
    lhModel.content  = @"缓存2内容";
    //同步数据
    [self saveContext];
    
    
    //删除数据
    LHModel * lhModel1 = [array lastObject];
    [self.managedObjectContext deleteObject:lhModel1];
    //同步数据
    [self saveContext];
}

//managedObjectModel 属性的getter方法
- (NSManagedObjectModel *)managedObjectModel
{
    
    if (_managedObjectModel != nil) return _managedObjectModel;
    //.xcdatamodeld文件 编译之后变成.momd文件  (.mom文件)
    NSURL * modelURL = [[NSBundle mainBundle] URLForResource:@"MemoryData" withExtension:@"momd"];
    
    //把文件的内容读取到managedObjectModel中
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

//Coordinator 调度者负责数据库的操作 创建数据库 打开数据 增删改查数据
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) return _persistentStoreCoordinator;
    // 设置数据库存放的路径
    NSURL * storeURL = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:@"MemoryData.sqlite"];

    //根据model创建了persistentStoreCoordinator
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    
    
    
    NSError * error = nil;
    
    //如果没有得到数据库
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
    {
        NSLog(@"错误信息: %@, %@", error, [error userInfo]);
    }
    
    return _persistentStoreCoordinator;
}

//容器类 存放OC的对象
-(NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil)  return _managedObjectContext;
    
    NSPersistentStoreCoordinator * coordinator = [self persistentStoreCoordinator];
    if (!coordinator)
    {
        return nil;
    }
    
   /* 创建context对象 NSManagedObjectContext 实例提供一个线程。我们需要将这个 init 方法替换成 -initWithConcurrency: 方法。这个方法配置了 NSManagedObjectContext 实例化所在的线程。
    这就意味着我们要确定在哪个线程上实例化我们的 NSManagedObjectContext ,主线程,还是另外创建一个后台线程。我们可以选择的参数有:

    NSPrivateQueueConcurrencyType
    NSMainQueueConcurrencyType
    在这里,我把它配置成在主线程上进行实例化(一般选择主线程就可以)*/
    
  
    _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    
    //让context和coordinator关联   context可以对数据进行增删改查功能
    [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    
    return _managedObjectContext;
}

#pragma mark - Core Data Saving support

-(void)saveContext
{
    NSManagedObjectContext * managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil)
    {
        NSError * error = nil;
        // hasChanges 判断数据是否更改
        // sava 同步数据库中的数据
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
        {
            NSLog(@"错误信息: %@, %@", error, [error userInfo]);
        }
    }
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

运行结果 :

在这里插入图片描述
在这里插入图片描述

希望大家一起学习: 附有demo
csdn demo下载地址
https://download.csdn.net/download/u013983033/10745909

github demo 下载地址
https://github.com/lvhome/DataMemory

猜你喜欢

转载自blog.csdn.net/u013983033/article/details/83276831