iOS应用数据存储的常用方式
XML属性列表(plist)归档
Preference(偏好设置)
NSKeyedArchiver归档(NSCoding)
SQLite3
Core Data
注意:最常用的就是Documents,是有备份的
应用沙盒结构分析
应用程序包:(上图中的Layer)包含了所有的资源文件和可执行文件
Documents:保存应用运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录。例如,游戏应用可将游戏存档保存在该目录
tmp:保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录
Library/Caches:保存应用运行时生成的需要持久化的数据,iTunes同步设备时不会备份该目录。一般存储体积大、不需要备份的非重要数据
Library/Preference:保存应用的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时会备份该目录
应用沙盒目录的常见获取方式
沙盒根目录:NSString*home = NSHomeDirectory();
Documents:(2种方式)
利用沙盒根目录拼接”Documents”字符串
NSString *home = NSHomeDirectory();
NSString *documents = [home stringByAppendingPathComponent:@"Documents"];
// 不建议采用,因为新版本的操作系统可能会修改目录名
利用NSSearchPathForDirectoriesInDomains函数
// NSUserDomainMask 代表从用户文件夹下找
// YES 代表展开路径中的波浪字符“~”
NSArray *array = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);
// 在iOS中,只有一个目录跟传入的参数匹配,所以这个集合里面只有一个元素
NSString*documents = [array objectAtIndex:0];
tmp:NSString*tmp = NSTemporaryDirectory();
Library/Caches:(跟Documents类似的2种方法)
利用沙盒根目录拼接”Caches”字符串
利用NSSearchPathForDirectoriesInDomains函数(将函数的第2个参数改为:NSCachesDirectory即可)
Library/Preference:通过NSUserDefaults类存取该目录下的设置信息
属性列表
属性列表是一种XML格式的文件,拓展名为plist
如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,就可以使用writeToFile:atomically:方法直接将对象写到属性列表文件中
属性列表-归档NSDictionary
将一个NSDictionary对象归档到一个plist属性列表中
// 将数据封装成字典
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:@"母鸡"forKey:@"name"];
[dict setObject:@"15013141314"forKey:@"phone"];
[dict setObject:@"27" forKey:@"age"];
// 将字典持久化到Documents/stu.plist文件中
[dict writeToFile:path atomically:YES];
用xcode打开属性文件
属性列表-恢复NSDictionary
读取属性列表,恢复NSDictionary对象
// 读取Documents/stu.plist的内容,实例化NSDictionary
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSLog(@"name:%@", [dictobjectForKey:@"name"]);
NSLog(@"phone:%@", [dictobjectForKey:@"phone"]);
NSLog(@"age:%@", [dictobjectForKey:@"age"]);
打印信息如下
读取上次保存的设置
NSUserDefaults *defaults = [NSUserDefaultsstandardUserDefaults];
NSString *username = [defaults stringForKey:@"username"];
float textSize = [defaults floatForKey:@"text_size"];
BOOL autoLogin = [defaults boolForKey:@"auto_login"];
注意:UserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize方法强制写入
[defaultssynchornize];
NSKeyedArchiver
如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复
不是所有的对象都可以直接用这种方法进行归档,只有遵守了NSCoding协议的对象才可以
NSCoding协议有2个方法:
encodeWithCoder:
每次归档对象时,都会调用这个方法。一般在这个方法里面指定如何归档对象中的每个实例变量,可以使用encodeObject:forKey:方法归档实例变量
initWithCoder:
每次从文件中恢复(解码)对象时,都会调用这个方法。一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用decodeObject:forKey方法解码实例变量
NSKeyedArchiver-归档Person对象(Person.h)
@interface Person : NSObject<NSCoding>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) float height;
@end
@implementation Person
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.nameforKey:@"name"];
[encoder encodeInt:self.ageforKey:@"age"];
[encoder encodeFloat:self.heightforKey:@"height"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self.name = [decoder decodeObjectForKey:@"name"];
self.age = [decoder decodeIntForKey:@"age"];
self.height = [decoder decodeFloatForKey:@"height"];
return self;
}
- (void)dealloc {
[super dealloc];
[_name release];
}
@end
归档(编码)
Person *person = [[[Person alloc] init] autorelease];
person.name = @"MJ";
person.age = 27;
person.height = 1.83f;
[NSKeyedArchiver archiveRootObject:person toFile:path];
恢复(解码)
Person *person= [NSKeyedUnarchiver unarchiveObjectWithFile:path];
NSKeyedArchiver-归档对象的注意
如果父类也遵守了NSCoding协议,请注意:
应该在encodeWithCoder:方法中加上一句
[super encodeWithCode:encode];
确保继承的实例变量也能被编码,即也能被归档
应该在initWithCoder:方法中加上一句
self = [superinitWithCoder:decoder];
确保继承的实例变量也能被解码,即也能被恢复
NSData-归档2个Person对象到同一文件中
归档(编码)
// 新建一块可变数据区
NSMutableData *data = [NSMutableData data];
// 将数据区连接到一个NSKeyedArchiver对象
NSKeyedArchiver*archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data]autorelease];
// 开始存档对象,存档的数据都会存储到NSMutableData中
[archiver encodeObject:person1forKey:@"person1"];
[archiver encodeObject:person2forKey:@"person2"];
// 存档完毕(一定要调用这个方法)
[archiver finishEncoding];
// 将存档的数据写入文件
[data writeToFile:path atomically:YES];
NSData-从同一文件中恢复2个Person对象
恢复(解码)
// 从文件中读取数据
NSData *data = [NSData dataWithContentsOfFile:path];
// 根据数据,解析成一个NSKeyedUnarchiver对象
NSKeyedUnarchiver*unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
Person *person1 = [unarchiver decodeObjectForKey:@"person1"];
Person *person2 = [unarchiver decodeObjectForKey:@"person2"];
// 恢复完毕
[unarchiver finishDecoding];
数据存储
# 2-数据存储
## 1.plist存储
1.plist存储,生成一个plist文件.
2.plist不是数组就是字典,plist存储就是用来存储字典或者数组.
注意:Plist不能存储自定义对象
3.获取应用沙盒中Caches文件路径
directory:获取哪个文件夹
domainMask:在哪个范围内搜索,NSUserDomainMask:表示在用户的手机上查找
expandTilde:是否展开全路径 YES:表示展开全路径 NO:不会展开全路径,会把应用沙盒的路径用波浪号(~)代替
获取到Caches文件夹路径
NSString *cachePath =NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
4.读取plist,之前是什么类型存储的,读取也是什么
## 2.偏好设置存储
偏好设置存储:NSUserDefaults
以字典的形式进行偏好设置,用法跟字典.
偏好设置好处: 1.不需要关心文件名
2.快速进行键值对存储
3.直接存储基本数据类型
## 3.归档
1.NSKeyedArchiver专门用来做自定义对象归档
什么时候调用:当一个对象要归档的时候就会调用这个方法归档
作用:告诉苹果当前对象中哪些属性需要归档
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoderencodeObject:_name forKey:@"name"];
[aCoderencodeInt:_age forKey:@"age"];
}
什么时候调用:当一个对象要解档的时候就会调用这个方法解档
作用:告诉苹果当前对象中哪些属性需要解档
initWithCoder什么时候调用:只要解析一个文件的时候就会调用
- (id)initWithCoder:(NSCoder *)aDecoder
{
#warning [super initWithCoder]
if (self = [super init]) {
解档
注意一定要记得给成员属性赋值
_name = [aDecoder decodeObjectForKey:@"name"];
_age = [aDecoder decodeIntForKey:@"age"];
}
return self;
}
创建、打开、关闭数据库
创建或打开数据库
// path为:~/Documents/person.db
sqlite3 *db;
int result = sqlite3_open([pathUTF8String], &db);
代码解析:
sqlite3_open()将根据文件路径打开数据库,如果不存在,则会创建一个新的数据库。如果result等于常量SQLITE_OK,则表示成功打开数据库
sqlite3 *db:一个打开的数据库实例
数据库文件的路径必须以C字符串(而非NSString)传入
关闭数据库:sqlite3_close(db);
执行不返回数据的SQL语句
执行创表语句
char *errorMsg; // 用来存储错误信息
char *sql = "create table if not exists t_person(idinteger primary key autoincrement, name text, age integer);";
int result = sqlite3_exec(db,sql, NULL, NULL, &errorMsg);
代码解析:
sqlite3_exec()可以执行任何SQL语句,比如创表、更新、插入和删除操作。但是一般不用它执行查询语句,因为它不会返回查询到的数据
sqlite3_exec()还可以执行的语句:
开启事务:begintransaction;
回滚事务:rollback;
提交事务:commit;
带占位符插入数据
char *sql = "insert into t_person(name, age) values(?,?);";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db,sql, -1, &stmt, NULL) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, "母鸡", -1, NULL);
sqlite3_bind_int(stmt,2, 27);
}
if (sqlite3_step(stmt)!= SQLITE_DONE) {
NSLog(@"插入数据错误");
}
sqlite3_finalize(stmt);
代码解析:
sqlite3_prepare_v2()返回值等于SQLITE_OK,说明SQL语句已经准备成功,没有语法问题
sqlite3_bind_text():大部分绑定函数都只有3个参数
第1个参数是sqlite3_stmt*类型
第2个参数指占位符的位置,第一个占位符的位置是1,不是0
第3个参数指占位符要绑定的值
第4个参数指在第3个参数中所传递数据的长度,对于C字符串,可以传递-1代替字符串的长度
第5个参数是一个可选的函数回调,一般用于在语句执行后完成内存清理工作
sqlite_step():执行SQL语句,返回SQLITE_DONE代表成功执行完毕
sqlite_finalize():销毁sqlite3_stmt*对象
查询数据
char *sql = "select id,name,age from t_person;";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db,sql, -1, &stmt, NULL) == SQLITE_OK) {
while (sqlite3_step(stmt)== SQLITE_ROW) {
int _id = sqlite3_column_int(stmt, 0);
char *_name = (char *)sqlite3_column_text(stmt, 1);
NSString *name = [NSString stringWithUTF8String:_name];
int _age = sqlite3_column_int(stmt, 2);
NSLog(@"id=%i, name=%@,age=%i", _id, name, _age);
}
}
sqlite3_finalize(stmt);
代码解析
sqlite3_step()返回SQLITE_ROW代表遍历到一条新记录
sqlite3_column_*()用于获取每个字段对应的值,第2个参数是字段的索引,从0开始
搭建CoreData上下文环境
从应用程序包中加载模型文件
NSManagedObjectModel*model = [NSManagedObjectModel mergedModelFromBundles:nil];
传入模型,初始化NSPersistentStoreCoordinator
NSPersistentStoreCoordinator*psc = [[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]autorelease];
构建SQLite文件路径
NSString *docs =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) lastObject];
NSURL *url =[NSURL fileURLWithPath:[docsstringByAppendingPathComponent:@"person.data"]];
搭建CoreData上下文环境
添加持久化存储库,这里使用SQLite作为存储库
NSError *error = nil;
NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nilURL:url options:nil error:&error];
if (store == nil) { // 直接抛异常
[NSException raise:@"添加数据库错误" format:@"%@", [errorlocalizedDescription]];
}
初始化上下文,设置persistentStoreCoordinator属性
NSManagedObjectContext*context = [[NSManagedObjectContext alloc] init];
context.persistentStoreCoordinator = psc;
// 用完之后,还是要[context release];
添加数据
传入上下文,创建一个Person实体对象
NSManagedObject *person= [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
设置简单属性
[person setValue:@"MJ" forKey:@"name"];
[person setValue:[NSNumber numberWithInt:27]forKey:@"age"];
传入上下文,创建一个Card实体对象
NSManagedObject *card =[NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:context];
[card setValue:@"4414241933432"forKey:@"no"];
设置Person和Card之间的关联关系
[personsetValue:card forKey:@"card"];
利用上下文对象,将数据同步到持久化存储库
NSError *error = nil;
BOOL success = [contextsave:&error];
if (!success) {
[NSException raise:@"访问数据库错误"format:@"%@", [error localizedDescription]];
}
// 如果是想做更新操作:只要在更改了实体对象的属性后调用[contextsave:&error],就能将更改的数据同步到数据库
查询数据
初始化一个查询请求
NSFetchRequest *request= [[[NSFetchRequest alloc] init]autorelease];
设置要查询的实体
NSEntityDescription*desc = [NSEntityDescription entityForName:@"Person"inManagedObjectContext:context];
设置排序(按照age降序)
NSSortDescriptor *sort= [NSSortDescriptor sortDescriptorWithKey:@"age"ascending:NO];
request.sortDescriptors = [NSArray arrayWithObject:sort];
设置条件过滤(name like '%Itcast-1%')
NSPredicate *predicate= [NSPredicate predicateWithFormat:@"namelike %@", @"*Itcast-1*"];
request.predicate= predicate;
执行请求
NSError *error = nil;
NSArray *objs = [context executeFetchRequest:requesterror:&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不会根据实体中的关联关系立即获取相应的关联对象
比如通过Core Data取出Person实体时,并不会立即查询相关联的Card实体;当应用真的需要使用Card时,才会查询数据库,加载Card实体的信息
创建NSManagedObject的子类
那么生成一个Person实体对象就应该这样写
Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person"inManagedObjectContext:context];
person.name = @"MJ";
person.age = [NSNumber numberWithInt:27];
Card *card = [NSEntityDescription insertNewObjectForEntityForName:@”Card"inManagedObjectContext:context];
card.no = @”4414245465656";
person.card =card;
************************笔记*************************
*****************************************************
.数据存储(数据持久化)
1> 介绍iOS数据存储的5种方式
2> 介绍应用沙盒(应用程序的文件夹)
• 如何找到应用沙盒的路径?首先需要显示隐藏文件。
• 点击前往->个人->资源库->ApplicationSupport->iPhone Simulator->7.1->里面全是应用沙盒
3> 应用沙盒怎么多文件夹保存,在哪个文件夹。介绍沙盒里的每一个文件夹。
.plist存储
• 把一些系统自带的OC对象生成pilst文件存储起来。
1> 了解数据存储:数据存储一般有两个操作,一个存,一个取。拖两个按钮,一个用来存,一个用来取
2>plist存储原理:
• 只要有writeToFile的对象,就能进行plist存储,调用writeToFile就能自动生成plist格式的文件。
• 一般常用的Foundation对象都有这个方法,数组,字典,字符串等
3> 如何写入到沙盒,需要获取沙盒路径。
• 获取Documents路径
• 拼接文件名,因为数据是写入到文件中,不是写入到文件夹中。路径之间通过/分开的,为了避免自己写/,会用stringByAppendingPathCompent,自动在文件夹与文件之间添加/。
4> 如何读取,存储是什么类型存储,读取出来也是什么类型,直接用存储的类型,解析文件就好,用ContentsOfFile解析。
5> 注意plist存储,不能存储自定义对象,会失败的。
偏好设置
1> 什么是偏好设置存储:就是保存一些基本的信息,账号,密码,状态。
2> 偏好设置原理:不需要关心文件名,直接通过NSUserDefaults操作,默认就存到偏好设置里面了。
• 通过NSUserDefaults就能直接访问软件的偏好设置(Library/Preferences)
3> 怎么利用偏好设置存储?利用NSUserDefaults调用setObject:forKey存储。
• 偏好设置底层实现原理:底层其实就是利用一个字典,存储一些键值对。
• 偏好设置好处:能快速存储一些键值对,如果用字典去存储,还需要获取文件名比较麻烦。
• 偏好设置坏处:不能及时存储,需要做同步操作,把内存中的数据同步到硬盘上。
4> 怎么利用偏好设置读取?和字典一样,根据刚刚存储的Key读取。
自定义对象归档(归档:数据存储)
1> 自定义对象如何归档:用NSKeyedArchiver,调用archiveRootObject:toFile:方法,需要传一个对象,自定义一个对象,传进去。
• 会报错,说对象没有encodeWithCoder方法,说明归档的时候默认会调用这个方法,去实现这个方法。
• 默认打不出encodeWithCoder,必须遵守NSCoding协议才能实现这个方法。
•encodeWithCoder什么时候调用:对象归档时候调用
•encodeWithCoder作用:告诉系统对象里的哪些属性需要归档,怎么去归档,根据一个key去归档,目的就是以后取的时候,也根据这个key去取数据。
2> 自定义对象如何解档:用NSKeyedUnarchiver,调用unarchiveObjectWithFile方法,需要传一个文件名。
• 会报错,说对象没有initWithCoder方法,说明解档的时候默认会调用这个方法,去实现这个方法。
•initWithCoder什么时候调用:对象解档时候调用
•initWithCoder作用:告诉系统对象里的哪些属性需要解档,怎么去解档,根据之前存储的key去解档
•initWithCoder是一个初始化方法,需要先初始化父类的,但是不能调用[super initWithCoder:],因为父类NSObject没有遵守NSCoding协议。
3>initWithCoder什么时候需要调用[super initWithCoder:]
•initWithCoder原理:只要解析文件就会调用,xib,storyboard都是文件,因此只要解析这两个文件,就会调用initWithCoder。
• 因此如果在storyboard使用自定义view,重写initWithCoder方法,一定要调用[super initWithCoder:],因为只有系统才知道怎么解析storyboard,如果没有调用,就解析不了这个文件。