不管是在iOS还是Android开发过程中,我们都经常性地需要存储一些状态和数据,比如用户对于App的相关设置、需要在本地缓存的数据等等。根据要存储的的数据的大小、存储性质以及存储类型,在iOS和Android中哪个都有多种存储方式。其中,iOS中的存储方式主要包括以下六类:
在研究存储方式之前,我们有必要先研究下这些文件会存储到什么地方去,这就需要我们了解iOS App特有的沙盒机制了。iOS程序默认情况下只能访问程序自己的目录,这个目录被称为“沙盒”,即沙盒其实就是一个App特有的一个文件夹,iOS下每个App都有自己特有的一个沙盒,其结构和目录特性都是一样的。
既然沙盒就是一个文件夹,那就看看里面有什么吧。沙盒的目录结构如下图所示,每个App的沙盒都是由下图所示的四部分组成,每一部分中存放的数据和内容都是有一定的规范和性质的。该目录路径的获取方法是直接通过 NSHomeDirectory() 就得到和应用沙盒的路径。
此外,每一个App还有一个Bundle目录,即“应用程序包”,该目录下 存放的是应用程序的源文件,包括资源文件和可执行文件。
虽然沙盒中有这么多文件夹,但是没有文件夹都不尽相同,都有各自的特性。所以在选择存放目录时,一定要认真选择适合的目录。
NSString *path = [[NSBundle mainBundle] bundlePath];
NSLog(@"%@", path);
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"%@", path);
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"%@", path);
NSString *path = NSTemporaryDirectory();
NSLog(@"%@", path);
在文章的开始已经讲到了,iOS中本地存储的方式一般有6种。下面我们将一个个来进行学习和研究。
plist文件是将某些特定的类,通过XML文件的方式保存在目录中。可以被序列化的类型只有如下几种:
NSArray
NSMutableArray
NSDictionary
NSMutableDictionary
NSData
NSMutableData
NSString
NSMutableString
NSNumber
NSDate
1. 获得文件路径
项目中plist文件是存储在沙盒的documents中,所以要获取某个plist文件,只需要知道其文件名就可以了,如下方式就好可以获取并读取其中的内容,读取时通过对应类型的方式来获取plist的数据。一般plist中的内容都是以NSArray或NSDictionary的形式保存。
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@"123.plist"];
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
2. 存储
往plist中写内容也非常简单,直接用相应类型的writeToFile方法即可。
NSArray *array = @[@"123", @"456", @"789"];
[array writeToFile:fileName atomically:YES];
3. 注意
preefrence(偏好设置)顾名思义就是用户在使用过程中对App的一些状态和自定义设置状态的保存,例如App的皮肤样式、游戏时是否屏蔽电话和聊天、界面显示格式等等。一般对于一些基本的用户设置,因为数据量很小,我们可以使用OC语言中的NSUserDefaults类来进行处理。使用方法很简单,只需要调用类中的方法即可。此外,NSUserDefaults 创建的数据其实也是一个plist文件,其中数据保存格式是键值对形式,即NSDictionary形式,该文件存放在沙盒 Library/Preferences/ 目录下,一个以你包名命名的.plist文件。
1. 使用方法
//1.获得NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//2.向文件中写入内容
[userDefaults setObject:@"AAA" forKey:@"a"];
[userDefaults setBool:YES forKey:@"sex"];
[userDefaults setInteger:21 forKey:@"age"];
//2.1立即同步
[userDefaults synchronize];
//3.读取文件
NSString *name = [userDefaults objectForKey:@"a"];
BOOL sex = [userDefaults boolForKey:@"sex"];
NSInteger age = [userDefaults integerForKey:@"age"];
NSLog(@"%@, %d, %ld", name, sex, age);
2. 注意
之前说了,不管是NSUserDefaults 或者是 plist 都不能对自定义的对象进行存储,OC提供了解归档恰好解决这个问题。归档在iOS中是另一种形式的序列化,只要遵循了NSCoding协议的对象都可以通过它实现序列化。由于决大多数支持存储数据的Foundation和Cocoa Touch类都遵循了NSCoding协议,因此,对于大多数类来说,归档相对而言还是比较容易实现的。
1. 遵循NSCoding协议
NSCoding协议声明了两个方法,这两个方法都是必须实现的。一个用来说明如何将对象编码到归档中,另一个说明如何进行解档来获取一个新对象。
1 //1.遵循NSCoding协议
2 @interface Person : NSObject //2.设置属性
3 @property (strong, nonatomic) UIImage *avatar;
4 @property (copy, nonatomic) NSString *name;
5 @property (assign, nonatomic) NSInteger age;
6 @end
1 //解档
2 - (id)initWithCoder:(NSCoder *)aDecoder {
3 if ([super init]) {
4 self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
5 self.name = [aDecoder decodeObjectForKey:@"name"];
6 self.age = [aDecoder decodeIntegerForKey:@"age"];
7 }
8 return self;
9 }
10 //归档
11 - (void)encodeWithCoder:(NSCoder *)aCoder {
12 [aCoder encodeObject:self.avatar forKey:@"avatar"];
13 [aCoder encodeObject:self.name forKey:@"name"];
14 [aCoder encodeInteger:self.age forKey:@"age"];
15 }
如果需要归档的类是某个自定义类的子类时,就需要在归档和解档之前先实现父类的归档和解档方法。即 [super encodeWithCoder:aCoder] 和 [super initWithCoder:aDecoder] 方法。
2. 使用
需要把对象归档是调用NSKeyedArchiver的工厂方法 archiveRootObject: toFile: 方法。
1 NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
2 Person *person = [[Person alloc] init];
3 person.avatar = self.avatarView.image;
4 person.name = self.nameField.text;
5 person.age = [self.ageField.text integerValue];
6 [NSKeyedArchiver archiveRootObject:person toFile:file];
需要从文件中解档对象就调用NSKeyedUnarchiver的一个工厂方法 unarchiveObjectWithFile: 即可。
1 NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
2 Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
3 if (person) {
4 self.avatarView.image = person.avatar;
5 self.nameField.text = person.name;
6 self.ageField.text = [NSString stringWithFormat:@"%ld", person.age];
7 }
3. 注意
手动将数据存放到沙盒,其实就是自己在沙盒的某一个指定路径(第一部分介绍了沙盒各目录路径的获取方式)下新建一个保存数据的文件(.txt、.plist、.data等格式的文件),然后向其中写我们需要保存的数据即可。但是沙盒中只能保存OC中的基本数据,自定义的对象不能直接存入,但是可以通过归档存为.data文件。
1 //假设我们需往cache 存入数据,并命名为test的txt格式文件中
2 NSString *filePath = [cachesDir stringByAppendingPathComponent:@"test.txt"];
3 NSArray *dic = [[NSArray alloc] initWithObjects:@"test",@"test1" ,nil];
4
5 if([dic writeToFile:filePath atomically:YES]){
6 NSLog(@"存入成功");
7 }
8 //取出数据 打印
9 NSLog(@"%@",[NSArray arrayWithContentsOfFile:filePath]);
Core Date是ios3.0后引入的数据持久化解决方案,它是是苹果官方推荐使用的,不需要借助第三方框架。Core Date实际上是对SQLite的封装,提供了更高级的持久化方式。在对数据库操作时,不需要使用sql语句,也就意味着即使不懂sql语句,也可以操作数据库中的数据。
在各类应用开发中使用数据库操作时通常都会用到 (ORM) “对象关系映射”,Core Data就是这样的一种模式。ORM是将关系数据库中的表,转化为程序中的对象,但实际上是对数据中的数据进行操作。
在使用Core Data进⾏行数据库存取并不需要手动创建数据库,创建数据库的过程完全由Core Data框架自动完成,开发者需要做的就是把模型创建起来,具体数据库的创建不需要管。简单点说,Core Data实际上是将数据库的创建、表的创建、对象和表的转换等操作封装起来,极大的简化了我们的操作。
Core Date与SQLite相比较,SQLite比较原始,操作比较复杂,使用的是C的函数对数据库进行操作,但是SQLite可控性更强,并且能够跨平台。
关于Core Date的具体使用方法参见:IOS 数据存储之 Core Data详解
iOS系统自带Core Data
来进行持久化处理,而且Core Data
可以使用图形化界面来创建对象,但是Core Data
不是关系型数据库,对于Core Data
来说比较擅长管理在设备上创建的数据持久化存储用户创建的对象,但是要处理大量的数据时就应该优先选择SQL
关系型数据库来存储这些数据。
Core Data
在后台也是使用SQLite来存储数据的,但是开发人员不能直接访问这些数据,只能通过Core Data
提供的API来操作,如果一旦人为的通过SQLite
修改这些数据那么使用Core Data
再次访问这些数据时就会发生错误。
SQLite是使用C
语言写的开源库,实现了一个自包含的SQL关系型数据库引擎
,可以使用SQLite
存储操作大量的数据,作为关系型数据库我们可以在一个数据库中建立多张相关联的表来解决大量数据重复的问题。而且SQLite
库也针对移动设备上的使用进行了优化。 因为SQLite
的接口使用C
写的,而且Objective-C
是C
的超集所以可以直接在项目中使用SQLite
。
关于SQLite的详细使用方法详见:iOS开发数据库篇—SQLite的应用