iOS数据存储
iOS数据存储方式主要有NSUserDefaults、plist文件、归档、SQLite、CoreData等,不管采用何种存储方式都可以用下图来解释。
下面将主要介绍各种存储方式的使用场景和存储方法。
1、 NSUserDefaults
NSUserDefaults为应用偏好设置,是专门用来保存应用程序的配置信息,适合存储轻量级的本地数据。NSUserDefaults本质上一个plist文件,该文件存在在应用沙盒的Library/Preferences目录下。
由于plist文件只支持存储NSNumber(Integer、Float、Double)、NSString、NSData、NSArray、NSDictionary、BOOL等类型(注意:集合类型中不能存放除上述类型之外的自定义类型)。
NSUserDefaults简单易用,但必须注意:使用系统偏好设置对数据保存之后,它保存到系统的时间是不确定的(由于NSUserDefaults的所有数据都放在内存中,因此操作速度很快),会在将来某个时间点自动将数据保存到Preferences文件下面,如果需要立即将数据存储,可以使用 synchronize方法。
案例:
-(void) preferences
{
//1.获取实例对象
NSUserDefaults * defaults=[NSUserDefaultsstandardUserDefaults];
[defaults setInteger:12forKey:@"age"];
[defaults setObject:@"ssl"forKey:@"name"];
NSArray * arr=@[@"勤奋",@"勇敢"];
[defaults setObject:arr forKey:@"personality"];
//2.即时保存数据
[defaults synchronize];
}
2、 plist文件
plist文件为iOS属性文件,通常用来存储用户设置,也可以用于存储捆绑的信息。
plist是明文存储的,而且该文件支持存储NSNumber(Integer、Float、Double)、NSString、NSData、NSArray、NSDictionary、BOOL等类型(注意:集合类型中不能存放除上述类型之外的自定义类型)。
案例:
-(void) plist
{
//数据文件路径
NSURL * url=[[NSBundlemainBundle] URLForResource:@"user.plist"withExtension:nil];
NSDictionary * dict=[NSDictionarydictionaryWithContentsOfURL:url];
NSString * name=[dict valueForKey:@"name"];
NSLog(@"name=%@",name);
}
注:NSBundle
bundle是一个目录,其中包含了程序会使用到的资源,这些资源包含了如图像、音频等,对应bundle,cocoa提供了NSBundle类,方便对资源的管理和操作。
我们的应用程序是一个bundle(applicationName.app),里面包含了各种资源文件,我们把这个目录叫做应用程序的mainbundle。
bundle里面的资源,只读,而不能写。
3、 NskeyedArchiver
归档,在其他语言中又叫“序列化”,就是将对象保存到硬盘;解档,在其他语言又叫做“反序列化”就是将硬盘文件还原成对象。
如果要针对更多对象归档或者需要归档时能够加密的话就需要NSKeyedArchiver对数据进行归档和解档,该方式归档的范围更广,而且归档内容是密文存储。
从归档范围来讲NSKeyedArchiver适合所有OC对象。对于自定义的对象,需要实现NSCoding协议;从归档方式来讲NSKeyedArchiver分为简单归档和复杂对象归档,简单对象归档就是针对单个对象可以直接将该对象作为根对象(不用设置key),复杂对象就是针对多个对象,存储时不同对象需要指定不同的Key。
3.1、简单对象归档
-(void) simple
{
NSString * docPath=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)[0];
NSString * file=[docPath stringByAppendingPathComponent:@"my.data"];
NSString * text=@"Hello world";
if ([NSKeyedArchiverarchiveRootObject:text toFile:file])
{
NSLog(@"归档成功");
NSString * str=[NSKeyedUnarchiverunarchiveObjectWithFile:file];
NSLog(@"%@",str);
}
}
3.2、多个对象归档
-(void) complex
{
NSString * docPath=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)[0];
NSString * file=[docPath stringByAppendingPathComponent:@"my.data"];
NSString * text=@"Hello world";
NSDictionary * dict=@{@"name":@"ssl",@"age":@26};
NSArray * arr=@[@"ssl",@"lff"];
//临时存放数据
NSMutableData * data=[NSMutableDatadata];
NSKeyedArchiver * archiver=[[NSKeyedArchiveralloc] initForWritingWithMutableData:data];
[archiver encodeObject:textforKey:@"text"];
[archiver encodeObject:dictforKey:@"dict"];
[archiver encodeObject:arrforKey:@"arr"];
//结束归档
[archiver finishEncoding];
//写入文件
if ([data writeToFile:file atomically:YES])
{
NSLog(@"归档成功");
NSData * data=[NSDatadataWithContentsOfFile:file];
NSKeyedUnarchiver * unarchiver=[[NSKeyedUnarchiveralloc] initForReadingWithData:data];
NSString * textU=[unarchiverdecodeObjectForKey:@"text"];
NSDictionary * dictU=[unarchiverdecodeObjectForKey:@"dict"];
NSArray *arrU=[unarchiverdecodeObjectForKey:@"arr"];
[unarchiver finishDecoding];
NSLog(@"%@",textU);
NSLog(@"%@",dictU);
NSLog(@"%@",arrU);
}
}
3.3、自定义对象归档
// 模型数据
#import <Foundation/Foundation.h>
@interface User : NSObject<NSCoding>
@property(nonatomic,copy) NSString * name;
@property(nonatomic,assign)NSUInteger age;
@end
@implementation User
-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
NSLog(@"decode...");
self=[superinit];
if (self)
{
self.name=[aDecoderdecodeObjectForKey:@"name"];
self.age=[[aDecoderdecodeObjectForKey:@"age"]unsignedIntegerValue];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
NSLog(@"encoding...");
[aCoder encodeObject:self.nameforKey:@"name"];
[aCoder encodeObject:@(self.age)forKey:@"age"];
}
@end
// 测试程序
-(void) custom
{
User * user=[[Useralloc] init];
user.name=@"ssl";
user.age=26;
NSString * docPath=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)[0];
NSString * file=[docPath stringByAppendingPathComponent:@"my.data"];
if ([NSKeyedArchiverarchiveRootObject:user toFile:file])
{
NSLog(@"归档成功...");
User * userU=[NSKeyedUnarchiverunarchiveObjectWithFile:file];
NSLog(@"%@",userU.name);
}
}
3.4、自定义关联对象归档
对于自定义对象,首先要理解对象图的概念,对象图就是对象之间经由指针等关系链接在一起形成的对象之间的关系图。
// 数据模型——Address
#import <Foundation/Foundation.h>
@interface Address : NSObject<NSCoding>
@property(nonatomic,copy)NSString *city;
@end
@implementation Address
-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
NSLog(@"address decoding...");
self=[superinit];
if (self)
{
self.city=[aDecoderdecodeObjectForKey:@"city"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
NSLog(@"address encoding...");
[aCoder encodeObject:self.cityforKey:@"city"];
}
@end
// 数据模型——User
#import <Foundation/Foundation.h>
@class Address;
@interface User : NSObject<NSCoding>
@property(nonatomic,copy) NSString * name;
@property(nonatomic,assign)NSUInteger age;
@property(nonatomic,strong)Address *address;
@end
-(instancetype) initWithCoder:(NSCoder *)aDecoder
{
NSLog(@"decode...");
self=[superinit];
if (self)
{
self.name=[aDecoderdecodeObjectForKey:@"name"];
self.age=[[aDecoderdecodeObjectForKey:@"age"]unsignedIntegerValue];
self.address=[aDecoderdecodeObjectForKey:@"address"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
NSLog(@"encoding...");
[aCoder encodeObject:self.nameforKey:@"name"];
[aCoder encodeObject:@(self.age)forKey:@"age"];
[aCoder encodeObject:self.addressforKey:@"address"];
}
@end
// 测试程序
-(void) custom
{
Address * address=[[Addressalloc] init];
address.city=@"shanghai";
User * user=[[Useralloc] init];
user.name=@"ssl";
user.age=26;
user.address=address;
NSString * docPath=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)[0];
NSString * file=[docPath stringByAppendingPathComponent:@"my.data"];
if ([NSKeyedArchiverarchiveRootObject:user toFile:file])
{
NSLog(@"归档成功...");
User * userU=[NSKeyedUnarchiverunarchiveObjectWithFile:file];
NSLog(@"%@",userU.name);
NSLog(@"%@",userU.address.city);
}
}
4、 SQLite3
数据库是按照数据结构来组织、存储和管理数据的仓库。SQLite3是一款开源的嵌入式\移动端的关系型数据库,可移植性好、易使用、内存开销小。SQLite3的SQL语句与传统的SQL语句一致,具有较好实用性。
4.1、字段类型
SQLite将数据值的存储划分为以下几种存储类型:
1) NULL:表示该值为空值;
2) INTEGER:无符号整型值;
3) REAL:浮点值;
4) TEXT:文本字符串;
5) BLOB:二进制数据(图片等);
实际上,SQLite是无类型的,建表时声明不声明都可以。但为了保持良好的编程规范,方便程序员之间的交流,建表时最好加上每个字段的类型。
4.2、约束条件
SQLite为关系型数据库,自然存在约束条件,如主键约束(primarykey)、外键约束(references)、非空约束(notnull)、唯一性约束(unique)、自增(autoincrement)等,如下
CREATE TABLEt_student (id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL UNIQUE,age integerNOTNULL DEFAULT 1);
4.3、iOS应用
在iOS中要使用SQLite3,需要添加库文件sqlite3.dylib,并导入主头文件,该库文件为C语言库。
4.3.1、创建数据库
在使用sqlite3时,首先需要创建数据库,其创建方式如下:
/*
mainBundle中的资源是只读的,所以不可将已创建好的sqlite数据文件放在mainBunle中;
为了支持数据文件CRUD操作,需要在沙河中主动创建sqlite数据文件
*/
-(void) openDB
{
//1.沙河路径路径
NSString * docPath=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES)[0];
//2.数据文件路径
NSString * dataPath=[docPathstringByAppendingPathComponent:@"sqlite.db"];
//3.打开数据库,如果数据库不存在,则新建数据库;
if (SQLITE_OK==sqlite3_open(dataPath.UTF8String,&pDB))
{
NSLog(@"数据库连接成功");
}
else
{
NSLog(@"数据库连接失败");
}
}
4.3.2、单步执行
在SQLite3中,单步执行的步骤为:
A. 创建将要执行的SQL语句;
B. 调用sqlite3_exec执行SQL语句;
在SQLite3中,单步执行的操作主要包括
1) 创建数据库表;
/*
创建数据库表
*/
-(void) createTable
{
//1.sql语句
NSString * sql=@"CREATE TABLE IF NOT EXISTS t_person(id integer PRIMARY KEYAUTOINCREMENT,name text,age interger)";
char * errorMsg;
if (SQLITE_OK==sqlite3_exec(pDB, sql.UTF8String,NULL, NULL,&errorMsg))
{
NSLog(@"数据库表创建成功");
}
else
{
NSLog(@"数据库表创建失败,%s",errorMsg);
}
}
2) 插入数据;
*
插入
*/
-(void) insert
{
//1. sql
NSString * sql=@"INSERT INTO t_person (name,age)VALUES ('ssl',26)";
char * errrorMsg;
if(SQLITE_OK==sqlite3_exec(pDB, sql.UTF8String,NULL, NULL, &errrorMsg))
{
NSLog(@"插入成功");
}
else
{
NSLog(@"插入失败,%s",errrorMsg);
}
}
3) 更新数据;
4) 删除数据;
4.3.3、查询操作
1) sqlite3_prepare_v2检查sql的合法性;
2) sqlite3_setp逐行获取查询结果;
3) sqlite3_coloum_xxx获取对应类型的数据;
4) sqlite3_finalize释放stmt;
/*
查询
*/
-(void) query
{
NSString * sql=@"select * from t_person";
//1.评估sql语句是否正确
sqlite3_stmt *stmt=NULL;
//2.如果能够正常查询,调用单步执行方法,获取执行结果;
if (SQLITE_OK==sqlite3_prepare_v2(pDB, sql.UTF8String, -1, &stmt, NULL))
{
while (SQLITE_ROW==sqlite3_step(stmt))
{
//获取查询结果
const unsigned char * name=sqlite3_column_text(stmt,1);
int age=sqlite3_column_int(stmt,2);
NSLog(@"name=%s,age=%d",name,age);
}
}
else
{
NSLog(@"sql语法错误");
}
//3.释放句柄
sqlite3_finalize(stmt);
}
4.3.4、其他
SQLite3的SQL和传统的SQL一致,所以其他操作也与传统的数据库操作类似,在此不再详述。
5、 CoreData
CoreData是iOSSDK中一个强大的框架,允许程序员以面向对象的方式存储和管理数据。使用CoreData框架,程序员可以很轻松有效地通过面向对象的接口管理数据。
CoreData框架提供了对象-关系映射(ORM)的功能,即能够将OC对象转换成数据,保存在SQLite3或其他数据文件中,也能够将保存在数据文件中的数据还原为OC对象。
5.1、模型文件和实体
在使用CoreData之前,首先需要定义模型文件,并在模型文件中描述程序中的实体。
所谓实体,就是跟数据库进行映射的对象。
1) 建立模型文件
点击“NewFile”,在对话框选择“CoreData”,之后选择“DataModel”即可创建模型文件;
2) 建立实体
选择上述步骤建立的模型文件(以xcdatamodeld结尾的文件),在模型文件中可以添加实体,以及实体之间的关系。
注意:在Core Data中,不需要为实体,建立主键,只需要创建所需要的属性即可。
5.2、实体类
在模型文件中,我们只是简单了描述了实体以及实体之间的关系。除此之外,我们还需要建立实体类,那么如何建立实体类呢?
点击“NewFile”,在对话框选择“CoreData”,之后选择“NSManagedObjectsubclass”,即可选择创建相应的实体类。创建的实体类,已生成实体的属性和实体之间的关系。
NSManagedObject子类的实体实际上对应数据库中的一条记录,如下图所示
5.3、Core Data类关系图
1) NSManagedObjectContext:负责应用和数据文件之间的交互,也即CRUD操作;
2) NSPersistentStroreCoordinator:添加持久化存储库(如SQLite数据库),是物理数据文件和程序之间的联系的桥梁,负责管理不同的上下文;
3) NSManagedObjectModel:被管理的对象模型,对应定义的模型文件;
4) NSEntityDesription:实体描述;
5.4、应用
5.4.1、打开数据库
/*
打开数据库
1)sqllite方式
a)第一次运行时,在沙盒中创建数据库;
b)打开数据库后,获取数据库句柄,后续的操作都是基于该句柄;
c)创建数据文件;
d)CRUD操作等;
2)coredata方式
a)加载数据模型;
b)以数据模型实例化持久化调度器;
c)为持久化调度器添加数据文件;如果没有,新建并创建数据库表;如果已经存在,直接打开数据库;
d)打开数据库之后,会判断实体当前的结构与数据表的描述结构是否一致,如果不一致,则打开失败;
*/
-(void) openDB
{
//1.实例化(所有)数据模型,也即加载数据模型
NSManagedObjectModel * model=[NSManagedObjectModelmergedModelFromBundles:nil];
//2.实例化持久化存储调度器(model和数据文件之间的桥梁)
NSPersistentStoreCoordinator * storeCoordinator=[[NSPersistentStoreCoordinatoralloc] initWithManagedObjectModel:model];
//3.建立持久化数据文件
NSString * docPath=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)[0];
NSString * dbPath=[docPathstringByAppendingPathComponent:@"coredata.db"];
NSURL *dbUrl=[NSURLfileURLWithPath:dbPath];
NSError *error=nil;
//NSLog(@"%@",dbPath);
//打开或新建数据库文件,如果文件不存在,则新建之后打开
[storeCoordinator addPersistentStoreWithType:NSSQLiteStoreTypeconfiguration:nilURL:dbUrl options:nilerror:&error];
if (error==nil)
{
NSLog(@"数据库打开成功");
}
else
{
NSLog(@"数据库打开失败,%@",error);
}
//4.保存模型数据上下文,之后数据库的CRUD操作,通过上下文实现
_context=[[NSManagedObjectContextalloc] init];
_context.persistentStoreCoordinator=storeCoordinator;
}
5.4.2、新增记录
-(void) addData
{
//1.实例化并让context准备将一条记录增加到数据库
Person * p=[NSEntityDescriptioninsertNewObjectForEntityForName:@"Person"inManagedObjectContext:_context];
//2.设置个人信息
p.name=@"ssl";
p.age=@26;
p.address=@"shanghai";
Book * book=[NSEntityDescriptioninsertNewObjectForEntityForName:@"Book"inManagedObjectContext:_context];
book.name=@"Java";
book.price=@17.6;
p.books=[[NSSetalloc] initWithObjects:book,nil];
//3.保存
if ([_contextsave:nil])
{
NSLog(@"保存成功");
}
else
{
NSLog(@"保存失败");
}
}
5.4.3、查询
-(void) query
{
//1.查询请求
NSFetchRequest * request=[[NSFetchRequestalloc] initWithEntityName:@"Person"];
//2.条件查询,需要通过谓词来实现
request.predicate=[NSPredicatepredicateWithFormat:@"age > 16"];
//在CoreData中,关联对象,采用的是懒加载机制;并且不需要join操作,查询属性采用键值对的方式。
//3.执行查询
NSArray * array=[_contextexecuteFetchRequest:request error:nil];
for (Person * pin array)
{
NSLog(@"name=%@,age=%@,address=%@",p.name,p.age,p.address);
}
}
5.4.4、更新
/*
更新数据
1)在常规开发中,应该首先加载所有的数据,如果是这种情况,在修改个人记录时,是无需再次查询数据库;
2)在实际开发中,通常是从表格选中某一行,获取到对应的NSManagedObject,然后进行修改,如此,便可以只修改唯一一条记录;
*/
-(void) update
{
//1. 查询请求
NSFetchRequest * request=[NSFetchRequestfetchRequestWithEntityName:@"Person"];
//2.设置查询条件
request.predicate=[NSPredicatepredicateWithFormat:@"name='ssl'"];
//3.由上下文查询数据
NSArray * arr=[_contextexecuteFetchRequest:request error:nil];
for (Person * pin arr)
{
NSLog(@"name=%@,age=%@,addrss=%@",p.name,p.age,p.address);
p.name=@"lff";
}
[_context save:nil];
}
5.4.5、删除
/*
无论是新增,修改,还是删除都是一次性保存数据
*/
-(void) remove
{
//1. 查询请求
NSFetchRequest * request=[NSFetchRequestfetchRequestWithEntityName:@"Person"];
//2.设置查询条件
request.predicate=[NSPredicatepredicateWithFormat:@"name='ssl'"];
//3.由上下文查询数据
NSArray * arr=[_contextexecuteFetchRequest:request error:nil];
for (Person * pin arr)
{
NSLog(@"name=%@,age=%@,addrss=%@",p.name,p.age,p.address);
[_context deleteObject:p];
break;
}
[_context save:nil];
}