iOS本地数据保存有多种方式,比如NSUserDefaults
、归档
、plist文件保存
、数据库
、CoreData
、KeyChain(钥匙串)
等多种方式。其中KeyChain(钥匙串)
是保存到沙盒范围以外的地方,也就是与沙盒无关。
沙盒
- 每个iOS应用程序都有自己的独立目录,这个目录就是应用程序的沙盒。我们可以通过NSHomeDirectory()获取当前应用的家目录,也就是当前应用程序沙盒的根目录。
- Xcode5以下和Xcode6以上沙盒目录文件夹个数不一样,Xcode6以上有3个文件夹,如下图所示,而Xcode5以下多了一个项目名字.app的文件,比如项目名字为Demo,则沙盒中文件为Demo.app。
- Xcode5以下和Xcode6以上沙盒的模拟器路径也有变化。如下图为Xcode6沙盒模拟器路径:
-
沙盒目录文件分析
- Documents:保存用户产生的数据,iTunes同步设备的时候会备份该目录。用户产生的数据就是指用户在使用当前app的时候保存的一些数据,比如保存app中的图片、保存下载的文件等。
- Library:这个目录下有2个文件夹,一个是Caches、一个是Preferences,Caches主要保存缓存数据,比如SDWebImage把缓存的图片就存放到该目录下。当程序退出后,改目录保存的文件一直存在。
Preferences在Xcode6之前保存的是偏好设置,比如NSUserDefaults保存的文件。但是Xcode6以上就保存到/Users/用户名/Library/ Developer/CoreSimulator/Devices/模拟器UDID/data/Library/Preferences/文件夹下。 - tmp:保存程序中的临时数据,当程序退出后系统会自动删除tmp中所有的文件。
各个目录的获取方法
Documents:
路径获取有3种方法
- 利用字符串拼接,在home目录后面拼接字符串Documents。
//获取家目录
NSString *homeDocumentPath = NSHomeDirectory();
//拼接
NSString *documents = [homeDocumentPath stringByAppendingPathComponent:@"Documents"];
- NSSearchPathForDirectoriesInDomains方法
// 获取documents目录
//参数1:表示Documents目录。参数2:当前用户目录(user's home directory --- place to install user's personal items)。 参数3:YES 代表展开路径中的波浪字符“~”
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// 只有一个documents
NSString *documents = [paths lastObject];
- NSFileManager
//获取documents路径
[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]
iOS本地数据保存常用方式
- NSUserDefaults
- 归档(序列化)
- plist文件
- 数据库
- CoreData
- KeyChain
1. NSUserDefaults
NSUserDefaults 是一个单例对象,在整个应用程序的生命周期中都只有一个实例。
NSUserDefaults保存的数据类型有:NSNumber, 基本数据类型(int,NSInter,float,double,CGFlat......), NSString, NSData, NSArray, NSDictionary, NSURL。
NSUserDefaults一般保存配置信息,比如用户名、密码、是否保存用户名和密码、是否离线下载等一些配置条件信息。
基本用法:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//保存值(key值同名的时候会覆盖的)
[defaults setObject:@"用户名" forKey:kUsernameKey];
//立即保存
[defaults synchronize];
//取值
NSString *username = [defaults objectForKey:kUsernameKey];
- 同样,保存还有一些方法,比如:
//保存NSInteger
[defaults setInteger:(NSInteger) forKey:(nonnull NSString *)];
//保存BOOL
[defaults setBool:(BOOL) forKey:(nonnull NSString *)];
//保存NSURL
[defaults setURL:(nullable NSURL *) forKey:(nonnull NSString *)];
//保存float
[defaults setFloat:(float) forKey:(nonnull NSString *)];
//保存double
[defaults setDouble:(double) forKey:(nonnull NSString *)];
- 取值另外方法:
//取值
[defaults integerForKey:(nonnull NSString *)];
[defaults boolForKey:(nonnull NSString *)];
[defaults URLForKey:(nonnull NSString *)];
[defaults floatForKey:(nonnull NSString *)];
[defaults doubleForKey:(nonnull NSString *)];
- 删除方法:
//删除指定key的数据
[defaults removeObjectForKey:(nonnull NSString *)];
synchronize。立即保存,苹果文档这么说:
Writes any modifications to the persistent domains to disk and updates all unmodified persistent domains to what is on disk.
Return Value YES if the data was saved successfully to disk, otherwise NO.
2. 归档(序列化)
一般保存自定义的对象,但是只有遵守NSCoding的类才能只用归档。
-
准守NSCoding协议必须要实现两个require方法
(void)encodeWithCoder:(NSCoder *)aCoder //归档会触发
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder //解归档会触发
Coding类具体实现:
@interface Coding : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
3. plist文件保存
- 一般在iOS用plist保存,plist本身就是XML文件,名字后缀为.plist。
- plist主要保存的数据类型为NSString、NSNumber、NSData、NSArray、NSDictionary。
- 具体实现:
//把字典写入到plist文件,比如文件path为:~/Documents/data.plist
[dictionary writeToFile:path atomically:YES];
//把数组写入到plist文件中
[array writeToFile:path atomically:YES];
- 读取数据
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:(nonnull NSString *)]];
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:(nullable NSString *) ofType:(nullable NSString *)]];
NSArray *array = [NSArray arrayWithContentsOfURL:[NSURL fileURLWithPath:(nonnull NSString *)]];
NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:(nullable NSString *) ofType:(nullable NSString *)]];
4. 数据库
- 保存大量数据可以优先考虑用数据库,sql语句对查询操作有优化作用,所以从查询速度或者插入效率都是很高的。
-
sqlite使用步骤:
- 指定数据库路径。
- 创建sqlite3对象并且打开数据库。
- 创建表。
- 对数据库操作,包括增删改查。
- 关闭数据库。
- 具体实现:
- 数据库路径
//返回数据库路径,保存到Cache目录下
-(NSString *)databasePath
{
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
return [path stringByAppendingPathComponent:@"contacts.db"];
}
- 创建sqlite3对象并且打开数据库,如果数据库打开成功,就创建表。
//数据库对象
sqlite3 *contactDB;
const char *path = [[self databasePath] UTF8String];
if (sqlite3_open(path, &contactDB) == SQLITE_OK)
{
char *errMsg;
const char *sql_stmt = "CREATE TABLE IF NOT EXISTS CONTACTS(ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT, ADDRESS TEXT,PHONE TEXT)";
//执行语句
if (sqlite3_exec(contactDB, sql_stmt, NULL, NULL, &errMsg) != SQLITE_OK)
{
//创建表失败
}
}
else
{
//打开数据库失败
}
sqlite3_close(contactDB);
-
代码解释:
- sqlite3_open:打开指定路径的数据库,如果数据库不存在,就会创建一个新的数据库。
- SQLITE_OK 是一个常量,表示打开数据库成功。下面是苹果的定义:
-
define SQLITE_OK 0 / Successful result /
- contactDB 就是数据库对象。
- sqlite3_exec就是执行sql语句方法。
- sqlite3_close关闭数据库,一般暂时不用数据库的时候手动关闭,防止资源浪费。
保存数据到数据库
//是一个抽象类型,是一个句柄,在使用过程中一般以它的指针进行操作
sqlite3_stmt *statement;
//数据库路径
const char *path = [[self databasePath] UTF8String];
//使用的时候打开数据库
if (sqlite3_open(path, &contactDB) == SQLITE_OK)
{
NSString *insertSQL = [NSString stringWithFormat:@"INSERT INTO CONTACTS (name,address,phone) VALUES(\"%@\",\"%@\",\"%@\")",name.text,address.text,phone.text];
const char *insert_stmt = [insertSQL UTF8String];
// 这个函数将sql文本转换成一个准备语句(prepared statement)对象,同时返回这个对象的指针。这个接口需要一个数据库连接指针以及一个要准备的包含SQL语句的文本。它实际上并不执行这个SQL语句,它仅仅为执行准备这个sql语句
sqlite3_prepare_v2(contactDB, insert_stmt, -1, &statement, NULL);
//执行这个sql
if (sqlite3_step(statement) == SQLITE_DONE)
{
//TODO:已存储到数据库;
}
else
{
//TODO:保存失败
}
//销毁statement对象
sqlite3_finalize(statement);
//关闭数据库
sqlite3_close(contactDB);
}
- 查询操作
//数据库路径
const char *path = [[self databasePath] UTF8String];
//查询结果集对象句柄
sqlite3_stmt *statement;
//打开数据库
if (sqlite3_open(path, &contactDB) == SQLITE_OK)
{
//查询的sql语句
NSString *querySQL = [NSString stringWithFormat:@"SELECT address,phone from contacts where name=\"%@\"",name.text];
const char *query_stmt = [querySQL UTF8String];
//执行查询sql语句
if (sqlite3_prepare_v2(contactDB, query_stmt, -1, &statement, NULL) == SQLITE_OK)
{
//遍历每条数据
if (sqlite3_step(statement) == SQLITE_ROW)
{
//获取每条数据的字段。
NSString *addressField = [[NSString alloc] initWithUTF8String:(const char *)sqlite3_column_text(statement, 0)];
address.text = addressField;
NSString *phoneField = [[NSString alloc] initWithUTF8String:(const char *)sqlite3_column_text(statement, 1 )];
phone.text = phoneField;
//TODO:已查到结果
}
else
{
//TODO:未查到结果
}
sqlite3_finalize(statement);
}
sqlite3_close(contactDB);
}
5. CoreData
CoreData是一门功能强大的数据持久化技术,位于SQLite数据库之上,它避免了SQL的复杂性,能让我们以更自然的方式与数据库进行交互。CoreData提供数据--OC对象映射关系来实现数据与对象管理,这样无需任何SQL语句就能操作他们。
CoreData数据持久化框架是Cocoa API的一部分,⾸次在iOS5 版本的系统中出现,它允许按照实体-属性-值模型组织数据,并以XML、⼆进制文件或者SQLite数据⽂件的格式持久化数据
CoreData与SQLite进行对比
SQLite
1、基于C接口,需要使用SQL语句,代码繁琐
2、在处理大量数据时,表关系更直观
3、在OC中不是可视化,不易理解
CoreData
1、可视化,且具有undo/redo能力
2、可以实现多种文件格式:
* NSSQLiteStoreType
* NSBinaryStoreType
* NSInMemoryStoreType
* NSXMLStoreTyp
3、苹果官方API支持,与iOS结合更紧密
CoreData核心类与结构
NSManagedObjectContext(数据上下文)
- 对象管理上下文,负责数据的实际操作(重要)
- 作用:插入数据,查询数据,删除数据,更新数据
NSPersistentStoreCoordinator(持久化存储助理)
- 相当于数据库的连接器
- 作用:设置数据存储的名字,位置,存储方式,和存储时机
NSManagedObjectModel(数据模型)
- 数据库所有表格或数据结构,包含各实体的定义信息
- 作用:添加实体的属性,建立属性之间的关系
- 操作方法:视图编辑器,或代码
NSManagedObject(被管理的数据记录)
- 数据库中的表格记录
NSEntityDescription(实体结构)
- 相当于表格结构
NSFetchRequest(数据请求)
- 相当于查询语句
后缀为.xcdatamodeld的包
- 里面是.xcdatamodel文件,用数据模型编辑器编辑
- 编译后为.momd或.mom文件
各类之间关系图
6. KeyChain
- 钥匙串(英文: KeyChain)是苹果公司Mac OS中的密码管理系统。
- 一个钥匙串可以包含多种类型的数据:密码(包括网站,FTP服务器,SSH帐户,网络共享,无线网络,群组软件,加密磁盘镜像等),私钥,电子证书和加密笔记等。
- iOS的KeyChain服务提供了一种安全的保存私密信息(密码,序列号,证书等)的方式。每个iOS程序都有一个独立的KeyChain存储。从iOS 3.0开始,跨程序分享KeyChain变得可行。
- 当应用程序被删除后,保存到KeyChain里面的数据不会被删除,所以KeyChain是保存到沙盒范围以外的地方。
- KeyChain的所有数据也都是以key-value的形式存储的,这和NSDictionary的存储方式一样。
- 相比于NSUserDefaults来说,KeyChain保存更为安全,而且KeyChain里面保存的数据不会因为app删除而丢失。