数据持久化:概述

字数 1129阅读 70

分类

简单来说,IOS的数据持久化有四种方式:

  1. 属性列表
  2. 对象归档
  3. iOS的嵌入式关系数据库SQLite3
  4. 苹果公司提供的持久化工具Core Data

下面以带有四个TextLabel的界面为例,这个界面的功能是用户在TextLabel中输入值,系统将输入的内容的保存起来,再次启动程序的时候,TextLabel默认显示的仍然是用户之前输入的值。分别用IOS数据持久化的四种方式来实现上述功能。
完成这个例子需要三步——

  1. 保存label中的值就是写文件
  2. label显示默认值就是读文件;
  3. 选择相应的持久化方式,依靠这种方式进行读写。

属性列表


//1.读
-(void)viewDidLoad{

    //通过NSFileManager来检查数据是否存在
    NSString *filePath = [self dataFilePath]
    if( [[NSFileManager defaultManager] fileExistsAtPath:filePath] ){
      //文件存在 do something
      //这就把文件读出来了,读的方法是,声明一个与文件内容相同类型的变量,取出即可
      NSArray *array = [[NSArray alloc]initWithContentOfFile: filePath];

      //赋值到需要的地方
      UILabel *lable = self.textLabel;
      label.text = array[0]; 

     /*
     NOTE : 
           self.textLabel为该页面的UI变量,最后的目的是要把咋文件读出来的值显示在这里
           label先把指向self.textLabel的指针取到,然后由label来取值

           ***为何不直接      self.textLabel.text = array[0];       呢??

     */
     }

//下面是与归档编解码无关的部分,监听self,如果发生应用终止运行则及时保存数据
    UIApplication *app = [UIApplication sharedApplication];
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(applicationWillResignActive:)
     name:UIApplicationWillResignActiveNotification
     object:app];


    }

2.写文件
-(void)applicationWillResignActive:(NSNotification *)notification{
    NSString *filePath = [self dataFilePath];
    
    NSArray *array = @[@"a",@"b",@"c",@"d"];//=[self.lineFields valueFoeKey:@"text"];

    [array writeToFile:filePath atomically:YES];

}
//这个函数名字和形式比较特殊,只把它当做一个滴啊有特殊功能的函数就好
//这个方法应是作为observer的一个selector,作用是:使应用在进入后台或者终止运行时保存数据,只要应用不在是前正在与用户交互的应用就会发布通知,例如不小心按了home键或者突然有电话打进来


//获取Document文件路径
-(NSString)dataFilePath{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
    
    NSString *documentDirectory = paths[0];

    return [documentDirectory stringByAppendingPathComponent:@"data.plist"];//指定文件
    
    //读取tmp目录
    //NSString *tmpPath = NSTemporaryDirectory();
    //NSString *tempFile = [tempPath stringByAppendingPathComponent:@"data.plist"];//体现出用的是“属性列表”的方式
}

对象归档


#import <UIKit/UIKit.h>

//1.读就是解码的过程
-(void)viewDidLoad{

    NSString *filePath = [self dataFilePath]
    if( [[NSFileManager defaultManager] fileExistsAtPath:filePath] ){

    //解码通道
    NSData *data = [[NSMutableData alloc] initWithContentOfFile:filePath];
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData];
    //根据编码时设定的key找到要解码的对象
    MyObject *myObject = [unarchiver decodeObjectForKey:myObjectKey];
    //
    [unarchiver finishDecoding];
   //READ
    UILabel *label.text = myObject[ObjectAtIndex:somenumber];

    }

    //下面是与归档编解码无关的部分,监听self,如果发生应用终止运行则及时保存数据
    UIApplication *app = [UIApplication sharedApplication];
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(applicationWillResignActive:)
     name:UIApplicationWillResignActiveNotification
     object:app];
    
}


