对于任何一种语言来说,数据持久化都可以说是最重要、最实用的部分。
可以说任何一个程序都必须要涉及到数据的持久化,对于ios来说,数据的持久化主要有4种机制,分别为属性列表、对象归档、SQLite数据库和Core Data。其中的Core Data本质上也是通过SQLite存储的,只不过实用了ORM技术来实现。
首先要先知道一个概念,对于熟悉ios的人来说,应该都对document这个文件不陌生,在ios中,这个文件夹称为“沙箱目录”,任何一个程序安装完成后都会有这么一个文件夹产生,这是一个相对独立的文件夹,不同应用之间一般不能互相访问其他应用的沙箱目录。获取该目录的代码如下:
NSString* documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
在数据的持久化中会经常使用到这个方法获取沙箱目录。
一、属性列表
在java开发中,对于小数据存储,我们会使用xml文件来存储并使用xml解析来获取数据,这是一个比较复杂的过程。而在ios中,属性列表,也是一种xml文件,但是ios对其进行大量的封装,使其使用起来非常的方便和简洁,该文件存储为plist文件。其使用过程中的核心语句如下:
#pragma mark --数据持久化
- (NSString*)applicationDocumentsDirectoryFile{
//获取并返回属性列表文件的详细路径
NSString* documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString* path = [documentDirectory stringByAppendingString:@"/NoteList.plist"];
return path;
}
- (void)createEditableCopyOfDatabaseIfNeeded{
NSFileManager* fileManager = [NSFileManager defaultManager];
NSString* writableDBPath = [self applicationDocumentsDirectoryFile];
//判断plist文件是否已经存在
if (![fileManager fileExistsAtPath:writableDBPath]) {
//获取资源目录
NSString* defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/NoteList.plist"];
NSError* error;
//将资源目录中的plist文件复制到Document文件夹中
NSAssert([fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error], @"写入文件错误");
NSLog(@"成功复制资源文件");
}
}
- (NSMutableArray*)findAll{
//获取plist文件详细路径
NSString* path = [self applicationDocumentsDirectoryFile];
//将plist文件中的数据存储到Array数组中
NSMutableArray* array = [[NSMutableArray alloc] initWithContentsOfFile:path];
NSMutableArray* data = [[NSMutableArray alloc] init];
//创建日期格式类
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
//遍历Array数组
for (NSDictionary* dict in array) {
Note* note = [[Note alloc] init];
NSDate* date = [dateFormatter dateFromString:[dict objectForKey:@"date"]];
note.date = date;
note.content = [dict objectForKey:@"content"];
[data addObject:note];
}
return data;
}
- (int)create:(Note*)model{
NSString* path = [self applicationDocumentsDirectoryFile];
NSMutableArray* array = [[NSMutableArray alloc] initWithContentsOfFile:path];
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
//创建一个字典类型来存储note对象
NSDictionary* dict = [NSDictionary dictionaryWithObjects:@[[dateFormatter stringFromDate:model.date],model.content] forKeys:@[@"date",@"content"]];
//将创建的字典类型增加Array数组中
[array addObject:dict];
//将改变了的数组写入到plist文件中
[array writeToFile:path atomically:YES];
return 0;
}
二、对象归档
对象归档是一种将对象序列化为文件并进行存储的技术,对象归档不适合于大量数据的频繁读写,我目前还没有弄清楚这种持久化方式适用的地方,但也对其使用方式进行整理。
使用对象归档的对象Note必须能够归档,所以,Note类必须实现NSCoding协议,相关代码如下:
<pre name="code" class="objc">#import <Foundation/Foundation.h>
//实现NSCoding协议
@interface Note : NSObject<NSCoding>
@property(nonatomic,strong) NSDate* date;
@property(nonatomic,strong) NSString* content;
@end
#import "Note.h"
@implementation Note
@synthesize date,content;
- (void)encodeWithCoder:(NSCoder *)aCoder{
//编码方法
[aCoder encodeObject:date forKey:@"date"];
[aCoder encodeObject:content forKey:@"content"];
}
-(id)initWithCoder:(NSCoder *)aDecoder{
//反编码方法
self.date = [aDecoder decodeObjectForKey:@"date"];
self.content = [aDecoder decodeObjectForKey:@"content"];
return self;
}
@end
<pre name="code" class="objc"><span style="font-family: Arial, Helvetica, sans-serif;">- (NSMutableArray*)findAll{</span>
//获取归档文件的详细路径
NSString* path = [self applicationDocumentsDirectoryFile];
//实例化可变数组
NSMutableArray* listData = [[NSMutableArray alloc] init];
//从归档文件中读取字节数据
NSData* theData = [NSData dataWithContentsOfFile:path];
if ([theData length] > 0) {
//实例化NSkeyedUnarchiver对象,并由该对象来反归档字节数据
NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:theData];
//反归档字节数组
listData = [unarchiver decodeObjectForKey:@"ARCHIVE_KEY"];
//完成反归档
[unarchiver finishDecoding];
}
return listData;
}
- (int)modify:(Note*)model{
//获取归档文件的详细路径
NSString* path = [self applicationDocumentsDirectoryFile];
NSMutableArray* array = [self findAll];
for (Note* note in array) {
if ([note.date isEqualToDate:model.date]) {
note.content = model.content;
//Data类封装了字节数据的缓存类,提供了读写数据的方法,这里实例化NSMutableData类
NSMutableData* theData = [NSMutableData data];
//实例化NSkeyedArchiver对象,并由该对象来将归档数据
NSKeyedArchiver* archive = [[NSKeyedArchiver alloc] initForWritingWithMutableData:theData];
//将Array对象编码到NSkeyedArchiver对象中
[archive encodeObject:array forKey:@"ARCHIVE_KEY"];
//完成编码
[archive finishEncoding];
//写入文件
[theData writeToFile:path atomically:YES];
return 0;
}
}
return -1;
}
三、SQLite数据库
有过其他程序语言基础的人应该对SQLite数据库并不陌生,SQLite是用户嵌入式系统使用的关系型数据库,其使用方式与其他关系型数据库相似,在使用SQLite数据库进行数据持久化时,必须先在工程中引入SQLite3库。方法是,选择工程中的TARGET-->工程名-->Build Phases -->Link Binary With Libraries,点击左下方的”+“,选择libsqlite3.dylib或者libsqlite3.0.dylib,并点击”Add“完成添加,如图所示:
完成sqlite3库的导入后,就可以使用sqlite数据库进行开发了,需要注意的是,SQLite3函数是纯C语言函数,虽然objective-C中可以直接调用C语言函数,但是必须要注意两种语言之间的数据类型的兼容性问题,如NSString就是objective-C的数据类型,必须要转换为C语言数据类型char才可以正常使用。使用过程中用到核心代码如下:
#pragma mark --数据持久化
- (NSString*)applicationDocumentsDirectoryFile{
//获取数据库文件的详细路径
NSString* documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString* path = [documentDirectory stringByAppendingString:@"/NoteList.sqlite3"];
return path;
}
- (void)createEditableCopyOfDatabaseIfNeeded{
//获取数据库文件的详细目录并转换为char数据类型
NSString* writableDBPath = [self applicationDocumentsDirectoryFile];
const char* cpath = [writableDBPath UTF8String];
//打开数据库,第一个参数为数据库文件完整路径,第二个sqlite3指针变量的地址
if (sqlite3_open(cpath, &db) == SQLITE_OK) {
NSLog(@"成功打开SQLite3");
char* err;
//使用sql语句创建Note表
NSString* sql = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS Note(cdate TEXT PRIMARY KEY,content TEXT);"];
const char* cSql = [sql UTF8String];
//使用sqlite3_exec执行sql语句,其中,第三个参数为会掉函数,第四个参数为回调函数的参数
if (sqlite3_exec(db, cSql, nil, nil, &err) != SQLITE_OK) {
sqlite3_close(db);
NSAssert(NO, @"建表失败");
}else{
NSLog(@"建表成功");
}
}else{
//如果打开数据库失败,也必须要将数据库关闭
NSAssert(NO, @"打开SQLite3失败");
sqlite3_close(db);
}
}
- (NSMutableArray*)findAll{
NSString* path = [self applicationDocumentsDirectoryFile];
const char* cPath = [path UTF8String];
NSMutableArray* listData = [[NSMutableArray alloc] init];
if (sqlite3_open(cPath, &db)) {
sqlite3_close(db);
NSAssert(NO, @"打开数据库失败");
}else{
NSString* sql = @"SELECT cdate,content FROM Note";
const char* cSql = [sql UTF8String];
//sqlite3_stmt是语句对象,通过它可以执行sql语句
sqlite3_stmt* statement;
//预处理过程,sqlite3_prepare_v2函数的第三个参数为sql语句的只付出长度,第五个参数为sql语句未执行的部分
if (sqlite3_prepare_v2(db, cSql, -1, &statement, nil) == SQLITE_OK) {
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
//使用sqlite3_step函数执行sql语句,当返回值为SQLITE_ROW时说明statement还有行没有被遍历
while (sqlite3_step(statement) == SQLITE_ROW) {
//取出该行的第一个数据并格式化为NSDate类型,数据的序号从0开始
char* bufDate = (char*) sqlite3_column_text(statement, 0);
NSString* strDate = [[NSString alloc] initWithUTF8String:bufDate];
NSDate* date = [dateFormatter dateFromString:strDate];
//取出该行的第二个数据并格式化为NSString类型
char* bufContent = (char*) sqlite3_column_text(statement, 1);
NSString* strContent = [[NSString alloc] initWithUTF8String:bufContent];
Note* note = [[Note alloc] initWithDate:date content:strContent];
[listData addObject:note];
}
}
//关闭语句对象及数据库
sqlite3_finalize(statement);
sqlite3_close(db);
}
return listData;
}
- (int)create:(Note*)model{
NSString* path = [self applicationDocumentsDirectoryFile];
const char* cPath = [path UTF8String];
if (sqlite3_open(cPath, &db) != SQLITE_OK) {
NSAssert(NO, @"打开数据库失败");
sqlite3_close(db);
}else{
NSString* sql = @"INSERT OR REPLACE INTO Note (cdate,content) VALUES (?,?)";
const char* cSql = [sql UTF8String];
sqlite3_stmt* statement;
if (sqlite3_prepare_v2(db, cSql, -1, &statement, NULL) == SQLITE_OK) {
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSString* strDate = [dateFormatter stringFromDate:model.date];
const char* cDate = [strDate UTF8String];
const char* cContent = [model.content UTF8String];
//调用sqlite3_bind_text函数绑定sql语句的参数,第2个参数为序号,从1开始,第4个参数为字符串长度,第5个参数为函数指针
sqlite3_bind_text(statement, 1, cDate, -1, NULL);
sqlite3_bind_text(statement, 2, cContent, -1, NULL);
//调用sqlite3_step函数执行sql语句,若返回值为SQLITE_DONE则表示执行成功
if (sqlite3_step(statement) == SQLITE_DONE) {
NSLog(@"插入数据成功");
sqlite3_finalize(statement);
sqlite3_close(db);
return 1;
}else{
NSLog(@"插入数据失败");
}
}
sqlite3_finalize(statement);
sqlite3_close(db);
}
return 0;
}
使用SQLite是比较高效、便捷的数据持久化方式,但是这样直接调用sqlite3的函数来进行数据持久化,会涉及到许多的sql语句以及objective-C和C语言之间类型转换的问题。