分类
简单来说,IOS的数据持久化有四种方式:
- 属性列表
- 对象归档
- iOS的嵌入式关系数据库SQLite3
- 苹果公司提供的持久化工具Core Data
下面以带有四个TextLabel的界面为例,这个界面的功能是用户在TextLabel中输入值,系统将输入的内容的保存起来,再次启动程序的时候,TextLabel默认显示的仍然是用户之前输入的值。分别用IOS数据持久化的四种方式来实现上述功能。
完成这个例子需要三步——
- 保存label中的值就是写文件
- label显示默认值就是读文件;
- 选择相应的持久化方式,依靠这种方式进行读写。
属性列表
//1.读
-(void)viewDidLoad{
//通过NSFileManager来检查数据是否存在
NSString *filePath = [self dataFilePath]
if( [[NSFileManager defaultManager] fileExistsAtPath:filePath] ){
//文件存在 do something
//这就把文件读出来了,读的方法是,声明一个与文件内容相同类型的变量,取出即可
NSArray *array = [[NSArray alloc]initWithContentOfFile: filePath];
//赋值到需要的地方
UILabel *lable = self.textLabel;
label.text = array[0];
/*
NOTE :
self.textLabel为该页面的UI变量,最后的目的是要把咋文件读出来的值显示在这里
label先把指向self.textLabel的指针取到,然后由label来取值
***为何不直接 self.textLabel.text = array[0]; 呢??
*/
}
//下面是与归档编解码无关的部分,监听self,如果发生应用终止运行则及时保存数据
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification
object:app];
}
2.写文件
-(void)applicationWillResignActive:(NSNotification *)notification{
NSString *filePath = [self dataFilePath];
NSArray *array = @[@"a",@"b",@"c",@"d"];//=[self.lineFields valueFoeKey:@"text"];
[array writeToFile:filePath atomically:YES];
}
//这个函数名字和形式比较特殊,只把它当做一个滴啊有特殊功能的函数就好
//这个方法应是作为observer的一个selector,作用是:使应用在进入后台或者终止运行时保存数据,只要应用不在是前正在与用户交互的应用就会发布通知,例如不小心按了home键或者突然有电话打进来
//获取Document文件路径
-(NSString)dataFilePath{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentDirectory = paths[0];
return [documentDirectory stringByAppendingPathComponent:@"data.plist"];//指定文件
//读取tmp目录
//NSString *tmpPath = NSTemporaryDirectory();
//NSString *tempFile = [tempPath stringByAppendingPathComponent:@"data.plist"];//体现出用的是“属性列表”的方式
}
对象归档
#import
//1.读就是解码的过程
-(void)viewDidLoad{
NSString *filePath = [self dataFilePath]
if( [[NSFileManager defaultManager] fileExistsAtPath:filePath] ){
//解码通道
NSData *data = [[NSMutableData alloc] initWithContentOfFile:filePath];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData];
//根据编码时设定的key找到要解码的对象
MyObject *myObject = [unarchiver decodeObjectForKey:myObjectKey];
//
[unarchiver finishDecoding];
//READ
UILabel *label.text = myObject[ObjectAtIndex:somenumber];
}
//下面是与归档编解码无关的部分,监听self,如果发生应用终止运行则及时保存数据
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification
object:app];
}
2.写就是编码的过程
-(void)applicationWillResignActive:(NSNotification *)notification{
//文件写入的地点,要有
NSString *filePath = [self dataFilePath];
//要保存的对象
MyObject *myObject = [[MyObject alloc]init];
myObject addObject:UILabel.text;//进行一些赋值操作,这里label没有正常声明
//打开“通道”(不知道这样形容合适不),必须要做的两个步骤
NSMutableData *data = [[NSMutableData alloc]init];
NSKeyedArchiver *archiver = [[NSKeyArchiver alloc]initFoeWritingWithMutableData:data];
//建立键值关系,解码时通过该key去找应该解码的对象
[archiver encodeObject:myObject forKey:myObjectKey];
//
[archiver finishEncoding];
//WRITE
[data writeToFile :filePath atomically:YES];
}
//获取文件路径
-(NSString)dataFilePath{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentDirectory = paths[0];
return [documentDirectory stringByAppendingPathComponent:@"data.plist"];//指定文件
return [documentDirectory stringByAppendingPathComponent:@"data.archive"]//体现出用的“归档”的方式
}
SQLite3
/*
NOTE:
1.SQL语句通过*char 传递而非NSString 两者的相互转换方式为:
const char *stringPath = [pathString UTF8String];
2.C语言小知识:如果两个内联字符串之间只有空白(或者换行符),而没有自他字符,则两个字符串将会被连接为一个字符串
*/
//基础使用
//1.声明和创建
sqlite3 *database;
int result = sqlite3_open("/path/to/database/file",&database);//result = SQLITE_OK if open database successfully
//2.执行语句
//2-1用于updata insert delete等不反回任何数据的命令 —— sqlite3_exce
char *errorMsg;
const char *createSQL = "CREATE TABLE IF NOT EXISTS PEOPLE" "(ID INTEGER PROMARY KEY AUTOINCREMENT ,FIELD_DATA TEXT)";
int reslut = sqlite3_exec(database,createSQL,NULL,NULL,&errorMsg);//errorMsg将对错误信息进行描述
//2-2用于query检索的命令 —— sqlite3_prepare_v2
NSString *query = @"SELECT ID , FIELD_DATA FROM FIELDS ORDER BY ROW";
sqlite3_stmt *statement;
int result = sqlite3_prepare_v2(database,[query UTF8String],-1,$statement,nil);
//3.取出数据
//4.使用绑定变量出入数据
char *sql = "insert into foo values (?,?); ";
sqlite3_stmt *stmt;
if(sqlite3_prepare_v2(database, sql,-1,&stmt,nil) == SQLITE_OK){
sqlite3_bind_int(stmt, 1, 235);
sqlite3_bind_text(stmt,2,"Text",-1,NULL);
//("prepare语句的返回值" , "替代问号的索引" , "问号的替代内容" , "上一个参数的传入长度:-1代表字符串长度","可选用的内存清理方式")
}
if(sqlite3_step(stmt)!= SQLITE_DONE){
NSLog(@"Real Error");
}
sqlite3_finalize(stmt);
程序中的读、写法
#import
1.读就是query检所操作
-(void)viewDidLoad{
}
2.写就是更新、插入、删除操作
//获取文件路径
-(NSString)dataFilePath{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentDirectory = paths[0];
return [documentDirectory stringByAppendingPathComponent:@"data.plist"];//指定文件
return [documentDirectory stringByAppendingPathComponent:@"data.archive"]//体现出用的“归档”的方式
return [documentDirectory stringByAppendingPathComponent:@"data.sqlite"]//体现出用SQLite3的方式
}
CoreData
概念
Core Data是水果推荐开发者使用的一种数据化持久方法,为了能够更加准确地理解,也是翻了不少前辈贡献的资料,着实收货很大
CoreData中的核心对象:
注:黑色表示类名,红色表示类里面的一个属性
1, Managed Object Model
Managed Object Model 是描述应用程序的数据模型,这个模型包含实体(Entity),特性(Property),读取请求(Fetch Request)等。(下文都使用英文术语。)
2,Managed Object Context
Managed Object Context 参与对数据对象(Managed Object)进行各种操作的全过程,并监测数据对象的变化,以提供对 undo/redo 的支持及更新绑定到数据的 UI。
3,Persistent Store Coordinator
Persistent Store Coordinator 相当于数据文件管理器,处理底层的对数据文件的读取与写入。一般我们无需与它打交道。在context的操作下,负责在数据库中生成取出managed object,或者将managed Object存到数据库
4,Managed Object
Managed Object 数据对象,与 Managed Object Context 相关联。
关于Model
模型有点像数据库的表结构,里面包含 Entry, 实体又包含三种 Property:Attribute(属性),RelationShip(关系), Fetched Property(读取属性)。Model class 的名字多以 "Description" 结尾。我们可以看出:模型就是描述数据类型以及其关系的。
主要的 Model class 有:
First Header | Second Header | Third Header |
---|---|---|
Managed Object Model | NSManagedObjectModel | 数据模型 |
Entity | NSEntityDescription | 抽象数据类型,相当于数据库中的表 |
Property | NSPropertyDescription | Entity 特性,相当于数据库表中的一列 |
Attribute | NSAttributeDescription | 基本数值型属性(如Int16, BOOL, Date等类型的属性) |
Relationship | NSRelationshipDescription | 属性之间的关系 |
Fetched Property | NSFetchedPropertyDescription | 查询属性(相当于数据库中的查询语句) |
和数据库的对应关系
Manged Object <------> Database
Entity <-------> Table
Property <-------> 列 : 包含 Attribute、Relationship、Fetched Property
开发步骤总结:
- 初始化NSManagedObjectModel对象,加载模型文件,读取app中的所有实体信息
- 初始化NSPersistentStoreCoordinator对象,添加持久化库(这里采取SQLite数据库)
- 初始化NSManagedObjectContext对象,拿到这个上下文对象操作实体,进行CRUD操作
这几个对象用通俗的话这样理解:(并且按照下面的顺序理解)
- NSManagedObjectModel 确定和哪个应用连接,就是说,这个Core Data是针对哪个app设计的
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"NewsModel" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
2 . NSPersistentStoreCoordinator
字面意就是持久化存储器,所以,这个对象作用就是定位数据库,就是说,我这个应用用的是哪个数据库,But~~还要指明是哪个应用对吧,哪个应用?这在上面的self.managedObjectModel
已经确定了,所以把managedObjectModel
引入就可以了。
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"NewsModel.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
3 . NSManagedObjectContext
程序中负责操作数据的接口提供者,就是说,用这个对象的实例去CRUD,这之前要指明该对象的实例用的是哪个持久化存储器persistentStore
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
增删查改
//插入数据
- (void)insertCoreData:(NSMutableArray*)dataArray
{
NSManagedObjectContext *context = [self managedObjectContext]; //都要声明这个负责上下文连接的实例
for (News *info in dataArray) {
News *newsInfo = [NSEntityDescription insertNewObjectForEntityForName:TableName inManagedObjectContext:context];
newsInfo.newsid = info.newsid;
newsInfo.title = info.title;
newsInfo.imgurl = info.imgurl;
newsInfo.descr = info.descr;
newsInfo.islook = info.islook;
NSError *error;
if(![context save:&error])
{
NSLog(@"不能保存:%@",[error localizedDescription]);
}
}
}
//查询
- (NSMutableArray*)selectData:(int)pageSize andOffset:(int)currentPage
{
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:TableName inManagedObjectContext:context];
//setFetchLimit
//setFetchOffset
//查找指定日期之后创建的
NSPredicate * predicate;
predicate = [NSPredicate predicateWithFormat:@"creationDate > %@", date];
//将结果按照title排序
NSSortDescriptor * sort = [[NSortDescriptor alloc] initWithKey:@"title"];
NSArray * sortDescriptors = [NSArray arrayWithObject: sort];
[fetchRequest setEntity:entity];
[fetchRequest setFetchLimit:pageSize]; // 限定查询结果的数量
[fetchRequest setFetchOffset:currentPage]; // 查询的偏移量
[fetchRequest setPredicted:predicate]; // 设置查询条件
[fetchRequest setSortDescriptors:sort]; // 设置查询结果的排序方法
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
NSMutableArray *resultArray = [NSMutableArray array];
for (News *info in fetchedObjects) {
NSLog(@"id:%@", info.newsid);
NSLog(@"title:%@", info.title);
[resultArray addObject:info];
}
return resultArray;
}
//删除
-(void)deleteData
{
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:TableName inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setIncludesPropertyValues:NO];
[request setEntity:entity];
NSError *error = nil;
NSArray *datas = [context executeFetchRequest:request error:&error];
if (!error && datas && [datas count])
{
for (NSManagedObject *obj in datas)
{
[context deleteObject:obj];
}
if (![context save:&error])
{
NSLog(@"error:%@",error);
}
}
}
//更新
- (void)updateData:(NSString*)newsId withIsLook:(NSString*)islook
{
NSManagedObjectContext *context = [self managedObjectContext];
NSPredicate *predicate = [NSPredicate
predicateWithFormat:@"newsid like[cd] %@",newsId];
//首先你需要建立一个request
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:TableName inManagedObjectContext:context]];
[request setPredicate:predicate];//这里相当于sqlite中的查询条件,具体格式参考苹果文档
//https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Predicates/Articles/pCreating.html
NSError *error = nil;
NSArray *result = [context executeFetchRequest:request error:&error];//这里获取到的是一个数组,你需要取出你要更新的那个obj
for (News *info in result) {
info.islook = islook;
}
//保存
if ([context save:&error]) {
//更新成功
NSLog(@"更新成功");
}
最后
关于几个持久化方法,有几个点需要注意一下:
- 使用 属性列表 和 归档 还需要考虑将数据存在一个文件还是多个文件中,即单文件持久化(多用)、多文件持久化;
- 在写文件是用到[myArray writeToFile:filePath atomically:YES] 第二个参数是叫数据写入辅助文件,写完后再将辅助文件复制到第一个参数指明的位置,保障写安全性;
- 采用属性列表法无法序列化自定义对象,且无法序列化NSURL,UIImage,UIColor等类;
- 归档能够大多数类进行编解码来实现数据持久化,只要类中的每个属性都是标量或者遵循NSCoding某各类的实例,都可以归档;
done!