@qidiandasheng
2016-09-05T20:07:53.000000Z
字数 6484
阅读 2727
iOS理论
所谓数据持久化,就是数据的存储。在iOS里数据的存储有以下几种:
在说数据存储之前肯定绕不开苹果的沙盒机制。出于安全考虑,iOS系统的沙盒机制规定每个应用都只能访问当前沙盒目录下面的文件(也有例外,比如系统通讯录能在用户授权的情况下被第三方应用访问)。
Xcode7上模拟器的沙盒路径:
数据目录:
/Users/用户名/Library/Developer/CoreSimulator/Devices/模拟器UDID/data/Containers/Data/Application/对应应用程序文件夹
这里面主要有Documents、Library(Caches和Preferences)、tmp,如图:
.app目录:
/Users/用户名/Library/Developer/CoreSimulator/Devices/模拟器UDID/data/Containers/Bundle/Application/对应应用程序文件夹/应用名.app
获取沙盒目录:
NSLog(@"%@",NSHomeDirectory());
输出:/var/mobile/Applications/21A34B34-9B30-4B8A-854E-1553480C078F
存放内容:
该目录包含了应用程序本身的数据,包括资源文件和可执行文件等。程序启动以后,会根据需要从该目录中动态加载代码或资源到内存,这里用到了lazy loading的思想。
整个目录是只读的:
为了防止被篡改,应用在安装的时候会将该目录签名。非越狱情况下,该目录中内容是无法更改的;在越狱设备上如果更改了目录内容,对应的签名就会被改变,这种情况下苹果官网描述的后果是应用程序将无法启动
是否会被iTunes同步
否
获取目录
NSLog(@"%@",[[NSBundle mainBundle] bundlePath]);
输出:/var/mobile/Containers/Data/Applications/21A34B34-9B30-4B8A-854E-1553480C078F/PhoneCall.app
存放内容
我们可以将应用程序的数据文件保存在该目录下。不过这些数据类型仅限于不可再生的数据,可再生的数据文件应该存放在Library/Cache目录下。
是否会被iTunes同步
是
获取目录
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
NSLog(@"%@",path);
输出:/var/mobile/Containers/Data/Applications/21A34B34-9B30-4B8A-854E-1553480C078F/Documents
存放内容
苹果建议用来存放默认设置或其它状态信息。
是否会被iTunes同步
是,但是要除了Caches子目录外
获取目录
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
NSLog(@"%@", path);
输出:/var/mobile/Containers/Data/Application/21A34B34-9B30-4B8A-854E-1553480C078F/Library
存放内容
主要是缓存文件,用户使用过程中缓存都可以保存在这个目录中。前面说过,Documents目录用于保存不可再生的文件,那么这个目录就用于保存那些可再生的文件,比如网络请求的数据。鉴于此,应用程序通常还需要负责删除这些文件。
SDWebImage
的图片缓存就是存储在这里。
是否会被iTunes同步
否
获取目录
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
NSLog(@"%@", path);
输出:/var/mobile/Containers/Data/Application/21A34B34-9B30-4B8A-854E-1553480C078F/Library/Caches
存放内容
应用程序的偏好设置文件。我们使用NSUserDefaults写的设置数据都会保存到该目录下的一个plist文件中,这就是所谓的写到plist中。
是否会被iTunes同步
是
存放内容
各种临时文件,保存应用再次启动时不需要的文件。而且,当应用不再需要这些文件时应该主动将其删除,因为该目录下的东西随时有可能被系统清理掉,目前已知的一种可能清理的原因是系统磁盘存储空间不足的时候。
是否会被iTunes同步
否
获取目录
NSLog(@"%@",NSTemporaryDirectory());
输出:/private/var/mobile/Containers/Data/Applications/21A34B34-9B30-4B8A-854E-1553480C078F/tmp/
plist,全名PropertyList,即属性列表文件,它是一种用来存储串行化后的对象的文件。这种文件,在ios开发过程中经常被用到。这种属性列表文件的扩展名为.plist,因此通常被叫做plist文件。文件是xml格式的。Plist文件是以key-value的形式来存储数据。既可以用来存储用户设置,也可以用来存储一些需要经常用到而不经常改动的信息。
可以被序列化的类型只有如下几种:
NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
获得文件路径:
我们看到一般plist
文件都是存储在沙盒的Documents
目录下的。如果document里没有此文件,会自动创建,读取赋值后,便可使用。
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@"userName.plist"];
存储
NSArray *array = @[@"齐滇大圣", @"哈哈", @"呵呵"];
[array writeToFile:fileName atomically:YES];
读取
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@"%@", result);
删除plist文件
NSFileManager *fileManage = [NSFileManager defaultManager];
NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0]stringByAppendingPathComponent:@"userName.plist"];
//如果文件路径存在的话
BOOL isExist = [fileManage fileExistsAtPath:filePath];
if (isExist) {
NSError *err;
[fileManage removeItemAtPath:filePath error:&err];
}
偏好设置主要用来存储一些存储轻量级的本地数据,比如一些应用程序的配置信息,用户名、密码之类的。
偏好设置会将所有数据保存到同一个文件中。即preference目录下的一个以此应用包名来命名的plist文件。
使用
//获得NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//向文件中写入内容
[userDefaults setObject:@"齐滇大圣" forKey:@"name"];
[userDefaults setBool:YES forKey:@"sex"];
[userDefaults setInteger:21 forKey:@"age"];
//立即同步,如果没有调用synchronize方法,系统会根据I/O情况不定时刻地保存到文件中。所以如果需要立即写入文件的就必须调用synchronize方法。
[userDefaults synchronize];
//读取文件
NSString *name = [userDefaults objectForKey:@"name"];
BOOL sex = [userDefaults boolForKey:@"sex"];
NSInteger age = [userDefaults integerForKey:@"age"];
NSLog(@"%@, %d, %ld", name, sex, age);
DSCategories里的NSUserDefaults+SafeAccess
分类可以快速方便的操作NSUserDefaults
。
归档在iOS中是另一种形式的序列化,只要遵循了NSCoding
协议的对象都可以通过它实现序列化。NSString
、NSDictionary
、NSArray
、NSData
、NSNumber
等类型都遵循了NSCoding
协议。
NSCoding
协议有2个方法:
- (void)encodeWithCoder:(NSCoder *)aCoder
每次归档对象时,都会调用这个方法。一般在这个方法里面指定如何归档对象中的每个实例变量,可以使用encodeObject:forKey:方法归档实例变量
- (id)initWithCoder:(NSCoder *)aDecoder
每次从文件中恢复(解码)对象时,都会调用这个方法。一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用decodeObject:forKey方法解码实例变量
归档一个NSArray对象
NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"array.archive"];
NSArray *array = [NSArray arrayWithObjects:@”哈哈”,@”呵呵”,nil];
[NSKeyedArchiver archiveRootObject:array toFile:filePath];
//恢复NSArray对象
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
归档Person对象
@interface Person : NSObject<NSCoding>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) float height;
@end
@implementation Person
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeInt:self.age forKey:@"age"];
[encoder encodeFloat:self.height forKey:@"height"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self.name = [decoder decodeObjectForKey:@"name"];
self.age = [decoder decodeIntForKey:@"age"];
self.height = [decoder decodeFloatForKey:@"height"];
return self;
}
@end
NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.archive"];
Person *person = [[Person alloc] init];
person.name = @"hosea";
person.age = 22;
person.height = 1.83f;
[NSKeyedArchiver archiveRootObject:person toFile:filePath];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
注意
如果父类也遵守了NSCoding协议,请注意:
应该在encodeWithCoder:方法中加上一句
[super encodeWithCode:encode];
确保继承的实例变量也能被编码,即也能被归档
应该在initWithCoder:方法中加上一句
self = [super initWithCoder:decoder];
确保继承的实例变量也能被解码,即也能被恢复
SQLite3是一款开源的嵌入式关系型数据库,可移植性好、易使用、内存开销小。
SQLite3是无类型的,意味着你可以保存任何类型的数据到任意表的任意字段中。
一般我们创建的的数据库是生成在Doucuments
目录下以dataName.sqlite
命名的。
iOS中原生的SQLite API
在使用上相当不友好,于是我们一般都会使用将SQLite API
进行过封装的第三方库。比如FMDB。
Core Data 是 iOS3.0 时引入的一个数据持久化的框架。他与 sqlite 对比最大的优点莫过于支持对象的存储了,苹果的官方文档说其简化了数据库的操作,使用 CoreData 确实可以大量减少代码中的 SQL 语句。
其实 Core Data
是构建在 SQLite
之上,对数据存储层进行了进一步的抽象。虽然说Core Data
减少了SQL语句,但是其学习成本并没有降低,复杂度依旧挺高,而且性能也没特别好。
详情可以阅读唐巧的我为什么不喜欢 Core Data这篇文章。