码迷,mamicode.com
首页 > 移动开发 > 详细

iOS数据持久化

时间:2015-11-18 01:59:59      阅读:258      评论:0      收藏:0      [点我收藏+]

标签:

      在iOS程序开发中,所有的应用程序都有自己独有的沙盒(Sandbox),Sandbox也就是应用的文件系统目录,每一个应用都是只能访问自己Sandbox中的文件,这其实也是安全性的一个体现,其它的应用都不能访问非属于自己的Sandbox.

      在默认的情况下,MAC中的隐藏文件夹默认不显示,不过我们可以通过终端命令让其显示。

      显示Mac隐藏文件的命令:defaults write com.apple.finder AppleShowAllFiles YES

      隐藏Mac隐藏文件的命令:defaults write com.apple.finder AppleShowAllFiles NO

      输完单击Enter键,退出终端,重新启动Finder就可以了

      重启Finder:鼠标单击窗口左上角的苹果标志-->强制退出-->Finder-->重新启动

     Sandbox结构如下:

     主要为4个文件夹    Documents(主要保存应用运行时生成的一些数据,比如游戏存档),

     tmp(主要保存一些临时的数据,会在应用使用完毕的时候删除),

     Library/Caches(主要保存运行时候生成的需要持久化的数据,一般主要为存储体积比较大但是却不是非常重要的数据),

     Library/Preference(保存所有的偏好设置,iOS中的settings会在这个目录中找到设置的相关信息)

     对于iTunes备份来说,tmp文件夹和Library/Caches文件夹下的内容由于一般体积比较大,数据也会被系统在应用运行完毕后删除,所以不会被iTunes备份;而Documents文件夹和Library/Preference文件夹下的内容由于体积比较小,同时也常常用来保存一些重要的存档数据与用户数据,同时iTunes也会备份这两个文件夹下的内容。

     当清楚了sandbox的基本结构后,在我们后续iOS应用开发的过程中也同时需要注意数据安全的问题,对于敏感数据存档不要明文存档。

     怎样通过代码来获取应用Sandbox的目录呢?

     可以通过NSSearchPathForDirectoriesInDomains函数来获取

1 NSArray *array = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,NO);// NSUserDomainMask 代表从用户文件夹下面找
2 NSString *documents = [array objectAtIndex:0];
3 // 在iOS中只会有一个目录跟传入的这个参数相匹配,里面只有一个元素

     可以通过NSUserDefaults类可以存取Library/Preference目录下的设置信息。

     上面介绍了关于iOS Sandbox的一些基本机构,下面说说iOS数据的持久化的几种形式:

     一般我们主要分为下面五种形式来进行数据持久化的操作:

     1.XML属性列表(plist文件)

     2.Preference(偏好设置)

    3.NSKeyedArchiver 归档

    4.SQLite3

    5.Core Data

    归档:主要为数据存取的一个过程,以某种形式进行数据的存储,方便后面可以还原这些对象。

 属性列表

          XML格式的文件,拓展名为plist。当对象是NSString,NSDictionary,NSArray,NSData,NSNumber类型的文件时,可以直接使用WriteToFile: atomically:方法将文件写入到属性列表文件中。

比如我们可以把一个字典对象归档到一个plist属性列表中:

// 1. 创建一个可变字典对象
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
// 2. 将要存入的数据封装成为字典
[ dict setObject:@"张三" forKey:@"name" ];
[ dict setObject:@"" forKey:@"性别"];
[ dict setObject:@"23" forKey:@"age" ];
[ dict setObject:@"2525252525" forKey:@"QQ"];
// 3. 将字典写入到plist文件中
[dict WriteToFile:path atomically:YES];

我们要恢复NSDictionary的时候:

NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSLog(@"name:%@",[dict objectForKey:@"name"]);
NSLog(@"性别:%@",[dict objectForKey:@"性别"]);
NSLog(@"age:%@",[dict objectForKey:@"age"]);
NSLog(@"QQ:%@",[dict objectForKey]);

打印结果为:

               name:张三

               性别:男

               age:23

               QQ:2525252525

       分析属性列表的存储和读取的过程:首先我们创建一个NSMutableDictionary对象,然后将数据封装成为字典,再通过writeToFile: atomically:方法将我们封装好的字典存入plist文件中;当读取的时候我们再通过dictionaryWithContentsOfFile:方法从路径中取出字典,再通过objectForKey:方法将之前存储的数据取出来。

