一、数据持久化概述
数据持久化就是数据的永久存储。其本质是将数据保存为文件,存到程序的沙盒中。
1、数据持久化的方式
1.1 writeToFile:简单对象写入文件
1.2 NSUserDefaults:应用程序偏好设置
1.3 Sqlite:轻量级关系型数据库,不能直接存储对象(NSData除外),需要用到一些SQL语句,先将复杂对象归档(对象->NSData)
1.4 CoreData:对象型数据库,实质是将数据库的内部存储细节封装
1.5 Plist文件
2、应用程序沙盒
每一应用程序都有自己的应用沙盒,沙盒的本质就是一个文件夹,名字是随机分配的。
与其他应用程序沙盒隔离,应用程序本身只能访问自己沙盒的数据。(iOS8+对沙盒之间的访问部分开放)
2.1应用程序包(.app)
包含了应用程序中所用到的所有资源文件和可执行文件(Base on Unix)。iOS8时,app不存储在沙盒中,有单独的文件夹存储所有程序的app包。
2.2 HomeDirectory
Documents:保存应用运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录。例如,游戏应用可将游戏存档保存在该目录
tmp:保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录
Library/Caches:保存应用运行时生成的需要持久化的数据,iTunes同步设备时不会备份该目录。一般存储体积大、不需要备份的非重要数据
Library/Preference:保存应用的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时会备份该目录
2.3 获取沙盒路径
沙盒根目录:NSHomeDirectory();
沙盒临时目录:NSTemporaryDirectory();
Library/Preferences:NSUserDefaults
//1.获取沙盒中Documents文件夹的路径
//第一种方式:
NSString*documentPath =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES).lastObject;//NO, path = @"~/"(相对路径); YES 绝对路径
NSLog(@"%@", documentPath);
//第二种方式: (不建议采用,因为新版本的操作系统可能会修改目录名)
NSString*homePath =NSHomeDirectory();
NSString*documentPath2 = [homePathstringByAppendingPathComponent:@"library/caches"];
NSLog(@"%@", documentPath2);
//2.获取应用程序包路径(.app)
NSLog(@"%@", [NSBundlemainBundle].resourcePath);
二、简单对象持久化
1、简单对象
NSString\NSArray\NSDictionary\NSData
使用writeToFile:方法,将数据存储为.plist文件
atomically参数为是否写入缓存
//字符串
NSString*string =@"I?U";
//数组
NSArray*array =@[@"张三",@"李四",@"王五"];
//字典
NSDictionary*dictionary =@{@"name":@"张三",@"age":@"20",@"sex":@"男"};
//NSData
UIImage*image = [UIImageimageNamed:@"1.jpg"];
NSData*data =http://my.oschina.net/zooyf/blog/UIImageJPEGRepresentation(image,1);
//1.拼接存储路径
NSString*strPath = [documentPathstringByAppendingPathComponent:@"string.txt"];
NSString*arrayPath = [documentPathstringByAppendingPathComponent:@"array.txt"];
NSString*dicPath = [documentPathstringByAppendingPathComponent:@"dict.txt"];
NSString*dataPath = [documentPathstringByAppendingPathComponent:@"data.txt"];
//2.写入文件
[stringwriteToFile:strPathatomically:YESencoding:NSUTF8StringEncodingerror:nil];
[arraywriteToFile:arrayPathatomically:YES];
[dictionarywriteToFile:dicPathatomically:YES];
[datawriteToFile:dataPathatomically:YES];
//3.读取文件内容
NSString*fileString = [NSStringstringWithContentsOfFile:strPathencoding:NSUTF8StringEncodingerror:nil];
NSArray*fileArray = [NSArrayarrayWithContentsOfFile:arrayPath];
NSDictionary*fileDict = [NSDictionarydictionaryWithContentsOfFile:dicPath];
NSData*fileData = http://my.oschina.net/zooyf/blog/[NSDatadataWithContentsOfFile:dataPath];
NSLog(@"%@", fileString);
NSLog(@"%@", fileArray);
NSLog(@"%@", fileDict);
NSLog(@"%@", fileData);
2、文件管理类:NSFileManager
2.1、功能
NSFileManager使用defaultManager创建单例对象。可以创建文件夹,可以删除、移动、创建文件,判断文件是否存在。
2.2、使用
//缓存文件夹所在路径
NSString*cachesPath =NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES).lastObject;
NSLog(@"%@", cachesPath);
//在cachesPath路径下创建一个文件夹
NSString*directoryPath = [cachesPathstringByAppendingPathComponent:@"path"];
NSFileManager*fileManager = [NSFileManagerdefaultManager];//创建文件管理类单例对象
//根据路径创建文件夹
NSDictionary*fileDate =@{@"createTime":@"2015-9-9"};
[fileManagercreateDirectoryAtPath:directoryPathwithIntermediateDirectories:YESattributes:fileDateerror:nil];
//根据路径创建文件(只能写入NSData类型的数据)
[fileManagercreateFileAtPath:directoryPathcontents:dataattributes:fileDate];
//删除文件
[fileManagerremoveItemAtPath:dicPatherror:nil];//删除~/documents/dict.txt
3、NSUserDefaults
NSUserDefaults*defaults = [NSUserDefaultsstandardUserDefaults];//单例
[defaultssetValue:@"yfyfyfyfyfyfyfy"forKey:@"username"];
[defaultssetValue:@"123"forKey:@"password"];
//注意:UserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize方法强制写入到文件中
[defaultssynchronize];
//读取
NSString*name = [defaultsvalueForKey:@"username"];
NSString*pwd = [defaultsvalueForKey:@"password"];
二、复杂对象持久化(NSKeyedArchiver)
1、复杂对象
复杂对象是在Foundation框架内不存在的数据类,无法通过writeToFile写入到文件内,且至少包含一个实例对象。
由于复杂对象无法通过writeToFile:方法写入文件,只能将复杂对象转化为NSData对象,再进行数据持久化。
2、NSCoding协议
@protocolNSCoding
- (void)encodeWithCoder:(NSCoder*)aCoder;
- (id)initWithCoder:(NSCoder*)aDecoder;// NS_DESIGNATED_INITIALIZER
@end
3、复杂对象写入文件
Person.h
//复杂对象归档一:遵守NSCoding协议
@interfacePerson :NSObject
@property(nonatomic,assign)NSIntegerage;
@property(nonatomic,retain)NSString*name;
@property(nonatomic,retain)NSString*gender;
@end
Person.m
#import"Person.h"
@implementationPerson//实现NSCoding协议
#pragma mark --进行编码--
- (void)encodeWithCoder:(NSCoder*)coder
{
// [super encodeWithCode:coder];如果父类也遵守了NSCoding协议,确保继承的实例变量也能被编码,即也能被归档
[coderencodeObject:self.nameforKey:@"name"];
[coderencodeInteger:self.ageforKey:@"age"];
[coderencodeObject:self.genderforKey:@"gender"];
}
#pragma mark --进行解码--
- (id)initWithCoder:(NSCoder*)aDecoder
{
// self = [super initWithCoder:aDecoder];确保继承的实例变量也能被解码,即也能被恢复
self= [superinit];
if(self) {
self.name= [aDecoderdecodeObjectForKey:@"name"];
self.gender= [aDecoderdecodeObjectForKey:@"gender"];
self.age= [aDecoderdecodeIntegerForKey:@"age"];
}
returnself;
}
@end
ViewController.m
类方法进行编码\解码(只能归档一个对象):
NSString*objPath = [cachesPathstringByAppendingPathComponent:@"person.txt"];
[NSKeyedArchiverarchiveRootObject:persontoFile:objPath];
Person*p2 = [NSKeyedUnarchiverunarchiveObjectWithFile:objPath];
实例方法(可以归档多个对象):
#pragma mark --对复杂对象进行持久化(归档\编码) --
//过程:(复杂对象->归档->NSData->writeToFile:)
Person*person = [[Personalloc]init];
person.name=@"yf";
person.age=20;
person.gender=@"man";
NSMutableData*mtData = http://my.oschina.net/zooyf/blog/[NSMutableDatadata];
//创建归档器
NSKeyedArchiver*archiver = [[NSKeyedArchiveralloc]initForWritingWithMutableData:mtData];
//进行归档
[archiverencodeObject:personforKey:@"person"];
//***结束归档
[archiverfinishEncoding];
//将归档之后的mtData写入文件
NSString*personPath = [cachesPathstringByAppendingPathComponent:@"person.txt"];
[mtDatawriteToFile:personPathatomically:YES];
NSLog(@"%@", personPath);
NSLog(@"%@", mtData);
#pragma mark --从文件中读取复杂对象(反归档\恢复\解码) --//过程:(读取文件(NSData)->反归档->复杂对象)
//读取
NSData*readData = http://my.oschina.net/zooyf/blog/[NSDatadataWithContentsOfFile:personPath];
//创建反归档工具
NSKeyedUnarchiver*unArchiver = [[NSKeyedUnarchiveralloc]initForReadingWithData:readData];
//使用反归档工具对readData进行反归档
Person*readPerson = [unArchiverdecodeObjectForKey:@"person"];
4、使用NSKeyedArchive进行深复制
比如对一个Person对象进行深复制
// 临时存储person1的数据
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person1];
// 解析data,生成一个新的Person对象
Student *person2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
// 分别打印内存地址
NSLog(@"person1:0x%x", person1); // person1:0x7177a60
NSLog(@"person2:0x%x", person2); // person2:0x7177cf0
OS中的数据持久化方式,基本上有以下四种:属性列表、对象归档、SQLite3和Core Data
NSUserDefaults,用于存储配置信息
SQLite,用于存储查询需求较多的数据
CoreData,用于规划应用中的对象
使用基本对象类型定制的个性化缓存方案
1.属性列表
涉及到的主要类:NSUserDefaults,一般 [NSUserDefaults standardUserDefaults]就够用了
@interface User : NSObject
@property (nonatomic, assign) NSInteger userID;
@property (nonatomic, copy) NSString *name;
@end
使用方法
1).分开存取
// 存
[[NSUserDefaults standardUserDefaults] setInteger:userID forKey:@”userID”];
[[NSUserDefaults standardUserDefaults] setObject:name forKey:@”name”];
// 取
NSInteger uId = [[[NSUserDefaults standardUserDefaults] integerValueForKey:@”userID”];
NSString* name = [[NSUserDefaults standardUserDefaults] stringForKey:@”name”];
2).按对象存取
// 存
[[NSUserDefaults standardUserDefaults] setObject:self forKey:@”user”];
// 取
User* u = [[NSUserDefaults standardUserDefaults]objectForKey”@”user”];
2.对象归档
要使用对象归档,对象必须实现NSCoding协议.大部分Object C对象都符合NSCoding协议,也可以在自定义对象中实现NSCoding协议,要实现NSCoding协议,实现两个方法:
- (void) encodeWithCoder:(NSCoder *)encoder 与 -(void)initWithCoder:(NSCoder *)encoder
同时,建议对象也同时实现NSCopying协议,该协议允许复制对象,要实现NSCopying协议须实现 -(id)copyWithZone:(NSZone *)zone 方法 。
@interface User : NSObject
@property (nonatomic, assign) NSInteger userID;
@property (nonatomic, copy) NSString *name;
@end
@implementation User
// 以下两个方法一定要实现,不然在调用的时候会crash
- (void)encodeWithCoder:(NSCoder *)aCoder;
{
// 这里放置需要持久化的属性
[aCoder encodeObject:[NSNumber numberWithInteger:self.userID] forKey:@”userID”];
[aCoder encodeObject:self.name forKey:@"name"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [self init])
{
// 这里务必和encodeWithCoder方法里面的内容一致,不然会读不到数据
self.userID = [[aDecoder decodeObjectForKey:@"userID"] integerValue];
self.name = [aDecoder decodeObjectForKey:@"name"];
}
return self;
}
// 使用方法
+ (BOOL)save {
NSError *error = nil;
// 确定存储路径,一般是Document目录下的文件
NSString* fileName = [self getFileName];
NSString* filePath = [self getFilePath];
if (![[NSFileManager defaultManager] createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLog(@”创建用户文件目录失败”);
return NO;
}
return [NSKeyedArchiver archiveRootObject:self toFile:[fileName:userId]];
}
@end
3.SQLite3
SQLite是一个开源的嵌入式关系数据库,它在2000年由D. Richard Hipp发布,它的减少应用程序管理数据的开销,SQLite可移植性好,很容易使用,很小,高效而且可靠。
SQLite嵌入到使用它的应用程序中,它们共用相同的进程空间,而不是单独的一个进程。从外部看,它并不像一个RDBMS,但在进程内部,它却是完整的,自包含的数据库引擎。 嵌入式数据库的一大好处就是在你的程序内部不需要网络配置,也不需要管理。因为客户端和服务器在同一进程空间运行。SQLite 的数据库权限只依赖于文件系统,没有用户帐户的概念。SQLite 有数据库级锁定,没有网络服务器。它需要的内存,其它开销很小,适合用于嵌入式设备。你需要做的仅仅是把它正确的编译到你的程序。
关于SQLite的开发资料较多,这里不再细说。只是建议不直接操作SQLite库,而是采用一些开源的第三方库来进行操作。比如:
FMDB:https://github.com/ccgus/fmdb.git
对SQLite都做了不错的封装。
4.Core Data
Core Data本质上是使用SQLite保存数据,但是它不需要编写任何SQL语句。
要使用Core Data,需要在Xcode中的数据模型编辑器中设计好各个实体以及定义好他们的属性和关系。之后,通过操作这些对象,结合Core Data完成数据的持久化:
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSError *error;
NSString *fieldName = [NSString stringWithFormat:@"test%d", i];
UITextField *theField = [self valueForKey:fieldName];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//创 建描述语句,需求Line对象。类似于在数据库中限定为Line表。
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Line" inManagedObjectContext:context];
[request setEntity:entityDescription];
//创建限制性语句,类似于SQL语句中的 where lineNum = i
NSPredicate *pred = [NSPredicate predicateWithFormat:@"(lineNum = %d)", i];
[request setPredicate:pred];
NSManagedObject *theLine = nil;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil){
NSLog(@”There was an error!”);
// Do whatever error handling is appropriate
}
if ([objects count] > 0){ //如果符合条件的object存在,则取出
theLine = [objects objectAtIndex:0];
}
else { //如果不存在,则插入一个新的.
theLine = [NSEntityDescription insertNewObjectForEntityForName:@"Line"
inManagedObjectContext:context];
[theLine setValue:[NSNumber numberWithInt:i] forKey:@”lineNum”]; //设置这个object的属性,coredata会自动将其写入sqlite
[theLine setValue:theField.text forKey:@"lineText"];
[request release];
}
}
下面是其取数据的过程:
Core_Data_PersistenceAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Line"
inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil)
{
NSLog(@”There was an error!”);
// Do whatever error handling is appropriate
}
//每一个对象在CoreData中都表示为一个NSManagedObject对象(类似于数据库表中的每一行),他的属性通过键/值 方式获取
for (NSManagedObject *oneObject in objects)
{
NSNumber *lineNum = [oneObject valueForKey:@"lineNum"];
NSString *lineText = [oneObject valueForKey:@"lineText"];
}
[request release];