iOS五种本地缓存数据方式
 

iOS本地缓存数据方式有五种:前言

1.直接写文件方式:可以存储的对象有NSString、NSArray、NSDictionary、NSData、NSNumber,数据全部存放在一个属性列表文件(*.plist文件)中。

2.NSUserDefaults(偏好设置),用来存储应用设置信息,文件放在perference目录下。

3.归档操作(NSkeyedArchiver),不同于前面两种,它可以把自定义对象存放在文件中。

4.coreData:coreData是苹果官方iOS5之后推出的综合型数据库,其使用了ORM(Object Relational Mapping)对象关系映射技术,将对象转换成数据,存储在本地数据库中。coreData为了提高效率,甚至将数据存储在不同的数据库中,且在使用的时候将本地数据放到内存中使得访问速度更快。我们可以选择coreData的数据存储方式,包括sqlite、xml等格式。但也正是coreData 是完全面向对象的,其在执行效率上比不上原生的数据库。除此之外,coreData拥有数据验证、undo等其他功能,在功能上是几种持久化方案最多的。

5.FMDB:FMDB是iOS平台的SQLite数据库框架,FMDB以OC的方式封装了SQLite的C语言API,使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码,对比苹果自带的Core Data框架,更加轻量级和灵活,提供了多线程安全的数据库操作方法,有效地防止数据混乱。

方式一:直接写文件

//获取沙盒中缓存文件夹路径

//方法一

//沙盒主目录

NSString *homePath = NSHomeDirectory();

//拼接路径

NSString *path = [homePath stringByAppendingPathComponent:@"Library/Caches"];

//方法二

//第一个参数目标文件夹目录(NSCachesDirectory查找缓存文件夹),第二个参数为查找目录的域(NSUserDomainMask为在用户目录下查找),第三个参数为结果中主目录是否展开,不展开则显示为~

NSArray *arr = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);

//虽然该方法返回的是一个数组,但是由于一个目标文件夹只有一个目录,所以数组中只有一个元素。

NSString *cachePath = [arr lastObject];

//或者

// NSString *cachePath = [arr objectAtIndex:0];

/**

//获取沙盒中Document文件夹或者tmp文件夹路径都可使用上面两种方法

//tmp文件夹路径可直接这样获取

NSString *tmpPath = NSTemporaryDirectory();

NSLog(@"%@",tmpPath);

**/

//拼接路径(目标路径),这个时候如果目录下不存在这个lotheve.plist文件,这个目录实际上是不存在的。

NSString *filePath = [cachePath stringByAppendingPathComponent:@"tese.plist"];

NSLog(@"%@",filePath);

//创建数据

NSDictionary *content = @{@"字典数据测试1":@"1",@"字典数据测试2":@"2",@"字典数据测试":@"3"};

//将数据存到目标路径的文件中(这个时候如果该路径下文件不存在将会自动创建)

//用writeToFile方法写文件会覆盖掉原来的内容

[content writeToFile:filePath atomically:YES];

//读取数据(通过字典的方式读出文件中的内容)

NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:filePath];

NSLog(@"%@",dic);

沙盒中Library/Caches目录下多了lotheve.plist文件:

文件内容:

如何获取模拟器沙盒路径:

打印日志,复制路径打开mac finder,点击左上角菜单前往,前往文件夹,把路径粘贴上去。

NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0];

NSLog(@"%@",path);

方式二:NSUserDefaults(偏好设置)

每个应用都有一个NSUesrDefaults实例,通过它可以存储应用配置信息以及用户信息,比如保存用户名、密码、字体大小、是否自动登录等等。数据自动保存在沙盒的Libarary/Preferences目录下。同样,该方法只能存取NSString、NSArray、NSDictionary、NSData、NSNumber类型的数据。