偏好设置(NSUserDefaults)

      在偏好设置中我们可以用来存储用户名,字体大小,密码,是否自动登录等设置。我们可以通过NSUserDefaults来存取偏好设置

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"张三" forKey:@"name"];
[defaults setObject:@"123456" forKey:@"pwd"];
[defaults setFloat:17.0f  forKey:@"text_size"];
[defaults setBool:YES  forKey:@"auto_login"];

    可以通过下面的代码来读取上面写入的相关数据

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *name = [defaults stringForKey:@"name"];
NSString *pwd  =  [defaults stringForKey:@"pwd"];
float textSize = [defaults floatForKey:@"text_size"];
Bool autoLogin = [defaults boolForKey:@"auto_login"];

在通过NSUserDefaults设置数据的时候,会根据时间戳定时的把缓存中的数据写入到本地的磁盘上面,所以可能会出现调用了set方法但是还没有将数据写入到磁盘,应用程序就已经终止了,为了防止这样的情况出现,可以通过调用synchornize方法强制写入数据

[defaults synchornize];

 NSKeydArchiver

      在遵守了NSCoding协议下,如果对象是NSString ,NSDictionary,NSArray,NSData,NSNumber等类型,可以直接使用NSKeyedArchiver进行数据归档和解码。

        在NSCoding的协议中有两个方法:

        encodeWithCoder:   每次归档对象的时候都会调用这个方法,在这个方法里面指定如何归档对象中的每一个实例变量,可以使用encodeObject: forKey: 方法来归档实例变量。

       initWithCoder:   每次从文件中解码都会调用这个方法,主要在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用decodeObject: forKey:方法解码实例变量。

比如:我们要将一个NSArray归档到Documents/array.archive中去

NSArray *array = [NSArray arrayWithObjects:@"a",@"b",@"c",nil];
[NSKeyedArchiver archiverRootObject:array toFile:path];

归档成功,当需要解码的时候

NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

  分析其过程:我们通过archiverRootObject: toFile:进行归档,通过unarchiveObjectWithFile:进行解码。

通过NSKeydArchiver归档一个Student对象

Student.h

@interface
Student:NSObject<NSCoding>
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int id;
@property (nonatomic,assign) int age;
@property (nonatomic,assign) int score;
@property (nonatomic,assign) float height;
@end

  

@implementation Student
- (void)encodeWithCoder:(NSCoder *)encoder
{
      [encoder encodeObject:self.name    forKey:@"name"];
      [encoder encodeObject:self.id         forKey:@"id"];
      [encoder encodeObject:self.age      forKey:@"age"];
      [encoder encodeObject:self.score    forKey:@"score"];
      [encoder encodeObject:self.height   forKey:@"height"];
}
- (id)initWithCoder:(NSCoder *)decoder
{
     self.name = [decoder decodeObjectForKey:@"name"];
     self.id       = [decoder decodeObjectForKey:@"id"];
     self.age    = [decoder decoderObjectForKey:@"age"];
     self.score  = [decoder decoderObjectForKey:@"score"];
     self.height = [decoder decoderObjectForKey:@"height"];
}
@end

归档:

Student *stu = [Student alloc] init];
stu.name = @"张三";
stu.id       = 11;
stu.age     = 23;
stu.score   = 99;
stu.height = 1.70f;
[NSKeyedArchiver archiveRootObject:student  toFile:path];

解档: 

Student *stu = [NSKeyedUnArchiver unarchiveObjectWithFile:path];

当父类也遵守了NSCoding协议的时候,应该在encodeWithCoder:中加上[super encodeWithCode:encode]; 能够确保继承的实例变量也能够被归档

在initWithCoder:中加上self = [super initWithCoder:decoder];能够确保继承的实例变量也能够被恢复。

当需要将多个对象写入到同一个文件中,就需要用到NSData来进行归档。NSData可以为数据提供临时的存储空间,方便后续写入文件。可以通过[NSMutableData data]创建一个可变的数据空间。

先将多个对象归档到NSData中,然后再将NSData写入到文件中;恢复过程也是先将数据解码到NSData,然后再从NSData中解码。

