IOS中数据的持久化保存这块内容,类似于Android中文件的几种常见的存储方式。
对于数据的持久化存储,ios中一般提供了4种不同的机制。
1.属性列表
2.对象归档
3.数据库存储(SQLite3)
4.苹果公司提供的持久性工具Core Data。
其实储存的形式无非就这么几种,而我们还必须要关心的是,这些文件会被放置在那个文件下,然后如何读取。
也就是说:IOS上数据存储,我们要了解的两点,数据存储格式(也就是存储机制),数据存储位置。
1》文件如何存储(如上面4点)
2》文件存储在哪里。
对于数据的操作,其实我们关心的是操作的速率。
就好比在Adnroid中偏好存储,数据库存储,io存储一样。
我大致问了我们公司新来的ios哥们,他说他们培训机构基本对数据操作这块就讲了属性列表和数据库,以及普通的文件存储(比如音视频图这些多媒体数据)。
我就只好先看看书了。
一:应用文件目录
首先我们来看了解下ios数据存储位置,因为只有知道位置路径我们才能去读取数据,而数据的持久化机制不过是针对操作速率来考虑的,
比如我们大致知道属性列表(既键值对形式)的存储熟虑应该高于数据库高于io文件流存储。
我们在选择用何种机制存储数据,主要也是看数据的形式。
一个ios应用安装后大致会有如下文件夹及其对应路径:
在mac上看模拟器中应用路径:
/Users/nono/Library/Application Support/iPhone Simulator/5.1/Applications/2D135859-1E80-4754-B36D-34A53C521DE3
你在finder中的home下可能找不到Library这个目录,因为貌似是影藏起来了(我这机器上是,在终端可以看到)。
最后那一窜的类似序列号的东西就是ios自动给应用生成的一组应用唯一识别码最为了应用的home目录名。
其下面就是上图所示了。
书上对这些文件夹介绍:
Document:应用程序将其数据存储在这个文件夹下,基于NSUserDefaults的首选项的设置除外。
简单理解是,基本上我们要操作的一些数据都是存储在这个文件夹下面的
TIPS:这边提下一点,对于ios系统这么分配文件夹,是因为在设备进行同步时,ITunes有选择性的意识来备份文件。
比如我们可以猜到,tmp下的应该就不会备份了。
对于Document文件夹目录路径的获取,API提供了这么一种方法:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = [paths objectAtIndex:0];
Library:基于NSUserDefault首选项设置存储在其下Preferences文件夹中,简单来说,这个文件夹一般你很少操作到。
书上对于这部分基本没介绍。估计对于初级部分是跳过了。
Tmp:应用临时存储文件,当不需要时,应用负责删除其下的文件数据。
该文件也提供了目录获取方法:
NSString *tmpDoc = NSTemporaryDirectory();
应用程序文件:这个基本没提到书上,但是我们大致可以猜测,这就是整个应用程序的程序文件夹吧。
好了,以上我们大致解决了我们提到的第一个点,文件存储目录
二:数据存储机制
1.属性列表 Write写入方式
这个其实我们早见过,plist就是,感觉用来存储键值对小数据是最合适,因为速率很高。
这个存储机制很简单,对于前面我们使用过了在plist文件来读取数据填充一些列表,只不过那会plist文件存储位置不同,
用的是Mainbundle什么的来返回文件夹,其实这边我也推测,上面提到有个应用程序文件夹,它下面的文件就是这么来读取的~(反正暂时不管他)
这边不过就是改变了存储位置,数据操作还是一样的
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = [paths objectAtIndex:0];
NSString *myFile = [docPath stringByAppendingPathComponent:@"my.list"];
//读取文件
NSArray *array = [[NSArray alloc] initWithContentsOfFile:myFile];
//操作完若修改了数据则,写入文件
[array writeToFile:myFile atomically:YES];
2.对象归档
上面的属性列表存储机制,我们都知道,这个机制支持NSArray,NSDictionary,NSData,NSString,NSNumber,NSDate 等等
这些对象直接写入plist文件中。
那么对于一些复杂对象,我要保存整个这个对象数据呢?
反正我是这么觉得,这个机制很像java中的对象整体序列化。当然,这些数据在读取是就需要遵循一种墨守成规的协议了。
首先我们定义的对象类,必须实现NSCoding和NSCopying协议(额,网上说后面这个不实现也可以,我猜是他对象没有copy操作,因此没出错)书本上反正是实现了这两个协议
然后归档中用到的操作类
NSKeyedArchiver
这边我们定义一个对象,h文件中定义两属性,申明要实现的NSCoding和NSCopying协议
Person.h
#import
@interface Person : NSObject
@property (nonatomic,retain)NSString *gender; // 性别
@property (nonatomic,retain)NSString *name; // 姓名
@property (nonatomic,assign)int age; // 年龄
@end
Person.m
#import "Person.h"
@implementation Person
// 归档 实际上就是将当前类的属性编码为NSData类型
- (void)encodeWithCoder:(NSCoder *)aCoder{
// 实际编码过程,原理就是将name这个属性的值编码为NSData类型,因为我们解码的时候,需要重新为该类属性赋值,所以需要加标记,也就是Key。
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeObject:self.gender forKey:@"gender"];
[aCoder encodeInt:self.age forKey:@"age"];
NSLog(@"执行了归档的方法");
}
// 反归档 因为归档的过程中,我们是将当前类转换为NSData类型,并且储存到了某个文件中,当我们从文件中读取出来数据的时候,基础类型,例如:NSArray等都有initWithContentsOfFile的方法来初始化,但是复杂类型没有类似的方法,只能是反归档来完成此事。
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
// 将刚才编码为NSData类型的属性,又通过解码方式变回原来的类型,上面编码过程中,所赋给的key值为何种名称,底下的解码得对应上。
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntForKey:@"age"];
self.gender = [aDecoder decodeObjectForKey:@"gender"];
NSLog(@"执行了反归档的方法");
}
return self;
}
@end
RootViewController.m
#import "RootViewController.h"
#import "SandBoxPaths.h"
#import "Person.h"
@interface RootViewController ()
@end
@implementation RootViewController
#pragma mark -------- 归档
// 归档并存入沙盒中
- (void)archiverAndSaveSandBox{
// 归档实际上就是将Person对象转换为NSData类型的数据
Person *person = [[Person alloc]init];
person.name = @"厦航";
person.age = 24;
person.gender = @"女";
// 归档的时候,实际是将复杂类对象的属性一一转换为NSData类型,所以是逐步转换的,最终需要将每一步转换好的NSData类型组装为一个完整的NSData,所以我们需要一个可变的NSData来接收它
NSMutableData *receiveData = [[NSMutableData alloc]init];
//=========================================================================
// 归档操作需要借助系统的一个归档工具类来实现,这个类实际的操作就是将Person对象转换为NSData类型的数据,并赋值给刚才咱们初始化的NSData对象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:receiveData];
//=========================================================================
// 归档开始
[archiver encodeObject:person forKey:@"person"];
// 需要有一个标志,让我们知道归档完成了,我们的receiveData中有值了 (不然会出错误)
[archiver finishEncoding];
// 已经转换完成的,就可以进行数据持久化了
NSString *pathString = [[SandBoxPaths documentsPath]stringByAppendingPathComponent:@"person.DA"];
[receiveData writeToFile:pathString atomically:YES];
NSLog(@"-------%@",pathString);
}
#pragma mark ----- 反归档
- (void)unArichiver{
// 反归档,实际上就是将NSData类型转换为复杂类型对象,就是本例中的person对象
NSString *pathString = [[SandBoxPaths documentsPath]stringByAppendingPathComponent:@"person.DA"];
NSData *data = [[NSData alloc]initWithContentsOfFile:pathString];
// 反归档,反归档也需要借助系统的一个反归档工具类来实现
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data];
// 开始反归档
Person *person = [unarchiver decodeObjectForKey:@"person"];
NSLog(@"name-------%@",person.name);
}
- (void)viewDidLoad {
[super viewDidLoad];
[self archiverAndSaveSandBox];
[self unArichiver];
}
三.数据库存储
1.这里必须先要导入一个系统文件。在xcode7.0之后把后缀改了,现在叫libsqlite3.tbd
//里面有操作sqlite数据库的所有函数,我们要操作数据库,就需要导入libSqlite3的系统库 路径:TARGETS -> Build Phases -> Link Binary with Libraries
导入头文件
#import //里面有操作sqlite数据库的所有函数,我们要操作数据库,就需要导入libSqlite3的系统库 路径:TARGETS -> Build Phases -> Link Binary with Libraries
#import "SandBoxPaths.h"
@implementation RootViewController
#pragma mark ------ 打开或者创建数据库
// 打开或者创建数据库
- (sqlite3 *)openOrCreateDB{
// 首先我们需要一个保存数据库的文件路径
NSString *dbPath = [[SandBoxPaths documentsPath]stringByAppendingPathComponent:@"db.sqlite" ];
// 数据库的句柄
sqlite3 *sqlite3 = NULL;
// 如果数据库已经存在,此函数就是打开当前数据库文件,如果该数据库文件不存在,那么此函数就是创建数据库文件并打开。
// filename:数据库文件的路径
// ppDb: 数据库句柄此变量的指针的指针。当前数据库创建或者打开成功之后,会将地址指针保存在该参数中,这样,此句柄变量就可以通过指针来操作数据库。
// 由于此函数为C函数,但是dbPath为OC对象,所以需要将OC字符串转换为C字符串
// 此函数有返回值,sqlite3中对所有的操作,只要没有返回数据结果集的,都会有一个int的返回值,来标识此操作是否进行成功。
int result = sqlite3_open(dbPath.UTF8String, &sqlite3);
if (result == SQLITE_OK) {
NSLog(@"数据库打开成功");
return sqlite3;
}else{
NSLog(@"数据库打开失败");
return NULL;
}
}
#pragma mark ---------- 执行无返回结果集的SQL操作
// 执行无返回结果集的SQL操作
- (BOOL)exeSqlWithSQLString:(NSString *)sqlStr{
// 打开数据库
sqlite3 *sqlDB = [self openOrCreateDB];
// 执行SQL的函数
// 第一个参数:数据库的句柄,可以理解为就是数据库
// 第二个参数:所要执行的sql语句
// 第三个参数:执行完SQL之后的回调方法。
// 第四个参数:回调方法的第一个参数
// 第五个参数:错误日志,等同于OC中的NSError,这里是char类型。
int result = sqlite3_exec(sqlDB, sqlStr.UTF8String, NULL, NULL, NULL);
if (result == SQLITE_OK) {
NSLog(@"语句执行成功");
// 关闭数据库
sqlite3_close(sqlDB);
return YES;
}else{
NSLog(@"语句执行失败");
// 关闭数据库
sqlite3_close(sqlDB);
return NO;
}
}
#pragma mark ----- 查询数据库
// 查询数据库
- (NSArray *)queryDBWithSqlString:(NSString *)sqlStr{
// 初始化一个可变数组,一会用来盛放所有的结果
NSMutableArray *resultMutableArray = [[NSMutableArray alloc]init];
// 打开数据库
sqlite3 *sqlDB = [self openOrCreateDB];
// 用来保存记录的指针对象
sqlite3_stmt *stament = NULL;
// 用来检查SQL的函数,如果SQL语句编译无问题,就将编译好的SQL保存到stament中
int result = sqlite3_prepare(sqlDB, sqlStr.UTF8String, -1, &stament, NULL);
if (result == SQLITE_OK) {
while (sqlite3_step(stament) == SQLITE_ROW){
// while每执行一次,就会有一条新记录,所以我们每次都需要一个新的字典,也就是初始化好的字典来盛放记录
NSMutableDictionary *rowDic = [NSMutableDictionary dictionary];
// 每执行一次step函数,都会在stament中保存一条完整的记录。
// 取出一条记录中的某个字段
// 第二个参数是指取出第几列的字段值
int number = sqlite3_column_int(stament, 0);
[rowDic setObject:[NSNumber numberWithInt:number]forKey:@"number"];
const unsigned char *name = sqlite3_column_text(stament, 1);
NSString *nameString = [NSString stringWithCString:(const char *)name encoding:NSUTF8StringEncoding];
// NSString *nameString = [NSString stringWithUTF8String:(const char *)name];
[rowDic setObject:nameString forKey:@"name"];
const unsigned char *gender = sqlite3_column_text(stament, 2);
NSString *genderString = [NSString stringWithCString:(const char *)gender encoding:NSUTF8StringEncoding];
[rowDic setObject:genderString forKey:@"gender"];
//组装好字典之后,将该字典放入数组
[resultMutableArray addObject:rowDic];
}
}
// 关闭数据库
sqlite3_close(sqlDB);
// 释放stament所持有的资源
sqlite3_finalize(stament);
return resultMutableArray;
}
这里封装了DataBaseHelper单例可以直接拿来使用。
四。Core Data存储机制
大致浏览下基本感觉就是将对象归档搞成了可视化和简单化。
这块内容比较多。网上资料也挺丰富的。
暂时不做介绍了。
总结下:其实对于ios数据存储,最常用和主要要掌握的就是属性列表和数据库,因为两个是出镜率比较高的。
其他可能在数据存明显体现出储优势时,我们会去考虑用另外两种机制。
基础的来说,必须掌握属性列表和sqlite的操作存储。