属性列表是一种明文的轻量级存储方式,其存储格式有多种,最常规格式为XML格式。在我们创建一个新的项目的时候,Xcode会自动生成一个info.plist文件用来存储项目的部分系统设置。plist只能用数组(NSArray)或者字典(NSDictionary)进行读取,由于属性列表本身不加密,所以安全性几乎可以说为零。因为,属性列表正常用于存储少量的并且不重要的数据。

在程序启动后,系统会自动创建一个NSUserDefaults的单例对象,我们可以获取这个单例来存储少量的数据,它会将输出存储在.plist格式的文件中。其优点是像字典一样的赋值方式方便简单,但缺点是无法存储自定义的数据。

代码示例:

//点击button保存数据

- (IBAction)saveData:(id)sender {

//获取NSUserDefaults对象

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];

//存数据,不需要设置路劲,NSUserDefaults将数据保存在preferences目录下

[userDefaults setObject:@"Lotheve" forKey:@"name"];

[userDefaults setObject:@"NSUserDefaults" forKey:@"demo"];

//立刻保存(同步)数据(如果不写这句话,会在将来某个时间点自动将数据保存在preferences目录下)

[userDefaults synchronize];

NSLog(@"数据已保存");

}

//点击button读取数据

- (IBAction)getData:(id)sender

{

//获取NSUserDefaults对象

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];

//读取数据

NSString *name = [userDefaults objectForKey:@"name"];

NSString *demo = [userDefaults objectForKey:@"demo"];

//打印数据

NSLog(@"name = %@ demo =%@",name,demo);

}

点击“保存数据”后,查看沙盒中的Libarary/ Preferences目录:

数据以plist的格式写入磁盘中了。点开查看数据:

方式三:NSKeyedArchiver(归档操作)

与属性列表相反,同样作为轻量级存储的持久化方案,数据归档是进行加密处理的,数据在经过归档处理会转换成二进制数据,所以安全性要远远高于属性列表。另外使用归档方式,我们可以将复杂的对象写入文件中,并且不管添加多少对象,将对象写入磁盘的方式都是一样的。

使用NSKeyedArchiver对自定义的数据进行序列化,并且保存在沙盒目录下。使用这种归档的前提是让存储的数据模型遵守NSCoding协议并且实现其两个协议方法。(当然,如果为了更加安全的存储,也可以遵守NSSecureCoding协议,这是iOS6之后新增的特性)

使用归档操作存储数据的主要好处是,不同于前面两种方法只能存储几个常用的数据类型的数据,NSKeyedArchiver可以存储自定义的对象。

代码示例:

先创建一个继承NSObject的类,该类遵守NSCoding协议

TestPerson.h

@interface TestPerson : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) NSInteger age;

@property (nonatomic, copy) NSString *sex;

@property (nonatomic, strong) NSArray *familyMumbers;

@end

TestPerson.m

#import "TestPerson.h"

@interface TestPerson ()

@end

@implementationTestPerson

- (void)viewDidLoad

{

[super viewDidLoad];

}

#pragma mark - NSCoding协议方法 (一定要实现)

//当进行归档操作的时候就会调用该方法

//在该方法中要写清楚要存储对象的哪些属性

- (void)encodeWithCoder:(NSCoder *)aCoder

{

NSLog(@"调用了encodeWithCoder方法");

[aCoder encodeObject:_name forKey:@"name"];

[aCoder encodeInteger:_age forKey:@"age"];

[aCoder encodeObject:_sex forKey:@"sex"];

[aCoder encodeObject:_familyMumbers forKey:@"familyMumbers"];

}

//当进行解档操作的时候就会调用该方法

//在该方法中要写清楚要提取对象的哪些属性

- (id)initWithCoder:(NSCoder *)aDecoder

{

NSLog(@"调用了initWithCoder方法");

if (self = [super init]) {

self.name = [aDecoder decodeObjectForKey:@"name"];

self.age = [aDecoder decodeIntegerForKey:@"age"];

self.sex = [aDecoder decodeObjectForKey:@"sex"];

_familyMumbers = [aDecoder decodeObjectForKey:@"familyMumbers"];

}

return self;

}