比如:将3个student对象归档到同一文件中

// 1. 新建一块可变的数据存储空间
NSMutableData *data = [NSMutableData data];
// 2.将数据存储空间与一个NSKeyedArchiver对象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
// 3. 开始归档对象,归档的数据会存储到NSMutableData中
[archiver encodeObject:student1 forKey:@"student1"];
[archiver encodeObject:student2 forKey:@"student2"];
[archiver encodeObject:student3 forKey:@"student3"];
// 4. 归档完毕调用
[archiver finishEncoding];
// 5. 将存储的数据写入到文件
[data writeToFile:path atomically:YES];

从文件中恢复:

// 1. 从文件中读取数据
NSData *data = [NSData dataWithContentsOfFile:path];
// 2. 根据数据,解析成为NSKeyedUnarchiver对象
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
Student *student1 = [unarchiver decodeObjectForKey:@"student1"];
Student *student2 = [unarchiver decodeObjectForKey:@"student2"];
Student (student3 =  [unarchiver decodeObjectForKey:@"student3"];
// 3. 恢复完毕
[unarchiver finishDecoding];

我们也可以通过归档实现深复制(deep copy):

// 1. 临时存储student1的数据
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:student1];
// 2. 解析data ,生成一个新的student对象
Student *student2 = [NSKeyedUnArchiver unarchiveObjectWithData:data];
// 3. 分别打印内存地址
NSLog(@"student1:0X%p",student1);
NSLog(@"student2:0X%p",student2);

结果显示内存地址不一样,说明成功通过归档实现了深复制。

 SQLite3

      SQLite3是一款开源的嵌入式关系型数据库,易于使用,占用内存小,可移植性好。它本身是没有类型的,但为了可读性,我们在书写的时候还是应该将字段类型加上。

create table t_student(name text,age integer); 

数据库(Database)按照数据结构来组织存储和管理数据的仓库,主要分为关系型数据库和对象型数据库。

数据库的存储结构以表(table)为单位,数据库存储数据的步骤如下几步:

1. 新建一张表(table)

2. 添加多个字段(column,列, 属性)

3. 添加多行纪录(row,每行存储多个字段对应的值)

Mac系统下也有Navicat数据库管理软件,支持主流数据库(对于图形界面的使用操作,这里不再说明)

SQL 语句

     如果要在程序运行的过程中来操作数据库中的数据,我们得先学会基本的SQL语句。

SQL(structured query language)是一门结构化查询语言,它是对关系型数据库进行定义和操作的语言。

在程序运行过程中,如果要想CRUD数据库中的数据,那么就必须使用SQL语句。

SQL语句的特点:

         不区分大小写,以分号结束;常用的关键字有:select,insert,update,delete,from,create,desc,where,order,by,group,table,alter,view,index等(在数据库中不能以关键字来进行命名表和字段)

SQL语句的种类:

        数据定义语句(DDL:Data Definition Language)

                主要为create和drop操作,创建新表create table 删除表drop table;

        数据操作语句(DML:Data Manipulation Language)

                主要是insert,delete,update操作,插入,删除和更新表中的数据

        数据查询语句(DQL:Data Query Language)

                主要是select操作,在SQL中用得最多的操作;主要用于查询获得表中的数据;

                 DQL常用的关键字:where,order by,group by,having.

创建一张表

       create table 表名(字段名1 字段类型1,字段名2 字段类型2,字段3 字段类型3);

       create table t_student(字段名1 字段类型1,字段名2 字段类型2,字段3 字段类型3);

       create table if not exists t_student(字段名1 字段类型1,字段名2 字段类型2,字段3 字段类型3);

 比如: 创建一张表,表中含有姓名,年龄,身高属性

        create table if not exists t_student(name text,age integer,height real);

字段类型

       SQLite将数据分为以下几种存储类型:integer(整型),text(文本字符串),real(浮点型),blob(二进制数据)

      实际上SQLite是无类型的,为了规范,在编写建表语句的时候加上每个字段的具体类型。

删表

      drop table表名;

       drop table if exists 表名;

     比如:drop table if exists t_student;