2.写就是编码的过程
-(void)applicationWillResignActive:(NSNotification *)notification{
    //文件写入的地点,要有
    NSString *filePath = [self dataFilePath];

    //要保存的对象
    MyObject *myObject = [[MyObject alloc]init];
    myObject addObject:UILabel.text;//进行一些赋值操作,这里label没有正常声明
    //打开“通道”(不知道这样形容合适不),必须要做的两个步骤
    NSMutableData *data = [[NSMutableData alloc]init];
    NSKeyedArchiver *archiver = [[NSKeyArchiver alloc]initFoeWritingWithMutableData:data];
    //建立键值关系,解码时通过该key去找应该解码的对象
    [archiver encodeObject:myObject forKey:myObjectKey];
    //
    [archiver finishEncoding];
    //WRITE
    [data writeToFile :filePath atomically:YES];
    
}

//获取文件路径
-(NSString)dataFilePath{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
    
    NSString *documentDirectory = paths[0];

    return [documentDirectory stringByAppendingPathComponent:@"data.plist"];//指定文件
    return [documentDirectory stringByAppendingPathComponent:@"data.archive"]//体现出用的“归档”的方式
    
}

SQLite3


/*
NOTE:
1.SQL语句通过*char 传递而非NSString    两者的相互转换方式为:
 const char *stringPath = [pathString UTF8String];

2.C语言小知识:如果两个内联字符串之间只有空白(或者换行符),而没有自他字符,则两个字符串将会被连接为一个字符串
*/
//基础使用
//1.声明和创建
 sqlite3 *database;
 int result = sqlite3_open("/path/to/database/file",&database);//result = SQLITE_OK if open database successfully

//2.执行语句
 //2-1用于updata insert delete等不反回任何数据的命令 ——  sqlite3_exce
 char *errorMsg;
 const char *createSQL = "CREATE TABLE IF NOT EXISTS PEOPLE" "(ID INTEGER PROMARY KEY AUTOINCREMENT ,FIELD_DATA TEXT)";
 int reslut = sqlite3_exec(database,createSQL,NULL,NULL,&errorMsg);//errorMsg将对错误信息进行描述

 //2-2用于query检索的命令 —— sqlite3_prepare_v2
 NSString *query = @"SELECT ID , FIELD_DATA FROM FIELDS ORDER BY ROW";
 sqlite3_stmt *statement;
 int result = sqlite3_prepare_v2(database,[query UTF8String],-1,$statement,nil);

//3.取出数据
 




//4.使用绑定变量出入数据
   
  char *sql = "insert into foo values (?,?); ";
  sqlite3_stmt *stmt;
  if(sqlite3_prepare_v2(database, sql,-1,&stmt,nil) == SQLITE_OK){
      sqlite3_bind_int(stmt, 1, 235);
      sqlite3_bind_text(stmt,2,"Text",-1,NULL);
      //("prepare语句的返回值" , "替代问号的索引" , "问号的替代内容" , "上一个参数的传入长度:-1代表字符串长度","可选用的内存清理方式")
  }
  if(sqlite3_step(stmt)!= SQLITE_DONE){
    NSLog(@"Real Error");
  }
  sqlite3_finalize(stmt);
程序中的读、写法
#import <UIKit/UIKit.h>

1.读就是query检所操作
-(void)viewDidLoad{
    


}

2.写就是更新、插入、删除操作






//获取文件路径
-(NSString)dataFilePath{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
    
    NSString *documentDirectory = paths[0];

    return [documentDirectory stringByAppendingPathComponent:@"data.plist"];//指定文件
    return [documentDirectory stringByAppendingPathComponent:@"data.archive"]//体现出用的“归档”的方式
    return [documentDirectory stringByAppendingPathComponent:@"data.sqlite"]//体现出用SQLite3的方式 
}

CoreData


概念

Core Data是水果推荐开发者使用的一种数据化持久方法,为了能够更加准确地理解,也是翻了不少前辈贡献的资料,着实收货很大

CoreData中的核心对象:


iamge

注:黑色表示类名,红色表示类里面的一个属性
1, Managed Object Model
Managed Object Model 是描述应用程序的数据模型,这个模型包含实体(Entity),特性(Property),读取请求(Fetch Request)等。(下文都使用英文术语。)