@end

这里还要讲一下一个小技巧:使用static修饰来替代宏定义。上面的序列化中,我们可以看到NSCoding的协议方法中对数据进行序列化并且使用一个key来保存它。正常情况下我们可以使用宏来定义key,但是过多的宏定义在编译时也会造成大量的损耗。这时候可以使用static定义静态变量来取代宏定义。

static NSString * const kUserNameKey = @"userName";

让自定义的数据遵循NSCoding协议后,我们就能使用NSKeyedArchiver和NSKeyedUnarchiver来对持久化的数据进行存取操作了:

- (IBAction)saveData:(id)sender

{

//创建一个自定义类的实例

_p = [[TestPerson alloc]init];

_p.name = @"Lotheve";

_p.age = 20;

_p.sex = @"m";

_p.familyMumbers = @[@"Father",@"Mather",@"Me"];

//获取文件路径

NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];

//文件类型可以随便取,不一定要正确的格式

NSString *targetPath = [docPath stringByAppendingPathComponent:@"lotheve.plist"];

//将自定义对象保存在指定路径下

[NSKeyedArchiver archiveRootObject:_p toFile:targetPath];

NSLog(@"文件已储存");

}

- (IBAction)getData:(id)sender

{

//获取文件路径

NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];

NSString *targetPath = [docPath stringByAppendingPathComponent:@"lotheve.plist"];

TestPerson *person = [NSKeyedUnarchiver unarchiveObjectWithFile:targetPath];

NSLog(@"name = %@ , age =%ld , sex = %@ , familyMubers = %@",person.name,person.age,person.sex,person.familyMumbers);

NSLog(@"文件已提取");

}

点击“保存数据”后,查看沙盒中Documents目录:

点击查看文件内容:

点击“提取数据”后打印结果:

方式四:coreData

coreData是iOS5之后苹果推出的数据持久化框架,其提供了ORM的功能,将对象和数据相互转换。其中,它提供了包括sqlite、xml、plist等本地存储文件,默认使用sqlite进行存储。coreData具有两个模型:关系模型和对象模型,关系模型即是数据库,对象模型为OC对象。其关系图如下:

由于我们不需要关心数据的存储,coreData使用起来算是最简单的持久化方案。要使用coreData有两个方式,一个是在创建项目的时候勾选use core data,另一个则是手动创建。在这里我们要讲解的是前者创建的方式:

1、创建新项目勾选使用coreData

 
 
 
 

2、创建关系模型,在这里我创建的模型名字是LXDCoreDataDemo(注意名字一定要和项目名称保持一致)

3、在创建的关系模型中添加实体,命名为Person,并且添加三个字段:name、age、score

到了这里我们的实体模型就创建好了,接下来就是通过NSManagedObject来将实体模型转换成对象。通过从coreData取出的对象,全部都是继承自NSManagedObject的子类。那么我们需要根据当前的关系模型来创建Person类

选择LXDCoreDataDemo -> Next -> Person -> Create,我们就创建好了Person,这时候三个成员属性都会自动添加完成

在执行操作的类实现文件中,我们要加入AppDelegate和Person的头文件,因为在创建项目的时候如果我们勾选了use core data的选项,appDelegate文件中会帮我们生成用于管理、存储这些模型的对象,我们可以通过添加头文件来使用。插入数据的代码如下:

//先取出coredata上下文管理者

AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];

NSManagedObjectContext *context = appDelegate.managedObjectContext;

//保存新数据

Person *person = [NSEntityDescription insertNewObjectForEntityForName: @"Person" inManagedObjectContext: context];

person.name = @"czk"

person.score = [NSNumber numberWithInt:100];

person.age = [NSNumber numberWithInt:25];

[appDelegate saveContext];

//查询所有数据

NSError *error;

NSFetchRequest *request = [NSFetchRequest new];