插入数据

        insert into 表名( 字段名1,字段名2,字段名3) values(字段1的值,字段2的值,字段3的值);

        insert into t_student(name,age,height) values(‘张三‘,22,170);

        数据库中字符串内容应该用单引号扩住 

更新数据

        update 表名 字段1 = 字段1的值,字段2 = 字段2的值...;

        update t_student name ="张三",age = 22; 

        上面一句会将t_student中所有的name改为张三,age全部改为22

删除数据

          delete from 表名;

          delete from t_student;

          这句会将t_student中的所有记录都删除

条件语句

          当我们想更加准确的更新或者删除某些固定的数据记录的时候,需要在DML语句后面加上条件语句

          where 字段 某个值;

          where 字段 is 某个值;

          where 字段 != 某个值;

          where 字段 is not 某个值;

          where 字段 > 某个值;

          where 字段 = 某个值  and 字段2>某个值;

           where 字段 = 某个值  or 字段2 = 某个值;

DQL语句

        select 字段1,字段2,...from 表名;

         select *from 表名;查询所有字段

  比如:select name,age,from t_student;

         select *from t_student where age = 10;

 起别名 字段和表都可以起别名

计算记录的数量

          select count(字段名)from 表名;

          select count(*)from 表名;

    比如: select count(age)from t_student;

              select count(*)from t_student where score>=60;

排序

       查询出来的数据可以通过order by进行排序,默认升序asc由小到大.

        select *from t_student order by  age desc;

        select *from t_student order by age asc;

      对多个字段进行排序:

       select *from t_student order by age asc,height desc;

limit

      可以精确的控制查询的结果

      select *from t_student limit 4,8;

     跳过前面4条,取8条数据;

     limit主要用来做分页查询,比如每页固定显示5条数据,

     第一页:select *from t_student limit 0,5;

     第二页:select *from t_student limit 5,5;

     第三页:select *from t_student limit 10,5;

     第n页:select *from t_student limit 5*(n-1),5;

简单约束

     not null,unique,default;给字段设定严格的约束,保证数据的规范

主键约束

     主键(Primary Key),用来唯一的标识别某一条记录;主键可以是一个字段也可以是多个字段。

     主键的设计原则:对用户无意义;不要更新主键;不能包含动态变化的数据;由计算机自动生成。

     create table t_student(id integer primary key autoincrement,age integer,name text);

外键约束

     利用外键可以建立表与表之间的联系,通常为一张表的某个字段引用着另外一张表的主键字段;

     create table t_student(id integer primary key autoincrement,name text,age integer,class_id integer,constraint fk_t_student_class_id_t_class_id foreign key(class_id)(id) );references t_class;

     t_student表中有一个叫做fk_t_student_class_id_t_class_id的外键,这个外键的作用是用t_student中的class_id字段引用t_class表的id字段。

表连接查询

     联合多张表才能查到想要的数据

      inner join(显示的是左右表都有完整字段值的记录) ,left outer join(左外连接,保证左表数据的完整性) 

SQLite3常用的5种数据类型:text,integer,float,boolean,blob,在iOS中如果要使用SQLite3,首先应该添加库文件libsqlite3.dylib和导入主头文件。

在OC程序中,SQLite3同样还是按照之前的几个步骤进行操作:

     1. 创建数据库

     2. 创建表

     3. 添加多个字段

     4. 对数据库进行操作

首先,我们先获取数据库沙盒的位置

NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) lastObject ];
NSString *sqlFilePath = [path stringByAppendingPathComponent:@"student.sqlite"];
int result = sqlite3_open(sqliteFilePath.UTF8String,&_db);
// 上面传入的参数告诉系统数据库文件的地址,获取到数据库之后保存到什么地方

if(result ==SQLite_OK){
       NSLog(@"打开数据库成功");
}

sqlite3_open这个方法会根据文件的地址去打开数据库,如果数据库存在就直接打开,如果不存在,那么它就会创建一个新的数据库;

如果返回值result为SQLite_ok表示已经成功打开了数据库,数据库文件的路径需要通过C语言字符。

关闭一个数据库

sqlite3_close(db);

不返回数据的SQL语句

sqlite3_exec() 语句可以做创建表,更新,插入删除等操作,还可以执行开启事务,回滚事务,提交事务,但是由于由于它不会返回查询到的数据,所以很少用它用它来执行查询语句

