iOS常用的存储方式介绍
在iOS App开发过程中经常需要操作一些需要持续性保留的数据,比如用户对于App的相关设置、需要在本地缓存的数据等等。本文针对OC中经常使用的一下存储方式做了个整理。
常用的存储工具/方式:
NSUserDefaults类
Plist文件
解归档
手动存放沙盒
// sqlite
CoreData
第三方数据存储框架
1. NSUserDefaults
一般对于一些基本的用户设置,因为数据量很小,我们可以使用OC语言中的 NSUserDefaults类来进行处理。使用方法很简单,只需要调用类中的方法即可:
NSMutableArray *mutArr = [[NSMutableArray alloc]initWithObjects:@"1", nil];
//存入数组并同步
[[NSUserDefaults standardUserDefaults] setObject:mutArr forKey:@"mutableArr"];
[[NSUserDefaults standardUserDefaults] synchronize];
//读取存入的数组 打印
NSArray *arr = [[NSUserDefaults standardUserDefaults] objectForKey:@"mutableArr"];
NSLog(@"%@",arr);
NSUserDefaults类除了可以存储数组、字典、NSdata外,还可以直接存储OC基本类型属性。但是不能直接作用到自定义对象,如果是自定义对象需要进行归档操作,这里后面会讲到。
2. Plist文件
Plist文件作为Xcode的一种资源包,也可以作为一种存储工具。
1.在项目中创建Plist文件。 在项目中创建的好处是文件的可视化,我们可以很直观的看到文件的内容,同时Xcode还提供了直接操作文件的功能。便于我们对文件内容的增删改查。这种方式的缺点是项目中的plist文件一般作为固态的数据形势保存,对于经常需要改动的数据就不好操作了。
获取文件中的数据代码
NSString *string = [[NSBundle mainBundle] pathForResource:@"testPlist" ofType:@"plist"];
NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:string];
NSLog(@"%@",dic); //打印文件中的内容
2.代码读写Plist文件。避免了在项目中创建Plist文件导致不便更改的麻烦。
//创建一个plist文件 testPlist
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *plistpath = [paths objectAtIndex:0];
NSLog(@"path = %@",plistpath);
NSString *filename=[plistpath stringByAppendingPathComponent:@"testPlist.plist"];
NSFileManager* fm = [NSFileManager defaultManager];
[fm createFileAtPath:filename contents:nil attributes:nil];
//写入内容
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"test",nil];
[dic writeToFile:filename atomically:YES];
//读文件
NSDictionary* dic2 = [NSDictionary dictionaryWithContentsOfFile:filename];
NSLog(@"dic is:%@",dic2);
3.解归档
之前说了,不管是NSUserDefaults 或者是 plist 都不能对自定义的对象进行存储,OC提供了解归档恰好解决这个问题。 解归档针对的是一个对象,假设我们现在有一个TestModel的类,需要进行归档和接档,上代码。
对象的.h文件
#import <Foundation/Foundation.h>
@interface TestModel : NSObject <NSCoding> //解归档需要遵循Nscoding协议,并实现相关方法
@property (nonatomic,strong) NSString *name;
@property (nonatomic,assign) NSInteger age;
@property (nonatomic,strong) NSString *sex;
@end
对象的.m文件
#import "TestModel.h"
#define Name @"name"
#define Age @"age"
#define Sex @"sex"
@implementation TestModel
//需要实现NSCoding中的协议的两个方法
- (id)initWithCoder:(NSCoder *)aDecoder{
if (self == [super init]) {
self.name = [aDecoder decodeObjectForKey:Name];
self.sex = [aDecoder decodeObjectForKey:Sex];
self.age = [[aDecoder decodeObjectForKey:Age] integerValue];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.name forKey:Name];
[aCoder encodeObject:self.sex forKey:Sex];
[aCoder encodeObject:[NSNumber numberWithInteger:self.age] forKey:Age];
}
- (NSString *)description{
return [NSString stringWithFormat:@"%@--%@--%ld岁",self.name,self.sex,(long)self.age];
}
@end
接下来只要使用解/归档辅助类就可以TestModel类进行解归档
//创建对象 并赋值
TestModel *model = [[TestModel alloc]init];
model.name = @"小明";
model.age = 25;
model.sex = @"man";
//归档
NSMutableData *data = [[NSMutableData alloc] init];
//创建归档辅助类
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
//编码
[archiver encodeObject:model forKey:@"model"];
//结束编码
[archiver finishEncoding];
//写入到沙盒
NSArray *array = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *fileName = [array.firstObject stringByAppendingPathComponent:@"archiverModel"];
if([data writeToFile:fileName atomically:YES]){
NSLog(@"归档成功");
}
//解档
NSData *undata = [[NSData alloc] initWithContentsOfFile:fileName];
//解档辅助类
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:undata];
//解码并解档出model
TestModel *unModel = [unarchiver decodeObjectForKey:@"model"];
NSLog(@"%@",unModel);
//关闭解档
[unarchiver finishDecoding];
运行代码之后 打印
4.手动存放沙盒
iphone沙箱模型的有四个文件夹,分别是documents,tmp,app,Library。
1、Documents 目录:您应该将所有的应用程序数据文件写入到这个目录下。这个目录用于存储用户数据或其它应该定期备份的信息。为了不让App的备份过于庞大,我们不建议在这里存放大容量的文件。
2、AppName.app 目录:这是应用程序的程序包目录,包含应用程序的本身。由于应用程序必须经过签名,所以您在运行时不能对这个目录中的内容进行修改,否则可能会使应用程序无法启动。
3、Library 目录:这个目录下有两个子目录:Caches 和 Preferences
Preferences 目录:包含应用程序的偏好设置文件。您不应该直接创建偏好设置文件,而是应该使用NSUserDefaults类来取得和设置应用程序的偏好.
Caches 目录:用于存放应用程序专用的支持文件,保存应用程序再次启动过程中需要的信息。细心的话你会发现几乎所有的第三方框架的缓存信息处理都在这个文件中,一般的大容量文件都放在这里。
4、tmp 目录:这个目录用于存放临时文件,保存应用程序再次启动过程中不需要的信息。Nsuserdefaults保存的文件一般在tmp文件夹里。
获取这些目录路径的方法:
1,获取家目录路径的函数:
NSString *homeDir = NSHomeDirectory();
2,获取Documents目录路径的方法:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
3,获取Caches目录路径的方法:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachesDir = [paths objectAtIndex:0];
4,获取tmp目录路径的方法:
NSString *tmpDir = NSTemporaryDirectory();
沙盒中只能保存OC中的基本数据,自定义的对象不能直接存入。 存入方式如下。
// 1,获取家目录路径的函数:
// NSString *homeDir = NSHomeDirectory();
// 2,获取Documents目录路径的方法:
// NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// NSString *docDir = [paths objectAtIndex:0];
// 3,获取Caches目录路径的方法:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachesDir = [paths objectAtIndex:0];
// 4,获取tmp目录路径的方法:
// NSString *tmpDir = NSTemporaryDirectory();
//假设我们需往cache 存入数据,并命名为test的txt格式文件中
NSString *filePath = [cachesDir stringByAppendingPathComponent:@"test.txt"];
NSArray *dic = [[NSArray alloc] initWithObjects:@"test",@"test1" ,nil];
if([dic writeToFile:filePath atomically:YES]){
NSLog(@"存入成功");
}
//取出数据 打印
NSLog(@"%@",[NSArray arrayWithContentsOfFile:filePath]);
打印如图:
上一篇文章介绍了OC内部一些方法进行数据的本地存储,其中包括 NSUser类、Plist文件、解归档、手动沙盒存储。这里将继续介绍其他的存储方式。本文主针对Sqlite的存储做一点介绍。
不管是CoreData还是大部分其他的第三方DB,因为数据的轻量级,都是基于sqlite实现存储的。目前使用的主流是sqlite3,为了帮助更好的理解(我自己也在初学 阶段),我们先从最基本的数据操作接触,然后再详细探究CoreData和其他的第三方库DB库。
1.理解sqlite
这里对于sqlite有一些常识性的解答,了解即可。OC中,对于Sqlite的支持使用的是一套C语言的API,对于OC开发者来说,使用来并不是难事。
2.基本的sqlite操作/使用。
对于我们普通的使用人员来说,我们关心的是如何使用数据达到存储数据的要求。接下来,我就使用一个小小的案例展示:
1).搭建sqlite使用环境。
2).创建数据/关闭数据库
3).对数据库中的数据进行增删改查
1).Xcode项目中,搭建sqlite使用环境。
添加sqlite依赖库,target >build phases> link binary with libraries ,搜索sqlite ,发现有两个库类:libsqlite3.tdb,libsqlite3.0.tdb,理论上来说添加哪一个都是一样的,不同的是libsqlite3.0.tdb 指向的是库的当前版本最新的libsqlite3.tdb,也即是当Xcode库更新,程序依赖的库也随之更新。如非必要,建议添加 libsqlite3.0.tdb。
比如我们需要写一个操作sqlite 的工具 SqliteTool,为了保证当前数据库在工程里是唯一的,我们用单例的方式创建SqliteTool。在这之前应该为SqliteTool添加头文件 #import <sqlite3.h>。
SqliteTool.h 文件
#import <Foundation/Foundation.h>
#import <sqlite3.h>
@interface SqliteTool : NSObject
+ (SqliteTool *)shareinstance;
@end
.m文件
#import "SqliteTool.h"
#import "TestModel.h"
@implementation SqliteTool
{
sqlite3 *_dbPoint; //用于保存数据库对象的地址
}
+ (SqliteTool *)shareinstance{
static SqliteTool *tool = nil;
static dispatch_once_t oneToken;
dispatch_once(&oneToken, ^{
tool=[[SqliteTool alloc] init];
});
return tool;
}
这样,在SqliteTool中我们配置好了使用环境。下一步是使用。
2).创建数据/关闭数据库
打开数据库。iOS 中,数据库存储在沙盒中,上一篇讲到,如果数据并不是很大并希望跟随应用备份,那么数据应该存Documents文件中。
我们给SqliteTool添加一个方法
- (BOOL)creatSqliteDB
创建数据库:
//如果系统根据这个文件路径查找的时候有对应文件则直接打开数据库,如果没有则会创建一个相应的数据库
- (BOOL)creatSqliteDB{
NSString *sqitePath = [NSHomeDirectory() stringByAppendingPathComponent:@"testModel.sqlite"];
int result = sqlite3_open([sqitePath UTF8String], &_dbPoint); //在指定路径下,创建一个数据,并将数据库的地址赋值给_dbPoint
if (result == SQLITE_OK) {
NSLog(@"数据库打开成功");
NSLog(@"%@",sqitePath);
return YES;
}else{
NSLog(@"数据库打开失败");
}
return NO;
}
给SqliteTool添加一个方法
- (BOOL)closeSqliteDB
关闭数据库
- (BOOL)closeSqliteDB{
int result = sqlite3_close(_dbPoint);
if (result==SQLITE_OK) {
NSLog(@"数据库关闭成功");
return YES;
}else{
NSLog(@"数据库关闭失败");
}
return NO;
}
3).使用数据库进行数据的增删改查
创建好了Sqlite,接下来我们要使用SqliteTool工具对TestModel类进行存储了。 当然基本数据类型一样可以存储。 假设TestModel类的属性是这样的:
.h
#import <Foundation/Foundation.h>
@interface TestModel : NSObject <NSCoding>
@property (nonatomic,strong) NSString *name;
@property (nonatomic,assign) NSInteger age;
@property (nonatomic,strong) NSString *sex;
@end
我希望SqlilteTool能够存贮这个testModel,往下看。
我们在存储数据的时候,先要在数据库中建立一张表,将数据存入表中。之后对表中的数据进行增删改查。意思就是将数据都写入一张表格里面,我们通过对表格操作来进行数据的增删改查。
SqliteTool.h 中声明以下方法
//创建一个存储列表。 一个数据库可以创建很多列表,用来存储不同的对象。
- (BOOL)createTable;
//往表中插入数据
- (void)insertModel:(TestModel *)model;
//更新表中的数据
- (void)updateModel:(TestModel *)model;
//删除表中的数据
- (void)deletedateModel:(TestModel *)model;
//查看表中的数据
- (NSMutableArray *)selectAllModel;
.m实现
创建table
/*
创建一个存储列表。 一个数据库可以创建很多列表,用来存储不同的对象。
primary key 是主键的意思,主健在当前表里数据是唯一的,不能重复,可以唯一标识一条数据,一般是整数
autoincrement自增,为了让主键不重复,会让主键采用自增的方式
if not exists 如果没有表才会创建,防止重复创建覆盖之前数据
*/
- (BOOL)createTable{
//语句中包涵的信息应该仔细, nsstring类型对应的是text 数组对应arr 整型对应integer 等等
NSString *sqlStr = @"create table if not exists test(number integer primary key autoincrement,name text,age integer,sex text)";
//执行这条sql语句
int result = sqlite3_exec(_dbPoint, [sqlStr UTF8String], nil, nil, nil);
if (result == SQLITE_OK) {
NSLog(@"表创建成功");
return YES;
}else{
NSLog(@"表创建失败");
}
return NO;
}
增
//往表中插入数据
- (BOOL)insertModel:(TestModel *)model{
NSString *sqlStr=[NSString stringWithFormat:@"insert into test (name,age,sex) values ('%@','%ld','%@')",model.name,model.age,model.sex];
//执行sql语句
int result = sqlite3_exec(_dbPoint, [sqlStr UTF8String], nil, nil, nil);
if (result == SQLITE_OK) {
NSLog(@"添加%@成功",model.name);
return YES;
}else {
NSLog(@"添加model失败");
}
return NO;
}
删
//删除表中的内容
- (BOOL)deletedateModel:(TestModel *)model{
NSString *sqlStr=[NSString stringWithFormat:@"delete from test where name='%@'",model.name];
// NSString *sqlStr=[NSString stringWithFormat:@"delete from test"]; //不添加添加条件则删除所有数据
//执行sql语句
int result = sqlite3_exec(_dbPoint, [sqlStr UTF8String], nil, nil, nil);
if (result == SQLITE_OK) {
NSLog(@"删除%@成功",model.name);
return YES;
}else {
NSLog(@"删除失败");
}
return NO;
}
改
//更新表中的数据
- (BOOL)updateModel:(TestModel *)model{
NSString *sqlStr= [NSString stringWithFormat:@"update test set sex='%@',age=%ld where name='%@'",model.sex,model.age,model.name];
//执行sql语句
int result = sqlite3_exec(_dbPoint, [sqlStr UTF8String], nil, nil, nil);
if (result == SQLITE_OK) {
NSLog(@"修改成功");
return YES;
}else {
NSLog(@"修改失败");
}
return NO;
}
查
/*
查询逻辑
1.先从本地的数据库中读取某张表里的所有数据
2.然后逐条进行读取,对model进行赋值
3.把已经赋值好得model放到数组中,并且返回
4.在语句里*是通配符的意思,通过一个*相当于代替了表里的所有的字段名
5.接下来需要定义一个跟随指针,它用来遍历数据库表中的每行数据
6.第三个参数:查询语句字数限制,-1是没有限制
*/
//查看表中的数据
- (NSMutableArray *)selectAllModel{
NSString *sqlStr=@"select * from test";
sqlite3_stmt *stmt=nil;
int result=sqlite3_prepare_v2(_dbPoint, [sqlStr UTF8String], -1, &stmt, nil);//这个方法相当于把数据库和跟随指针关联,一同完成查询功能
NSMutableArray *modelArr = [NSMutableArray array];
//初始化学生类数组 获取遍历得到的数据
if (result == SQLITE_OK) {
NSLog(@"查询成功");
//开始遍历查询数据库的每一行数据
while (sqlite3_step(stmt) == SQLITE_ROW) {
//让跟随指针进行遍历查询,如果没有行,才会停止循环
//满足条件,则逐列的读取内容
//第二个参数表示当前这列数据在表的第几列
const unsigned char *name = sqlite3_column_text(stmt, 1);
int age = sqlite3_column_int(stmt, 2);
const unsigned char *sex = sqlite3_column_text(stmt,3);
//把列里的数据再进行类型的转换
NSInteger modelAge = age;
NSString *modelName = [NSString stringWithUTF8String:(const char *)name];
NSString *modelSex = [NSString stringWithUTF8String:(const char *)sex];
//给对象赋值,然后把对象放到数组里
TestModel *model = [[TestModel alloc] init];
model.name = modelName;
model.sex = modelSex;
model.age = modelAge;
[modelArr addObject:model];
}
}else{
NSLog(@"查询失败");
}
return modelArr;
}
方法写完,现在调用测试。
//创建对象 并赋值
TestModel *model = [[TestModel alloc]init];
model.name = @"小明";
model.age = 25;
model.sex = @"man";
TestModel *model1 = [[TestModel alloc]init];
model1.name = @"小花";
model1.age = 23;
model1.sex = @"woman";
if([[SqliteTool shareinstance] creatSqliteDB]){
if([[SqliteTool shareinstance] createTable]){
//增
if([[SqliteTool shareinstance] insertModel:model] && [[SqliteTool shareinstance] insertModel:model1]){
NSLog(@"增加model、model1");
NSLog(@"%@",[[SqliteTool shareinstance] selectAllModel]);
}
//删
if([[SqliteTool shareinstance] deletedateModel:model1]){
NSLog(@"删除model1");
NSLog(@"%@",[[SqliteTool shareinstance] selectAllModel]);
}
//改
model.age = 28;
//注意 这里因为是以名字作为索引对象,因此名字是不能修改的, 如果要修改名字,应该另外增加一个不变属性作为索引
if([[SqliteTool shareinstance] updateModel:model]){
NSLog(@"修改model");
NSLog(@"%@",[[SqliteTool shareinstance] selectAllModel]);
}
//查
NSLog(@"%@",[[SqliteTool shareinstance] selectAllModel]);
}
}
打印结果