NSEntityDescription *entity = [NSEntityDescription entityForName: @"Person" inManagedObjectContext: context];

[request setEntity: entity];

NSArray *results = [[context executeFetchRequest: request error: &error] copy];

for (Person *p in results) {

NSLog(@"%@, %@, %@", p.name, p.age, p.score);

}

注意:如果出现崩溃异常,请卸载App后重新安装。

方式五:FMDB

优点:

对多线程的并发操作进行处理,所以是线程安全的;

以OC的方式封装了SQLite的C语言API,使用起来更加的方便;

FMDB是轻量级的框架,使用灵活。

缺点:

因为它是OC的语言封装的,只能在ios开发的时候使用,所以在实现跨平台操作的时候存在局限性。

FMDB有三个主要的类

(1)FMDatabase

一个FMDatabase对象就代表一个单独的SQLite数据库

用来执行SQL语句

(2)FMResultSet

使用FMDatabase执行查询后的结果集

(3)FMDatabaseQueue

用于在多线程中执行多个查询或更新,它是线程安全的

这里建议使用CocoaPods导入FMDB,关于CocoaPods的使用这里就不啰嗦了。

创建数据库:

db=[FMDatabasedatabaseWithPath:database_path];

1、当数据库文件不存在时,fmdb会自己创建一个。

2、 如果你传入的参数是空串:@"" ,则fmdb会在临时文件目录下创建这个数据库,数据库断开连接时,数据库文件被删除。

3、如果你传入的参数是 NULL,则它会建立一个在内存中的数据库,数据库断开连接时,数据库文件被删除。

打开数据库:

[dbopen];

返回BOOL型。

关闭数据库:

[dbclose];

数据库增删改等操作:

除了查询操作,FMDB数据库操作都执行executeUpdate方法,这个方法返回BOOL型。

看一下例子:

创建表:

if([dbopen]){

NSString*sqlCreateTable=[NSStringstringWithFormat:@"CREATETABLEIFNOTEXISTS'%@'('%@'INTEGERPRIMARYKEYAUTOINCREMENT,'%@'TEXT,'%@'INTEGER,'%@'TEXT)",TABLENAME,ID,NAME,AGE,ADDRESS];

BOOLres=[dbexecuteUpdate:sqlCreateTable];

if(!res){

NSLog(@"errorwhencreatingdbtable");

}else{

NSLog(@"successtocreatingdbtable");

}

[dbclose];

}

添加数据:

if([dbopen]){

NSString*insertSql1=[NSStringstringWithFormat:

@"INSERTINTO'%@'('%@','%@','%@')VALUES('%@','%@','%@')",

TABLENAME,NAME,AGE,ADDRESS,@"张三",@"13",@"济南"];

BOOLres=[dbexecuteUpdate:insertSql1];

NSString*insertSql2=[NSStringstringWithFormat:

@"INSERTINTO'%@'('%@','%@','%@')VALUES('%@','%@','%@')",

TABLENAME,NAME,AGE,ADDRESS,@"李四",@"12",@"济南"];

BOOLres2=[dbexecuteUpdate:insertSql2];

if(!res){

NSLog(@"errorwheninsertdbtable");

}else{

NSLog(@"successtoinsertdbtable");

}

[dbclose];

}

修改数据:

if([dbopen]){

NSString*updateSql=[NSStringstringWithFormat:

@"UPDATE'%@'SET'%@'='%@'WHERE'%@'='%@'",

TABLENAME,AGE,@"15",AGE,@"13"];

BOOLres=[dbexecuteUpdate:updateSql];

if(!res){

NSLog(@"errorwhenupdatedbtable");

}else{

NSLog(@"successtoupdatedbtable");

}

[dbclose];

}

删除数据:

if([dbopen]){

NSString*deleteSql=[NSStringstringWithFormat:

@"deletefrom%@where%@='%@'",

TABLENAME,NAME,@"张三"];

BOOLres=[dbexecuteUpdate:deleteSql];

if(!res){

NSLog(@"errorwhendeletedbtable");

}else{

NSLog(@"successtodeletedbtable");

}

[dbclose];

}

数据库查询操作:

查询操作使用了executeQuery,并涉及到FMResultSet。

if([dbopen]){

NSString*sql=[NSStringstringWithFormat:

@"SELECT*FROM%@",TABLENAME];

FMResultSet*rs=[dbexecuteQuery:sql];

while([rsnext]){

intId=[rsintForColumn:ID];

NSString*name=[rsstringForColumn:NAME];

NSString*age=[rsstringForColumn:AGE];

NSString*address=[rsstringForColumn:ADDRESS];

NSLog(@"id=%d,name=%@,age=%@address=%@",Id,name,age,address);

}

[dbclose];

}

FMDB的FMResultSet提供了多个方法来获取不同类型的数据:

数据库多线程操作:

如果应用中使用了多线程操作数据库,那么就需要使用FMDatabaseQueue来保证线程安全了。 应用中不可在多个线程中共同使用一个FMDatabase对象操作数据库,这样会引起数据库数据混乱。 为了多线程操作数据库安全,FMDB使用了FMDatabaseQueue,使用FMDatabaseQueue很简单,首先用一个数据库文件地址来初使化FMDatabaseQueue,然后就可以将一个闭包(block)传入inDatabase方法中。 在闭包中操作数据库,而不直接参与FMDatabase的管理。

FMDatabaseQueue * queue = [FMDatabaseQueue databaseQueueWithPath:database_path];

dispatch_queue_tq1=dispatch_queue_create("queue1",NULL);

dispatch_queue_tq2=dispatch_queue_create("queue2",NULL);

dispatch_async(q1,^{

for(inti=0;i<50;++i){

[queueinDatabase:^(FMDatabase*db2){

NSString*insertSql1=[NSStringstringWithFormat:

@"INSERTINTO'%@'('%@','%@','%@')VALUES(?,?,?)",

TABLENAME,NAME,AGE,ADDRESS];

NSString*name=[NSStringstringWithFormat:@"jack%d",i];

NSString*age=[NSStringstringWithFormat:@"%d",10+i];

BOOLres=[db2executeUpdate:insertSql1,name,age,@"济南"];

if(!res){

NSLog(@"errortoinsterdata:%@",name);

}else{

NSLog(@"succtoinsterdata:%@",name);

}

}];

}

});

dispatch_async(q2,^{

for(inti=0;i<50;++i){

[queueinDatabase:^(FMDatabase*db2){

NSString*insertSql2=[NSStringstringWithFormat:

@"INSERTINTO'%@'('%@','%@','%@')VALUES(?,?,?)",

TABLENAME,NAME,AGE,ADDRESS];

NSString*name=[NSStringstringWithFormat:@"lilei%d",i];

NSString*age=[NSStringstringWithFormat:@"%d",10+i];

BOOLres=[db2executeUpdate:insertSql2,name,age,@"北京"];

if(!res){

NSLog(@"errortoinsterdata:%@",name);

}else{

NSLog(@"succtoinsterdata:%@",name);

}

}];

}

});

总结:

上面已经分别介绍了五种方案的优缺点,在开发中,并没有说哪种持久化方案是最好的,只能说在不同开发场景下,最适合使用的持久化方案。