NSString *sql = @"create table if not exists t_student(name text,age integer ,score real,id integer primary key auto increment);"
result = sqlite3_exec(_db,sql.UTF8String,NULL,NULL,NULL);
// 第1个参数为需要执行sql 语句的数据库,第2个参数为要执行的sql语句,第3个参数为执行成功的回调,第4个参数为回调的参数,第5个为错误信息

比如要插入一个数据

NSString *sql = @"insert into t_student(name,age,height) values(‘张三‘,23,170);";
int result = sqlite3_exec(_db,sql.UTF8String,NULL,NULL,NULL);
if(result == SQLIKE_OK){
     NSLog(@"插入数据成功");
}else{
    NSLog(@"插入数据失败");
}

带占位符的的插入数据

NSString *sql = @"insert into t_student(name,age) values(?,?);";
sqlite3_stmt *stmt;
if(sqlite3_prepare_v2(_db,sql,-1,&stmt,NULL) == SQLITE_OK)
{// 返回SQLITE_OK 说明SQL语句准备成功
   sqlite3_bind_text(stmt,1,张三NULL);
   sqlite3_bind_int(stmt,2,27);    
}
  if(sqlite3_step(stmt) != SQLITE_DONE){
    NSLog(@"插入数据错误");
}
sqlite3_finalize(stmt);

 

查询数据

// 1. 准备查询
sqlite3_stmt *stmt;
// 提取数据的对象
int result = sqlite3_prepare_v2(_db,sql.UTF8String,-1,&stmt,NULL);
// 2. 开始查询
if (result == SQLITE_OK)
{
    NSLog(@"准备成功");
    // 3.调用sqlite3_step方法
    while(sqlite3_step(stmt) == SQLITE_ROW)
     {// 返回SQLITE_ROW代表遍历到一条新的记录
         NSLog(@"成功提取了一条数据");
         const unsigned char * name = sqlite3_column_text(stmt,0);
         double score = sqlite3_column_double(stmt,1);
         int rank = sqlite3_column_int(stmt,2);
         NSLog(@"%s %.f %d",name,score,rank);
     }
}else{
    NSLog(@"准备失败");
}
// sqlite3_step()返回的SQLITE_ROW代表遍历到一条新的记录
// sqlite3_column_*() 用于获取每个字段对应的值,第2个参数是字段的索引,从0开始

在平时的开发中我们也可以通过FMDB框架来更方便的操作数据库,FMDB主要是封装了C语言,面向对象,省去代码量,与CoreData相比更加轻量级和灵活,同时提供了多线程安全的数据库操作方法,可以防止数据混乱。

主要分为三个核心类:FMDatabase:代表单独的数据库,主要用来执行SQL 语句

                            FMResultSet:执行查询后的结果集

                            FMDatabaseQueue:多线程中执行多个查询和更新,多线程安全

首先导入FMDB框架与头文件,libsqlite3.0.dylib

技术分享
#import "ViewController.h"
#import "FMDatabase.h"
@interface ViewController ()
- (IBAction)insert:(UIButton *)sender;
@property (nonatomic,strong) FMDatabase *db;
@end
@implementation ViewController
- (void)ViewDidLoad{
    [super viewDidLoad];
    // 数据库沙盒路径
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) lastObject];
   NSString *sqliteFilePath = [path stringByAppendingPathComponent:@"student.sqlite"];
// 加载数据库
 self.db = [FMDatabase databaseWithPath:sqliteFilePath];
// 打开数据库
if([self.db open]){
    NSLog(@"打开数据库成功");
    // 在FMDB框架中出了查询以外其他的操作都叫update
    NSString *sql = @"create table if not exists t_student(name text,score real,ranked integer,id integer primary key autoincrement);";
    if([self.db executeUpdate:sql])
    {
      NSLog(@" 创建表成功");
    }else{
        NSLog(@"创建表失败");
    }
}else{
    NSLog(@"打开数据库失败");
   }
}
// 执行sql语句
- (IBAction)insert:(UIButton *)sender{
    // 在FMDB中支持占位符号,可以使用?暂时替代需要设置的值,在后面拼接,当使用占位符,后面拼接的数据必须是对象类型
    if([self.db executeUpdate:@"insert into t_student(name,score,rank)values(?,?,?);",@"张三",@(80.0),@(1)])
    {
     NSLog(@"插入表成功");
    }else{
    NSLog(@"插入表失败");
    }
}
- (IBAction)select:(UIButton *)sender
{
    NSString*sql = @"select name,age,score,id FROM t_student;";
    FMResultSet *st = [self.db executeQuery:sql];
    while([set next]){
       NSString *name = [set stringForColumn:@"name"];
      int age = [set doubleForColumn:@"age"];
     double score = [set doubleForColumn:@"score"];
    NSLog(@"%@,%d,%d",name,age,score);
    }
}
@end
FMDB使用