2,Managed Object Context
Managed Object Context 参与对数据对象(Managed Object)进行各种操作的全过程,并监测数据对象的变化,以提供对 undo/redo 的支持及更新绑定到数据的 UI。

3,Persistent Store Coordinator
Persistent Store Coordinator 相当于数据文件管理器,处理底层的对数据文件的读取与写入。一般我们无需与它打交道。在context的操作下,负责在数据库中生成取出managed object,或者将managed Object存到数据库

4,Managed Object
Managed Object 数据对象,与 Managed Object Context 相关联。

关于Model
模型有点像数据库的表结构,里面包含 Entry, 实体又包含三种 Property:Attribute(属性),RelationShip(关系), Fetched Property(读取属性)。Model class 的名字多以 "Description" 结尾。我们可以看出:模型就是描述数据类型以及其关系的。

主要的 Model class 有:

First Header Second Header Third Header
Managed Object Model NSManagedObjectModel 数据模型
Entity NSEntityDescription 抽象数据类型,相当于数据库中的表
Property NSPropertyDescription Entity 特性,相当于数据库表中的一列
Attribute NSAttributeDescription 基本数值型属性(如Int16, BOOL, Date等类型的属性)
Relationship NSRelationshipDescription 属性之间的关系
Fetched Property NSFetchedPropertyDescription 查询属性(相当于数据库中的查询语句)

和数据库的对应关系

Manged Object <------> Database

Entity <-------> Table

Property <-------> 列 : 包含 Attribute、Relationship、Fetched Property

开发步骤总结:

  1. 初始化NSManagedObjectModel对象,加载模型文件,读取app中的所有实体信息
  2. 初始化NSPersistentStoreCoordinator对象,添加持久化库(这里采取SQLite数据库)
  3. 初始化NSManagedObjectContext对象,拿到这个上下文对象操作实体,进行CRUD操作

这几个对象用通俗的话这样理解:(并且按照下面的顺序理解)

  1. NSManagedObjectModel 确定和哪个应用连接,就是说,这个Core Data是针对哪个app设计的
- (NSManagedObjectModel *)managedObjectModel  
{  
    if (_managedObjectModel != nil) {  
        return _managedObjectModel;  
    }  
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"NewsModel" withExtension:@"momd"];  
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];  
    return _managedObjectModel;  
}  

2 . NSPersistentStoreCoordinator 字面意就是持久化存储器,所以,这个对象作用就是定位数据库,就是说,我这个应用用的是哪个数据库,But~~还要指明是哪个应用对吧,哪个应用?这在上面的self.managedObjectModel已经确定了,所以把managedObjectModel引入就可以了。

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator  
{  
    if (_persistentStoreCoordinator != nil) {  
        return _persistentStoreCoordinator;  
    }  
      
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"NewsModel.sqlite"];  
      
    NSError *error = nil;  
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];  
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {  
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);  
        abort();  
    }  
      
    return _persistentStoreCoordinator;  
}  