iOS五种本地缓存数据方式的更多相关文章

  1. HTML5本地缓存数据

    //HTML5本地缓存数据 function putObj(key, data) { if (!!window.localStorage) { var obj = { "key": ...

  2. 微信小程序---存储本地缓存数据

    微信小程序之数据缓存 开发中常用setStorageSync来实现本地数据缓存操作 (1)点击缓存案例: <button bindtap="toStorage">存储& ...

  3. Guava的两种本地缓存策略

    Guava的两种缓存策略 缓存在很多场景下都需要使用,如果电商网站的商品类别的查询,订单查询,用户基本信息的查询等等,针对这种读多写少的业务,都可以考虑使用到缓存.在一般的缓存系统中,除了分布式缓存, ...

  4. JavaScript常见的五种数组去重的方式

    ▓▓▓▓▓▓ 大致介绍 JavaScript的数组去重问题在许多面试中都会遇到,现在做个总结 先来建立一个数组 var arr = [1,2,3,3,2,'我','我',34,'我的',NaN,NaN ...

  5. Java中五种遍历HashMap的方式

    import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class Java8Templat ...

  6. 五种团队的组织方式落地 DevOps

    原文链接:https://blog.matthewskelton.net/2013/10/22/what-team-structure-is-right-for-devops-to-flourish/ ...

  7. oc/object-c/ios哪种遍历NSArray/NSDictionary方式快?测试报告

    做app的时候,总免不了要多次遍历数组或者字典.究竟哪种遍历方式比较快呢?我做了如下测试:首先定义测试用宏: ? 1 2 3 4 5 6 7 8 9 #define MULogTimeinterval ...

  8. 保证唯一的一种js提交数据方式,还不错

    <div class="project_right"> <div class="project_title_right" id="m ...

  9. 用五种不同的布局方式实现“左右300px中间自适应”的效果

    float浮动 <section class="layout float"> <style media="screen"> .layou ...

随机推荐

  1. Bootstrap看厌了?试试Metro UI CSS吧

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:Bootstrap作为一款超级流行的前端框架,已经成为很多人的首选,不过有时未免有点审 ...

  2. mysql 方法row_number()方法

    1.  SELECT  t.*,        @curRow := @curRow + 1 AS row_numberFROM    structure tJOIN    (SELECT @curR ...

  3. hdu 5833 Zhu and 772002 高斯消元

    Zhu and 772002 Problem Description Zhu and 772002 are both good at math. One day, Zhu wants to test ...

  4. 2016 ACM/ICPC Asia Regional Dalian Online HDU 5877 Weak Pair treap + dfs序

    Weak Pair Problem Description   You are given a rooted tree of N nodes, labeled from 1 to N. To the  ...

  5. MySQL客户端工具推荐

    PhpMyAdmin 傻瓜级的 Web 页面管理器,无需到处安装,只需一台支持 PHP 运行环境的服务器 功能上一般只限数据表的增删改查 在一台安装了phpmyadmin的机器上是可以连其它服务器上的 ...

  6. Android 编程下的自定义 xmlns

    什么是 xmlns xmlns是 XML Namespaces 的缩写,中文名称是 XML命名空间. xmlns 使用规则 xmlns:namespace-prefix="namespace ...

  7. js控制密码的显示与隐藏实例

    原理是建立2个input,一个type是text,一个type是password.在点击按钮时,这两input个的显示状态与val()的值在切换. html: <!DOCTYPE html> ...

  8. Transform组件C#游戏开发快速入门

    Transform组件C#游戏开发快速入门大学霸 组件(Component)可以看作是一类属性的总称.而属性是指游戏对象上一切可设置.调节的选项,如图2-8所示.本文选自C#游戏开发快速入门大学霸   ...

  9. bluetooth 蓝牙协议和标准,配置

    Bluetooth 配置文件表达了一般行为,Bluetooth 设备可以通过这些行为与其它设备进行通信.Bluetooth 技术定义了广泛的配置文件,描述了许多不同类型的使用案例.为了使用 Bluet ...

  10. 修改WordPress中上传附件2M大小限制的方法/php+iis上传附件默认大小修改方法

    在服务器上架设好WordPress后,使用过程中发现,上传附件大小有2M的限制 话说服务器就是本机,可以直接把文件拖到附件存储文件夹下,然后在需要附件的地方引用链接 可是这种落后的方法终究不是办法,还 ...