在一般情况下,如果不涉及到多线程访问数据库,一般不用FMDatabaseQueue,因为性能比较低

 [self.dbQueue inDatabase:^(FMDatabase *db) {
        if ([db executeQuery:@"INSERT INTO  t_student (name, socre, rank) VALUES(?, ?, ?);", @"jack", @(99.8), @(1)]))
         {
             NSLog(@"插入表成功");
         }else{
             NSLog(@"插入表失败");
        }
    }];

在数据库中:事务主要用来执行多条SQL语句,让多条语句一起成功或者失败

如果开启了事务,只有提交事务才会真正的去修改数据库

// 加载数据库
self.db = [FMDatabaseQueue databaseQueueWithPath:sqlitePath];

// 更新数据库
- (IBAction)update:(UIButton *)sender{
      [self.dbQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        // 提交事务
        [db  beginTransaction];
        if ([db executeUpdate:@"update t_student set mongy = 1000 where name = ‘张三‘;"]) {
            NSLog(@"张三");
        }
        // 意外中断
        NSArray *arr = @[@"abc"];
        arr[6];
        if ([db executeUpdate:@"update t_student set money = 2000 where name =‘王五‘;"]) {
            NSLog(@"王五");
        }
        NSLog(@"%s",__func__);
        /** 提交事务 */
        [db commit];
    }];
}

 CoreData 

   CoreData框架是iOS SDK中一个比较强大的框架,让我们能够以面向对象的方式储存和管理数据。Core Data是一个模型层的技术,也同样是一种持久话化的技术,不仅仅是一个加载、保存数据的框架,而且还能够与内存中的数据很好的共事。

   在这个数据操作的过程中,我们完全不需要编写任何SQL语句,在经过了成千上万的应用程序和数以百万的用户反复的验证,CoreData已经是一个非常成熟的框架,Core Data利用OC语言和运行时,巧妙的集成了Core Foundation框架,是一个非常易于使用的框架,可以优雅的管理对象图,同时在内存管理方面也是异常的优异。

Core Data不是数据库,同样也不是将数据持久化保存到数据库的API,Core Data是一个主要用来管理对象图的框架。

Core Data stack是Core Data的核心:

   NSManagedObjectContext:管理模型对象的集合

   NSPersistentStoreCoordinator:主要负责将数据保存到磁盘

   NSManageObjectModel:被管理的模型对象

   CoreData框架提供了对象-关系映射的功能,可以通过不编写SQL语句实现OC对象转化成数据库数据,保存数据库中,同时也可以将数据库数据还原成为OC对象。

   其根本是对SQLite的包装,效率比SQLite低;

创建持久化存储和上下文:

// 实例化数据模型
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:modelName withExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// 实例化持久化存储
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSURL *dbURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
dbURL = [dbURL URLByAppendingPathComponent:dbName];
[psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:dbURL options:nil error:NULL];
// 实例化上下文
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:psc];

   更深入的了解,使用CoreData 需要导入CoreData.framework和导入CoreData/CoreData.h头文件

    在CoreData中,需要进行映射的对象叫做实体(entity),需要使用CoreData模型文件来描述应用的所有实体和实体属性,它们之间属于一对一双向关联

主要操作过程为:1.创建模型文件2.添加实体3.添加实体的属性

可以通过Xcode图形界面实现

    默认情况下,从数据库中取出的数据都是NSManageObject对象,NSManageObject的工作模式与NSDictionary对象类似,都是通过键-值对来进行存取所有的实体属性。

     setValue: forKey:存储属性值(属性名)

     valueForKey:获取属性值(属性名)

