1、iOS应用数据存储的常用方式
1、XML属性列表(plist)归档
plist文件只能归档(存储)字典和数组,字典和数组里面保存的数据必须是 Boolean,Data,Date,Number,String
这几种系统自带的对象类型。
不能存储自定义的对象。
2、Preference(偏好设置)
Preference 是和 plist 文件类似,可以开速的进行一些键值对的存储。本质是底层封装了一个字典。
也是不能存储自定义的对象。
3、NSKeyedArchiver归档(NSCoding) 和 NSKeyedUnarchiver 解档
NSKeyedArchiver 可以对自定义的对象进行归档和解档。和xml(plist)偏好设置比较,更加的适合存储数据。
弊端:
但是NSKeyedArchiver 在进行多个对象归档的时候,显得过于繁琐。
NSKeyedArchiver 的文件的写入和读取是一次性的写入和读取。如果读取和写入的文件过于大,对于内存有限的移动设备,很容易导致app的奔溃。
这个是我们不愿意看到的。
4、SQLite3
SQLite3是一款开源的嵌入式关系型数据库,可移植性好、易使用、内存开销小,性能高,学习成本低。
(SQLite3 已经运用到所有的移动端)
SQLite3 的数据的写入是可以持续的分批次的,你想写多少写多少。读取数据也是可以持续的分批次的,你想读取多少就读取多少。
SQLite3是无类型的,意味着你可以保存任何类型的数据到任意表的任意字段中。
(虽然 SQLite3有常用的5种数据类型:text、integer、float、boolean、blob)对于可以保存二进制数据这一条。
我们就可以很好地利用。我们可以想使用服务器数据一样,直接存取二进制的数据和直接获取二进制的数据。
既然 SQLite3 是一个数据库,我们还以这个数据库对我们的数据进行管理。
有点忧伤的是 SQLite3 是纯 c 语言的。(我们可以找第三方封装的oc框架使用)
5、Core Data
Core Data:苹果的亲儿子,是对 SQLite 做的面向对象的封装。
存在弊端:
既然是对 SQLite 做的面向对象的封装 ,在其中的转换的时候是要消耗性能的。
Core Data:用在数据比较复杂的时候是比较好的,封装好一堆很好用的方法可以直接使用,减轻我们的开发难度。
(手机端存储的数据越简单越好。这样性能的消耗就越小,对于移动端,处理简单的数据,建议使用 SQLite3 存储数据)
plist归档,Preference (偏好设置) NSKeyedArchiver 归档(NSCoding)都是苹果自带的东西,其他的程序和软件是没有的。
2、对于数据存储必须了解的 —— NSBundle 和 沙盒
2.1、主NSBundle(mainBundle)
主NSBundle:就是app的资源包, mainBundle包含app的所有的资源文件和可执行文件。
app的所有的资源文件和可执行文件都保存在NSBundle文件夹里面。主NSBundle 是一个压缩包,会将app用到的资源打包放在NSBundle 里面。
2.2、应用沙盒
应用的沙盒就是app在手机上保存数据的一个文件夹。每一个ios应用都有自己的应用沙盒,与其他的文件系统是隔离的。app只能访问自己的沙盒里面的数据,其他应用是不能访问该沙盒的。
我们是将我们要保存的一些数据一文件的形式保存在沙盒文里面的某个文件夹里面。
下面是沙盒文件夹的列表:
我记得是从ios8 开始 bundle和沙盒不在一个文件夹里面。在两个不同的地方
下面是沙盒的示意图:(bundle请看上上张图)
2.3、沙盒中各个文件夹的作用的详解
Documents :保存应用运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录。
例如,游戏应用可将游戏存档保存在该目录。
(通常用来保存比较重要的数据,因为会被备份)
苹果不建议使用这个文件夹。
使用这个文件要注意的问题:
将资源保存到这个文件夹里面,在app上架的时候,可能会被拒绝。
解决的方法:将保存到这里的数据设置不会被iTunes备份。(网上又源码自己百度)
Library/Caches:保存应用运行时生成的需要持久化的数据,iTunes同步设备时不会备份该目录。一般存储体积大、不需要备份的非重要数据
Caches:用来保存缓存数据(图片资源)。
SDWebImage的缓存资源是保存到这个文件夹里面。
Library/Preference:保存应用的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时会备份该目录
Preference:偏好设置文件夹。这个文件是由系统管理的。(我们基本不将自己的文件保存到这个文件夹里面)
在使用这个文件夹的时候,系统提供了相应地方法。
tmp:保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录。
(这个文件下面的数据不是安全的,可能会被系统清除 )
主要用来保存当前需要用的数据,下次启动程序的时候比一定要的数据。
在开发中我们使用的最多的时Caches 和 temp 这个文件。Documents 主要是做游戏的时候才会用到(用作游戏的存档)。
2.4、 应用沙盒目录里面文件夹的获取的2种方式
第一种方式:通过系统提供的一个函数直接获取沙盒路径,后面在拼接相应的文件夹的路径。
函数:NSHomeDirectory();
沙盒根目录:
NSString *homePath = NSHomeDirectory();
NSLog(@"%@",homePath);
打印的沙河路径:
Users/zhuohong_xiao/Library/Developer/CoreSimulator/Devices/390F60C7-3159-4452-AA5E-8D9DC4C9964E/data/Containers/Data/Application/76159507-978A-4DF0-B82D-8C7D73ACF0FD
这个是沙盒的文件名:
76159507-978A-4DF0-B82D-8C7D73ACF0FD(这个是经过加密计算的一个文件名,为的时避免文件名的重复,避免出错)
获取沙盒中 Documents 文件夹的路径
// 获取沙盒路径
NSString *homePath = NSHomeDirectory();
// 拼接 Documents 文件夹的路径
// stringByAppendingPathComponent 使用这个方法,默认会在 Documents 前面添加一个/ 变成 /Documents 添加到沙盒路径后面
NSString *documentsPath = [homePath stringByAppendingPathComponent:@"Documents"];
NSLog(@"%@",documentsPath);
打印的 Documents 文件路径:
2015-04-23 14:44:54.145 block[7267:2723774] /Users/zhuohong_xiao/Library/Developer/CoreSimulator/Devices/390F60C7-3159-4452-AA5E-8D9DC4C9964E/data/Containers/Data/Application/D668A28B-7A84-41EE-B429-901ED769EBD9/Documents
这种获取文件夹路径的方式我们是不推荐使用。
因为新版本的操作系统可能会修改目录名
第二种方式:苹果为我们提供了一种查找文件夹的函数:
NSSearchPathForDirectoriesInDomains函数 。
(返回值是一个数组。在iOS中,只有一个目录跟传入的参数匹配,所以这个集合里面只有一个元素,我们只要取最后一个或者第一个或者第0个即可)
/* 关于参数
NSSearchPathDirectory directory :要查找路径的文件夹
( NSDocumentDirectory:Document 文件夹 )
(NSDocumentationDirectory 这个文件夹不是我们要的)
NSSearchPathDomainMask domainMask : 在那个范围里面查找文件路径(Domain:范围。)
NSUserDomainMask 这个参数一般是固定的:在用户的文件范围内
BOOL expandTilde :是否展开理解;YES:展开路径,NO:不展开路径(不展开会将沙盒路径一 ~ 来替代)。
*/
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSLog(@"%@",documentsPath);
打印的路径:
2015-04-23 15:10:07.573 block[7308:2829321] /Users/zhuohong_xiao/Library/Developer/CoreSimulator/Devices/390F60C7-3159-4452-AA5E-8D9DC4C9964E/data/Containers/Data/Application/1735DE73-409F-47E4-88D4-A3E83FA21615/Documents
重要的提示:
我们要通过沙盒路径拼接文件夹路径的和使用函数搜寻路径的,只有 Documents 和 Library/Caches 这两个。偏好设置是系统管理的。有直接的类管理。 tmp 路径有直接的函数可以直接的获取路径。
2.5、 应用沙盒目录里面的tmp文件夹的获取方式(这个有点特殊,系统直接提供函数)
NSString *tmpPath = NSTemporaryDirectory();
NSLog(@"%@",tmpPath);
打印的结果:
2015-04-23 20:22:46.263 block[1160:237773] /Users/zhuohong_xiao/Library/Developer/CoreSimulator/Devices/390F60C7-3159-4452-AA5E-8D9DC4C9964E/data/Containers/Data/Application/61ABED27-F0D9-4114-99F8-04B3D6F2E233/tmp/
2.6、 应用沙盒目录里面的Library/Preference文件夹的使用(系统提供类管理)
Library/Preference:是由系统管理的,系统直接提供一个 NSUserDefaults类来管理。
3、XML属性列表(plist)归档和解档
属性列表
属性列表是一种XML格式的文件,拓展名为plist(苹果特有的格式)
属性列表-NSDictionary(NSArray数组)的存储和读取过程示例图:
使用plist存储数据的注意点:
plist文件只能归档(存储)字典和数组,字典和数组里面保存的数据必须是 Boolean,Data,Date,Number,String 这几种系统自带的对象类型。就可以使用writeToFile:atomically:方法直接将对象写到属性列表文件中。(我们自己创建的对象要通过归档和解档的方式来存储)
示例图:
(plist文件存储 ,只要一个对象有writeToFile方法,就可以保存到Plist。)
3.1、将数组或字典数据写入到plist文件保存到沙盒的文件夹里面。
pist存储是将数组和字典保存的文件当中
// 定义一个要保存的数组
NSArray *array = @[@"张三",@"李四",@"王五"];
// 获取要保存到沙盒里面的文件夹的路径
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// 拼接要保存的文件的全路径
NSString *filePath = [documentsPath stringByAppendingPathComponent:@"array.plist"];
// File: 这个一般都要写全路径
// atomically:这个是否原则性存储,YES:当写入到文件夹的数据不完整的时候不保存为文件。NO:当写入到文件夹的数据不完整的时候,也将数据保存为文件。
[array writeToFile:filePath atomically:YES];
存储后的结果:
3.2、将plist文件的数据进行读取
对plist文件的数据进行读取的时候,要注意XMLplist文件的根节点是数组还是字典
// 获取要plist文件保存到沙盒里面的文件夹的路径
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// 拼接plist文件的全路径
NSString *filePath = [documentsPath stringByAppendingPathComponent:@"array.plist"];
// 由于我们存的时候是数组,取的时候也必须用数组接(如果是字典的也是这样)
NSArray *arrayData = [NSArray arrayWithContentsOfFile:filePath];
NSLog(@"%@",arrayData);
打印的结果:
结果是没有转 utf8 的格式
2015-04-23 20:58:02.499 block[1380:385865] (
"\U5f20\U4e09",
"\U674e\U56db",
"\U738b\U4e94"
)
4、偏好设置存储
很多iOS应用都支持偏好设置,比如保存用户名、密码、字体大小等设置。
iOS提供了一套标准的解决方案来为应用加入偏好设置功能。
偏好设置存储是不需要我们管理的,偏好设置是由系统管理的,系统会提供一个类来让我们对偏好设置进行管理
NSUserDefaults :用户的默认设置。
SUserDefaults 这个类是专门用来偏好设置的存储
偏好设置存储是简单的做以一些键值对的设置就可以了(字典)
偏好设置底层就是包装了一个字典(使用plist进行存储)
使用偏好设置的好处:
1、偏好设置存储是不用关心存储的文件名()
2、快速的进行一些键值对的存储
偏好设置存储使用的注意点:
在ios8之前,进行偏好设置的存储,我们需要做一个同步操作 (同步:将缓存数据保存到硬盘上)
// 偏好设置存储只需要一行代码
[[NSUserDefaults standardUserDefaults] setObject:@"张三" forKey:@"name"];
[[NSUserDefaults standardUserDefaults] setInteger:20 forKey:@"age"];
[[NSUserDefaults standardUserDefaults] setFloat:170.5 forKey:@"height"];
// 适配ios8 做一次同步操作
[[NSUserDefaults standardUserDefaults] synchronize];
// 获取偏好设置存储的数据
NSString *userName = [[NSUserDefaults standardUserDefaults] objectForKey:@"name"];
NSString *userAge = [[NSUserDefaults standardUserDefaults] objectForKey:@"age"];
NSString *userHeight = [[NSUserDefaults standardUserDefaults] objectForKey:@"height"];
NSLog(@"name = %@, age = %@, height = %@",userName,userAge,userHeight);
打印的结果:
2015-04-23 16:36:17.896 block[718:90672] name = 张三, age = 20, height = 170.5
5、NSKeyedArchiver 归档 和解档
如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复。
不是所有的对象都可以直接用这种方法进行归档,只有遵守了NSCoding协议的对象才可以 实现了NSCoding协议的2个方法
encodeWithCoder:
每次归档对象时,都会调用这个方法。一般在这个方法里面指定如何归档对象中的每个实例变量,可以使用encodeObject:forKey:方法归档实例变量
initWithCoder:
每次从文件中恢复(解码)对象时,都会调用这个方法。一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用decodeObject:forKey方法解码实例变量
5.1、NSKeyedArchiver-归档NSArray(可以直接归档的示例)
归档一个NSArray对象到Documents/array.archive
NSArray *array = [NSArray arrayWithObjects:@”a”,@”b”,nil];
[NSKeyedArchiver archiveRootObject:array toFile:path];
恢复(解码)NSArray对象
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
5.2、自定义对象的归档和解档
我们自定义的对象要存储,我们要使用归档,
我们要将我们保存的自定义对象的归档的文件进行解析就需要解档。
NSKeyedArchiver 这个类是用来给对象进行归档和解档的
/*
[NSKeyedArchiver archiveRootObject: toFile:]
archiveRootObject:要归档那个对象
toFile:归档对象要保存的全路径(全路径包括文件名和类型)
*/
这个是对象的归档的过程:
// 创建一个对象
Person *person = [[Person alloc] init];
// 设置对象的属性
person.name = @"zhangsan";
person.age = 20;
person.height = 170.5;
/*
一个对象要归档必须先遵守 person必须 <NSCoding> 这个协议
要将自定义的对象进行归档必须重写对象的 encodeWithCoder 方法
*/
// 1、获取要保存的文件夹的全路径(我们保存到缓存目录)
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 2、拼接要保存文件的全路径(关于保存文件的后缀可以随便写,一般写data(plist ,mp3 都可以,只是打不开))
NSString *filePath = [cachesPath stringByAppendingPathComponent:@"userObject.data"];
// 3、使用 archiveRootObject: toFile: 这个方法进行对象的归档
[NSKeyedArchiver archiveRootObject:person toFile:filePath];
这个是对象的解档的过程:
// 创建一个对象
Person *person = [[Person alloc] init];
/*
一个对象要归档必须先遵守 <NSCoding> 这个协议
要将自定义的对象进行归档必须重写对象的 initWithCoder:方法
*/
// 获取要保存归档文件的文件夹的全路径(我们保存到缓存目录)
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 拼接要保存归档文件的全路径(关于保存文件的后缀可以随便写,一般写data(plist ,mp3 都可以,只是打不开))
NSString *filePath = [cachesPath stringByAppendingPathComponent:@"userObject.data"];
person = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"name:%@ ,age:%ld, height:%f", person.name, person.age, person.height);
打印的结果:
2015-04-23 18:43:51.662 block[1443:347576] name:zhangsan ,age:20, height:170.500000
注意:要对一个对象进行归档和解档,
1、这个对象必须遵守 < NSCoding>协议。
2、必须实现 encodeWithCoder: 这个归档的方法 和 initWithCoder 这个解档方法。
person 对象的.h 文件和 .m 文件
//
// Person.h
// block
//
// Created by 肖卓鸿 on 15/4/23.
// Copyright (c) 2015年 肖卓鸿. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface Person : NSObject <NSCoding>
@property (copy, nonatomic)NSString *name;
@property (assign, nonatomic)NSInteger age;
@property (assign, nonatomic)CGFloat height;
@end
//
// Person.m
// block
//
// Created by 肖卓鸿 on 15/4/23.
// Copyright (c) 2015年 肖卓鸿. All rights reserved.
//
#import "Person.h"
@implementation Person
// 这个方法是对象归档的时候调用
// 作用:
// 告诉
// 1、这个对象的那些属性需要归档
// 2、这些属性是怎么归档。
- (void)encodeWithCoder:(NSCoder *)aCoder {
/*
什么时候使用 [super encodeWithCoder:aCoder]; 父类遵守<NSCoding>
(父类遵守了<NSCoding> 协议,说明父类有些属性是需要归档,要不就会出现莫名其妙的问题就)
*/
[aCoder encodeObject:_name forKey:@"NameKey"];
[aCoder encodeInteger:_age forKey:@"AgeKey"];
[aCoder encodeFloat:_height forKey:@"HeightKey"];
}
// 这个方法是对象的解档的时候调用
// 作用:
// 告诉
// 1、这个对象的那些属性需要解档
// 2、这些属性是怎么解档
- (id)initWithCoder:(NSCoder *)aDecoder {
/*
什么时候使用 [super init]; 父类没有遵守<NSCoding>
什么时候使用 [super initWithCoder:aDecoder]; 父类遵守<NSCoding>
(父类遵守了<NSCoding> 协议,说明父类有些属性是需要解析的,不解析就会出现问题)
*/
if (self = [super init]) {
_name = [aDecoder decodeObjectForKey:@"NameKey"];
_age = [aDecoder decodeIntegerForKey:@"AgeKey"];
_height = [aDecoder decodeFloatForKey:@"HeightKey"];
}
return self;
}
@end
NSKeyedArchiver-归档对象的注意:
如果父类也遵守了NSCoding协议,请注意:
应该在encodeWithCoder:方法中加上一句
[super encodeWithCode:encode];
确保继承的实例变量也能被编码,即也能被归档
应该在initWithCoder:方法中加上一句
self = [super initWithCoder:decoder];
确保继承的实例变量也能被解码,即也能被恢复
5.3、NSData的归档(多个对象的归档)
使用archiveRootObject:toFile:方法可以将一个对象直接写入到一个文件中,但有时候可能想将多个对象写入到同一个文件中,那么就要使用NSData来进行归档对象
NSData可以为一些数据提供临时存储空间,以便随后写入文件,或者存放从磁盘读取的文件内容。可以使用[NSMutableData data]创建可变数据空间。
NSData-归档2个Person对象到同一文件中
归档(编码)
// 新建一块可变数据区
NSMutableData *data = [NSMutableData data];
// 将数据区连接到一个NSKeyedArchiver对象
NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
// 开始存档对象,存档的数据都会存储到NSMutableData中
[archiver encodeObject:person1 forKey:@"person1"];
[archiver encodeObject:person2 forKey:@"person2"];
// 存档完毕(一定要调用这个方法)
[archiver finishEncoding];
// 将存档的数据写入文件
[data writeToFile:path atomically:YES];
NSData-从同一文件中恢复2个Person对象
恢复(解码)
// 从文件中读取数据
NSData *data = [NSData dataWithContentsOfFile:path];
// 根据数据,解析成一个NSKeyedUnarchiver对象
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
Person *person1 = [unarchiver decodeObjectForKey:@"person1"];
Person *person2 = [unarchiver decodeObjectForKey:@"person2"];
// 恢复完毕
[unarchiver finishDecoding];
5.4、利用归档实现深复制
比如对一个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
6、SQLite3
6.1 SQLite3 和数据库知识的介绍
什么是SQLite
SQLite是一款轻型的嵌入式数据库
它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了
它的处理速度比Mysql、PostgreSQL这两款著名的数据库都还快
什么是数据库
数据库(Database)是按照数据结构来组织、存储和管理数据的仓库
数据库可以分为2大种类
关系型数据库(主流)
对象型数据库
常用关系型数据库
PC端:Oracle、MySQL、SQL Server、Access、DB2、Sybase
嵌入式\移动客户端:SQLite
数据库是如何存储数据的
数据库的存储结构和excel很像,以表(table)为单位
数据库存储数据的步骤
新建一个数据库
新建一张表(table)
添加多个字段(column,列,属性)
添加多行记录(row,每行存放多个字段对应的值)
疑惑点:
SQLite3是无类型的,意味着你可以保存任何类型的数据到任意表的任意字段中。比如下列的创表语句是合法的:
create table t_person(name, age);
为了保证可读性,建议还是把字段类型加上:
create table t_person(name text, age integer);
SQLite3常用的5种数据类型:text、integer、float、boolean、blob
6.1 SQLite3 的使用
要使用 SQLite3 我们就必须要添加一个类库。