上一篇:iOS 数据持久化-沙盒机制介绍(一)
iOS 数据存储有四种方案:
NSUserDefault
KeyChain(钥匙串,删除App,数据不会删除)
File(plist、Archive)
DB(SQLite、FMDB、CoreData)
NSUserDefault
Preference偏好设置,NSUserDefaults(plist),用来保存应用程序设置和属性。
KeyChain:钥匙串
1 . 更安全. 对比 NSUserDefault 存储一些数据, 会更加安全.
2 . 即便 App 被卸载, 存储的信息依旧存在, 再次安装App, 存储是信息依旧可以使用.
3 . 相同的 Team ID 开发, 可实现多个App 共享数据
File
plist:存储plist文件,plist文件打开,也是xml形式。(NSArray,NSDictionary writeToFile -
),不能存储用户自定义对象 Archive:采用归档的形式来保存数据,该数据对象需要遵守NSCoding协议。归档形式保存数据只能一次性归档保存以及一次性解压,只能针对小量数据,而且数据操作笨拙,即如果想改动数据的某一小部分,需要解压整个数据或者归档整个数据。
DB(SQLite、FMDB、CoreData):
相对前两种更为复杂,但也最好用。SQLite,可以自己封装,也可以使用FMDB等第三方封装库,还可以使用系统的CoreData
NSUserDefaults
用于存储用户的偏好设置和用户信息,如用户名,是否自动登录,字体大小等.
数据自动保存在沙盒的Libarary/Preferences目录下.
NSUserDefaults将输入的数据储存在.plist格式的文件下,这种存储方式就决定了它的安全性几乎为0,所以不建议存储一些敏感信息如:用户密码,token,加密私钥等!
它能存储的数据类型为:NSNumber(NSInteger、float、double),NSString,NSDate,NSArray,NSDictionary,BOOL.
不支持自定义对象的存储.
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
[userDefault setInteger:1 forKey:@"integer"];
[userDefault setBool:YES forKey:@"BOOl"];
[userDefault setFloat:6.5 forKey:@"float"];
[userDefault setObject:@"123" forKey:@"numberString"];
NSString *numberString = [userDefault objectForKey:@"numberString"];
BOOL myBool = [userDefault boolForKey:@"BOOl"];
[userDefault removeObjectForKey:@"float"];
[userDefault synchronize];
需要注意的问题:
NSUserDefaults存储的数据都是不可变的,想将可变数据存入需要先转为不可变才可以存储.
NSUserDefaults是定时把缓存中的数据写入磁盘的,而不是即时写入,为了防止在写完NSUserDefaults后程序退出导致的数据丢失,可以在写入数据后使用synchronize强制立即将数据写入磁盘.
KeyChain(钥匙串,删除App,数据不会删除)
用于本地重要数据的存储,将数据加密后存储在本地更安全.如:密码,秘钥,序列号等.当你删除APP后Keychain存储的数据不会删除,所以在重装App后,Keychain里的数据还能使用。从ios 3.0开始,跨程序分享keychain变得可行而NSUserDefaults存储的数据会随着APP而删掉.
使用keychain时苹果官方已经为我们封装好了文件KeychainItemWrapper,引入即可使用。当然也可是使用其他优秀的第三方的封装。
File
1.属性列表(plist文件)
即属性列表文件,全名是Property List,这种文件的扩展名为.plist,因此,通常被叫做plist文件。它是一种用来存储串行化后的对象的文件,用于存储程序中经常用到且数据量小而不经常改动的数据。
可以存储的类型:NSNumber,NSString,NSDate,NSData ,NSArray,NSDictionary,BOOL.
不支持自定义对象的存储.
plist的创建方式有两种:command + n 创建和纯代码创建,不同的创建方式使用方法也自然不同
command + n 创建:
创建:(创建方法自行百度)
读取:
NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"myTest" ofType:@"plist"];
NSMutableDictionary *dataDic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
/**如果为数组时*/
// NSMutableArray *dataArray = [NSMutableArray arrayWithContentsOfFile:plistPath];
NSLog(@"%@",dataDic);
修改:
[dataDic setObject:@"男" forKey:@"sex"];
[dataDic setObject:@15 forKey:@"age"];
[dataDic writeToFile:plistPath atomically:YES];
NSLog(@"----%@",dataDic);
纯代码创建:
创建:
NSArray *sandBoxPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [sandBoxPath objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:@"myTestPlist.plist"];
NSLog(@"%@",plistPath);
写入:
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setObject:@"18" forKey:@"age"];
[dic setObject:@"胡杨" forKey:@"name"];
[dic writeToFile:plistPath atomically:YES];
读取:
NSArray *sandBoxPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [sandBoxPath objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:@"myTestPlist.plist"];
NSLog(@"%@",plistPath);
NSMutableDictionary *dataDic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
NSLog(@"-----%@",dataDic);
修改:
[dataDic setObject:@"男" forKey:@"sex"];
[dataDic setObject:@15 forKey:@"age"];
[dataDic writeToFile:plistPath atomically:YES];
NSLog(@"----%@",dataDic);
需要注意的问题:
如果需要存储自定义类型的数据需要先进行序列化!
我们使用了属性列表来指定应用的默认设置与相应的数据存储,并且方便使用Xcode或者Property List Editor应用手动编辑它们,只要字典或者数据包含特定可序列化对象,就可以NSDictionary和NSArray实例写入属性列表或者从属性列表创建相应的对象;
什么是序列化对象?
序列化对象(Serialized objects),是指可以被转换为字节流以便于存储到文件中或者通过网络进行传输的对象;
虽然说任何对象都可以被序列化,但是只有某些特定的对象才能放置到某个集合类(例如:
NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
中,并使用该集合类的方法在属性列表存储中使用,其他的对象也可以使用归档的方法进行存储(在对象的归档、解档我们会进行详细介绍)。
注意
只有以上列出的类型才能使用plist文件存储。
存储时使用writeToFile: atomically:方法。 其中atomically表示是否需要先写入一个辅助文件,再把辅助文件拷贝到目标文件地址。这是更安全的写入文件方法,一般都写YES。
读取时使用arrayWithContentsOfFile:方法。
plist文件是将某些特定的类,通过XML文件的方式保存在目录中。
-(void)writeToFile:(NSMutableDictionary *)data
{
// 获取当前沙盒中的文件路径
NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask , YES);
NSLog(@"paths = %@",paths);
// 获取Document目录路径
NSString *documentPath = [paths objectAtIndex:0];
// 设置文件的路径
NSString *filePath = [documentPath stringByAppendingPathComponent:@"newdata.plist"];
[data writeToFile:filePath atomically:YES];
}
-(NSMutableDictionary *)readFile
{
// 获取当前沙盒中的文件路径
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask , YES);
NSLog(@"paths = %@",paths);
// 获取Document目录路径
NSString *documentPath = [paths objectAtIndex:0];
// 设置文件的路径
NSString *filePath = [documentPath stringByAppendingPathComponent:@"newdata.plist"];
// 文件管理类
NSFileManager *fm = [NSFileManager defaultManager];
// 判断文件是否存在,如果存在就读取数据
if ([fm fileExistsAtPath:filePath])
{
NSLog(@"111111111");
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
return dict;
} else {
NSLog(@"2222222");
NSMutableDictionary *emptyDict = [[[NSMutableDictionary alloc] init] autorelease];
return emptyDict;
}
}
2.归档(NSKeyedArchiver)
归档是iOS开发中数据存储常用的技巧,归档可以直接将对象储存成文件,把文件读取成对象。
相对于plist或者userdefault形式,归档可以存储的数据类型更加多样,并且可以存取自定义对象。对象归档的文件是保密的,在磁盘上无法查看文件中的内容,更加安全。
遵守NSCoding协议,并实现该协议中的两个方法。如果是继承,则子类一定要重写那两个方法。因为子类在存取的时候,会去子类中去找调用的方法,没找到那么它就去父类中找,所以最后保存和读取的时候新增加的属性会被忽略。需要先调用父类的方法,先初始化父类的,再初始化子类的。
保存数据的文件的后缀名可以随意命名。
demo
就像我们前面属性列表的介绍,归档(archiving)也是指另一种形式的序列化。但强大的一点是,它是任何对象都可以实现的更常规的储存数据类型;
在进行归档、解档的开发中,我们需要一起实现的,NSCoding,需要说明的是,标量(如int或float)以及大多数Foundation和Cocoa Touch类都遵循NSCoding协议(有例外,如UIImage不遵循),因此大多数类,还是比较容易实现归档操作的;
1、遵循NSCoding协议
NSCoding协议声明了2个方法:一个是将对象编码到归档中,另一个是对归档的解码来恢复我们之前归档的对象,使用方法与NSUserDefaults相似也可以用KVC对对象和原生数据类型(如int和float)进行编码和解码。
2、归档、解档
归档:创建一个NSKeyedArchiver实例,用于将对象归档到一个NSMutableData实例中,此时NSMutableData包含编码的数据,再使用键/码对需要的对象进行归档,最后告知完成,写入文件系统;
解档:也与归档对象步骤类似,创建一个NSData实例用于装载数据,并创建一个NSKeyedUnarchiver实例,对数据解码,然后使用先前用的键进行读取对象,最后告知程序解档完成;
如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复
不是所有的对象都可以直接用这种方法进行归档,只有遵守了NSCoding协议的对象才可以
归档一个NSArray对象到Documents/array.archive
NSArray *array = [NSArray arrayWithObjects:@”a”,@”b”,nil];
[NSKeyedArchiver archiveRootObject:array toFile:path];
恢复(解码)NSArray对象
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
归档Person对象
- (IBAction)save:(id)sender {
// 存储自定义对象,可使用归档,但需要遵循NSCoding协议,实现对应方法
Person* p = [[Person alloc] init];
p.age = 18;
p.name = @"zgq";
// 获取caches文件夹
NSString* cachesPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
NSString* path = [cachesPath stringByAppendingPathComponent:@"person.data"];
NSLog(@"%@", cachesPath);
// object: 需要归档的对象,任何对象都可以进行归档 file: 文件的全路径
[NSKeyedArchiver archiveRootObject:p toFile:path];
}
- (IBAction)read:(id)sender {
// 存进去是什么 就怎么取出
Person* p = [NSKeyedUnarchiver unarchiveObjectWithFile:[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"person.data"]];
NSLog(@"age: %ld , name: %@", p.age, p.name);
}
如果需要归档的类是某个自定义类的子类时,就需要在归档和解档之前先实现父类的归档和解档方法。即 [super encodeWithCoder:aCoder] 和 [super initWithCoder:aDecoder] 方法;
归档NSData
使用archiveRootObject:toFile:方法可以将一个对象直接写入到一个文件中,但有时候可能想将多个对象写入到同一个文件中,那么就要使用NSData来进行归档对象
NSData可以为一些数据提供临时存储空间,以便随后写入文件,或者存放从磁盘读取的文件内容。可以使用[NSMutableData data]创建可变数据空间
归档(编码)
// 新建一块可变数据区
NSMutableData *data = [NSMutableData data];
// 将数据区连接到一个NSKeyedArchiver对象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
// 开始存档对象,存档的数据都会存储到NSMutableData中
[archiver encodeObject:person1 forKey:@"person1"];
[archiver encodeObject:person2 forKey:@"person2"];
// 存档完毕(一定要调用这个方法,调用了这个方法,archiver才会将encode的数据存储到NSMutableData中)
[archiver finishEncoding];
// 将存档的数据写入文件
[data writeToFile:path atomically:YES];
恢复(解码)
// 从文件中读取数据
NSData *data = [NSData dataWithContentsOfFile:path];
// 根据数据,解析成一个NSKeyedUnarchiver对象
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
Person *person1 = [unarchiver decodeObjectForKey:@"person1"];
Person *person2 = [unarchiver decodeObjectForKey:@"person2"];
// 恢复完毕(这个方法调用之后,unarchiver不能再decode对象,而且会通知unarchiver的代理调用unarchiverWillFinish:和unarchiverDidFinish:方法)
[unarchiver finishDecoding];
PS:也可将多个对象放入到一个数组中。
将数组进行归档,在数组对象执行archiveRootObject:toFile时,数组中每个对象会自动调用encodeWithCoder:方法进行归档;
相反数组文件进行解档时,在数组对象执行unarchiveObjectWithFile:时,数组中每个对象会自动调用initWithCoder:方法进行解档。
利用归档实现深复制
比如对一个Person对象进行深复制
// 临时存储person1的数据
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person1];
// 解析data,生成一个新的Person对象
Person *person2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
// 分别打印内存地址
NSLog(@"person1:0x%x", person1); // person1:0x7177a60
NSLog(@"person2:0x%x", person2); // person2:0x7177cf0
四:CoreData、SQLite
适合储存数据量较大的数据,一般使用FMDB和CoreData来实现.
FMDB:
FMDB是iOS平台的SQLite数据库框架,FMDB以OC的方式封装了SQLite的C语言API,使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码,对比苹果自带的Core Data框架,更加轻量级和灵活,提供了多线程安全的数据库操作方法,有效地防止数据混乱。
CoreData:
Core Data是iOS5之后才出现的一个框架,它提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite数据库文件中,也能够将保存在数据库中的数据还原成OC对象。在此数据操作期间,我们不需要编写任何SQL语句.但是直接操作CoreData显的不是那么容易,所以我多数的时候会使用MagicRecord来实现。MagicRecord是对CoreData的二次封装,使用起来简单操作方便.
文件(NSFileManager)
用来操作文件的类
NSFileManager *fm = [NSFileManager defaultManager];
// 判断文件是否存在,如果存在就读取数据
if ([fm fileExistsAtPath:filePath]) {
NSMutableArray *array = [NSMutableArray arrayWithContentsOfFile:filePath];
return array;
} else {
NSMutableArray *emptyArray = [[[NSMutableArray alloc] init] autorelease];
return emptyArray;
}