下面通过代码来描述: 

   NSEntityDescription:用来描述实体

首先开始搭建Core Data环境

// 1. 从应用程序包开始加载模型文件
NSManageObjectModel *model = [NSManageObjectModel mergedModelFromBundles:nil];
// 2. 传模型,初始化NSPersistentStoreCoordinator
NSPersistentStoreCoordinator *psc = [[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
// 3. SQL的文件路径
NSString *docs = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) lastObject];
NSURL *url = [NSURL fileURLWithPath:[docs stringByAppendingPathComponent:@"person.data"]];
// 4. 添加持久化存储库,这里使用SQLite作为存储库
NSError *error = nil;
NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error ];
if(store == nil){ // 直接抛异常
   NSException raise:@"添加数据库错误" format:@"%@",[error localizedDescription]];
}
// 5. 初始化上下文,设置persistentStoreCoordinator 属性
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init ];
context.persistentStoreCoordinator = psc;

对于持久化存储库类型,一般来说还是选择SQLite作为持久化存储库

下一步进行添加数据

// 传入上下文,创建一个person实体对象
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
// 设置简单属性
[person setValue:@"张三" forKey:@"name"];
[person setValue:[NSNumber numberWithInt:27] forKey:@"age"];
// 传入上下文, 创建一个Card实体对象
NSManagedObject *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:context];
// 设置card的简单属性
[card setValue:@"23321321313" forKey:@"no"];
// 设置person与card之间的关联关系
[person setValue:@"card" forKey:@"card"];
// 利用上下文对象,将数据同步到持久化存储库
NSError *error = nil;
BOOL success = [context save:&error];
if (!success){
    [NSException raise:@"访问数据库错误" format:@"%@",[error localizedDescription]];
}// 如果只是想做更新操作,只需要在更改了实体对象的属性后调用[context save:&error],就可以将更改的数据同步数据库.

查询数据

// 初始化一个查询请求
NSFetchRequest *request = [NSFetchRequest alloc] init];
// 设置要查询的实体
NSEntityDescription *desc = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context]; 
// 设置排序(age降序)
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO ];
request.sortDescriptors = [NSArray arrayWithObject:sort];
// 设置条件过滤
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like%@",@"*aaaaa*"];
request.predicate = predicate;
// 对于条件过滤,数据库中的% 要使用*代替

// 执行请求
NSError *error = nil;
NSArray *objs = [context executeFetchRequest:request error:&error];
if (error){
    [NSException raise:@"查询错误" format:@"%@",[error localizedDescription]];
}
// 遍历数据
for (NSManagedObject *obj  in objs){
    NSLog(@"name = %@",[obj valueForKey:@"name"]);
}

 删除数据

// 传入需要删除的实体对象
[context deleteObject:managedObject];
// 将结果同步到数据库
NSError *error = nil;
[context save:&error];
if (error){
    [NSException raise :@"删除错误" format:@"%@",[error localizedDescription]];
}

Core Data的延迟加载

Core Data不会根据实体的关联关系立即去获取相应的关联对象,只有当应用真的需要的时候,才会去查询数据库,加载实体的信息。

创建NSManagedObject的子类

在默认情况下,通过CoreData取出的实体都是NSManagedObject类型,当有时需要添加业务方法的时候就需要创建NSManagedObject的子类

(1) use scalar properties for primitive data type :在iOS5和OS X10.7之前,scalar不能自动生成,程序员必须自己添加setter和getter的实现

(2) @dynamic: 在OC中,如果将某个属性实现为@dynamic,意味着告诉编译器不会在编译的时候确定这个属性的行为实现,因此不需要在编译期间对这个属性做setter和getter的检查 

// 生成person实体对象
Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person"   inManagedObjectContext:context];
person.name = @"张三";
person.age = [NSNumber numberWithInt:27];
Card *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:context];
card.no = @"23423422342";
person.card = card;

Core Data不是线程安全的,它的三个类都不是线程安全的。在多线程使用CoreData的时候,每个需要执行Core Data的线程都需要有一个NSManagedObjectContext,但是都不会知道彼此的存在,同时在一个上下文中做的修改也不会自动同步到另外一个上下文中。

iOS数据持久化

标签:

原文地址:http://www.cnblogs.com/rosered/p/4970593.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!