标签:
在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
在一般情况下,如果不涉及到多线程访问数据库,一般不用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的子类
(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,但是都不会知道彼此的存在,同时在一个上下文中做的修改也不会自动同步到另外一个上下文中。
标签:
原文地址:http://www.cnblogs.com/rosered/p/4970593.html