3 . NSManagedObjectContext 程序中负责操作数据的接口提供者,就是说,用这个对象的实例去CRUD,这之前要指明该对象的实例用的是哪个持久化存储器persistentStore

    
- (NSManagedObjectContext *)managedObjectContext  
{  
    if (_managedObjectContext != nil) {  
        return _managedObjectContext;  
    }  
      
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];  
    if (coordinator != nil) {  
        _managedObjectContext = [[NSManagedObjectContext alloc] init];  
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];  
    }  
    return _managedObjectContext;  
}  
增删查改
//插入数据  
- (void)insertCoreData:(NSMutableArray*)dataArray  
{  
    NSManagedObjectContext *context = [self managedObjectContext];  //都要声明这个负责上下文连接的实例
    for (News *info in dataArray) {  
        News *newsInfo = [NSEntityDescription insertNewObjectForEntityForName:TableName inManagedObjectContext:context];  
        newsInfo.newsid = info.newsid;  
        newsInfo.title = info.title;  
        newsInfo.imgurl = info.imgurl;  
        newsInfo.descr = info.descr;  
        newsInfo.islook = info.islook;  
          
        NSError *error;  
        if(![context save:&error])  
        {  
            NSLog(@"不能保存:%@",[error localizedDescription]);  
        }  
    }  
}  
//查询  
- (NSMutableArray*)selectData:(int)pageSize andOffset:(int)currentPage  
{  
    NSManagedObjectContext *context = [self managedObjectContext];  

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];  
      
          NSEntityDescription *entity = [NSEntityDescription entityForName:TableName inManagedObjectContext:context];
      
    //setFetchLimit  
    
    //setFetchOffset  
   
    //查找指定日期之后创建的
          NSPredicate * predicate;  
    predicate = [NSPredicate predicateWithFormat:@"creationDate > %@", date];  
    
    //将结果按照title排序                   
    NSSortDescriptor * sort = [[NSortDescriptor alloc] initWithKey:@"title"]; 
    NSArray * sortDescriptors = [NSArray arrayWithObject: sort]; 

    [fetchRequest setEntity:entity];  
    [fetchRequest setFetchLimit:pageSize]; // 限定查询结果的数量 
    [fetchRequest setFetchOffset:currentPage];  // 查询的偏移量  
    [fetchRequest setPredicted:predicate];  // 设置查询条件  
    [fetchRequest setSortDescriptors:sort];  // 设置查询结果的排序方法 
 
    NSError *error;  
    NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];  
    NSMutableArray *resultArray = [NSMutableArray array];  
    for (News *info in fetchedObjects) {  
        NSLog(@"id:%@", info.newsid);  
        NSLog(@"title:%@", info.title);  
        [resultArray addObject:info];  
    }  
    return resultArray;  
}  
  
//删除  
-(void)deleteData  
{  
    NSManagedObjectContext *context = [self managedObjectContext];  
    NSEntityDescription *entity = [NSEntityDescription entityForName:TableName inManagedObjectContext:context];  
  
    NSFetchRequest *request = [[NSFetchRequest alloc] init];  
    [request setIncludesPropertyValues:NO];  
    [request setEntity:entity];  
    NSError *error = nil;  
    NSArray *datas = [context executeFetchRequest:request error:&error];  
    if (!error && datas && [datas count])  
    {  
        for (NSManagedObject *obj in datas)  
        {  
            [context deleteObject:obj];  
        }  
        if (![context save:&error])  
        {  
            NSLog(@"error:%@",error);    
        }    
    }  
}  
//更新  
- (void)updateData:(NSString*)newsId  withIsLook:(NSString*)islook  
{  
    NSManagedObjectContext *context = [self managedObjectContext];  
  
    NSPredicate *predicate = [NSPredicate  
                              predicateWithFormat:@"newsid like[cd] %@",newsId];  
      
    //首先你需要建立一个request  
    NSFetchRequest * request = [[NSFetchRequest alloc] init];  
    [request setEntity:[NSEntityDescription entityForName:TableName inManagedObjectContext:context]];  
    [request setPredicate:predicate];//这里相当于sqlite中的查询条件,具体格式参考苹果文档  
      
    //https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Predicates/Articles/pCreating.html  
    NSError *error = nil;  
    NSArray *result = [context executeFetchRequest:request error:&error];//这里获取到的是一个数组,你需要取出你要更新的那个obj  
    for (News *info in result) {  
        info.islook = islook;  
    }  
      
    //保存  
    if ([context save:&error]) {  
        //更新成功  
        NSLog(@"更新成功");  
    }  

最后


关于几个持久化方法,有几个点需要注意一下:

  • 使用 属性列表 和 归档 还需要考虑将数据存在一个文件还是多个文件中,即单文件持久化(多用)、多文件持久化;
  • 在写文件是用到[myArray writeToFile:filePath atomically:YES] 第二个参数是叫数据写入辅助文件,写完后再将辅助文件复制到第一个参数指明的位置,保障写安全性;
  • 采用属性列表法无法序列化自定义对象,且无法序列化NSURL,UIImage,UIColor等类;
  • 归档能够大多数类进行编解码来实现数据持久化,只要类中的每个属性都是标量或者遵循NSCoding某各类的实例,都可以归档;

done!

推荐阅读更多精彩内容