iOS-4种基本存储方式

在iOS开发过程中,不管是做什么应用,都会碰到数据保存的问题。本地存储对提高数据交互效率有着重大的意义。本文总结一下数据存储的几种方式

在这之前我们需要先对应用程序的文件目录有所了解,请看下图

应用程序的文件目录

�应用文件目录
  • Document :保存应用运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录。例如,游戏应用可将游戏存档保存在该目录

  • Library/Caches :保存应用运行时生成的需要持久化的数据,iTunes同步设备时不会备份该目录。一般存储体积大、不需要备份的非重要数据

  • Library/Preference: 保存应用的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时会备份该目录

  • temp :保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录

总结:

为了避免同步过程时间过长,你需要对应用中使用的文件放在哪里做出选择。很大的数据文件,尽量放置在Caches目录下,而不是Documents目录下,Documents目录下文件将做备份,这样会很耗时。


存储方式介绍

  1. NSKeyedArchiver: 采用归档的形式来保存数据沙盒中;
  2. NSUserDefaults:偏好设置数据存到沙盒的Library/Preferences目录(本质是plist);
  3. Write写入方式: 永久保存在磁盘中;
  4. SQLite :采用SQLite数据库来存储数据。

1.NSKeyedArchiver:(归档)

采用归档的形式来保存数据,该数据对象需要遵守NSCoding协议,并且该对象对应的类必须提供encodeWithCoder:和initWithCoder:方法。
前一个方法告诉系统怎么对对象进行编码,而后一个方法则是告诉系统怎么对对象进行解码。

缺点:

归档的形式来保存数据,只能一次性归档保存以及一次性解压。所以只能针对小量数据,而且对数据操作比较笨拙,即如果想改动数据的某一小部分,还是需要解压整个数据或者归档整个数据。
例如对Person对象归档保存。
定义Person:

@interface Person:NSObject{//遵守NSCoding协议
 NSString *name;//待归档类型
}
@implementation Person
-(void)encodeWithCoder:(NSCoder *)aCoder
{
 [aCoder encodeObject:name forKey:@"name"];
}
-(void)initWithCoder:(NSCoder *)aDecoder
{
 name=[aDeCoder decodeObjectforKey:@"name"];
}

归档操作:
如果对Person对象name属性归档保存,只需要NSCoder子类NSKeyedArchiver的方法archiveRootObject:toFile: 即可。

    // 创建person对象
    Person *person = [[Person alloc] init];
    person.name = @"DNS";
    // 获取cache
    NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
    // 拼接文件全路径
    NSString *filePath = [cachePath stringByAppendingPathComponent:@"person.data"];
    // 把自定义对象归档
    [NSKeyedArchiver archiveRootObject:person toFile:filePath];

解档操作:
同样调用NSCoder子类NSKeyedArchiver的方法unarchiveRootObject:toFile: 即可

    NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
    NSString *filePath = [cachePath stringByAppendingPathComponent:@"person.data"];
    // 解档
    [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
注: initWithCoder什么时候需要调用[super initWithCoder:]
• initWithCoder原理:只要解析文件就会调用,xib,storyboard都是文件,因此只要解析这两个文件,就会调用initWithCoder。
• 因此如果在storyboard使用自定义view,重写initWithCoder方法,一定要调用[super initWithCoder:],因为只有系统才知道怎么解析storyboard,如果没有调用,就解析不了这个文件。

2.NSUserDefaults:(偏好设置,本质是plist)

用来保存应用程序设置和属性、用户保存的数据。用户再次打开程序或开机后这些数据仍然存在。
NSUserDefaults可以存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要存储其他类型,则需要转换为前面的类型,才能用NSUserDefaults存储。

  • 好处:1.不需要关心文件名
    2.快速做键值对存储

  • 坏处: 能及时存储,需要做同步操作,把内存中的数据同步到硬盘上

  • 底层:就是封装了一个字典

注:在iOS7之前,默认不会马上跟硬盘同步

存储:

    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];

    [userDefaults setObject:@"DNS" forKey:@"name"];
    // 在iOS7之前,默认不会马上把跟硬盘同步
    // 同步操作
    [userDefaults synchronize];

读取:

    NSString *name = [[NSUserDefaults standardUserDefaults] objectForKey:@"name"];
    NSLog(@"%@",name);

3. Write写入方式:永久保存在磁盘中。

具体方法为:
第一步:获得文件即将保存的路径:

//使用C函数NSSearchPathForDirectoriesInDomains来获得沙盒中目录的全路径。
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
//该函数有三个参数,目录类型、he domain mask、布尔值。
//其中布尔值表示是否需要通过~扩展路径。而且第一个参数是不变的,即为NSSearchPathDirectory 。
//在IOS中后两个参数也是不变的,即为:NSUserDomainMask 和 YES。
NSString *ourDocumentPath =[documentPaths objectAtIndex:0];


//还有一种方法是使用NSHomeDirectory函数获得sandbox的路径。
NSString *sandboxPath = NSHomeDirectory();
//将Documents添加到sandbox路径上
NSString *documentPath = [sandboxPath stringByAppendingPathComponent:@"Documents"];


//这两者的区别就是:使用NSSearchPathForDirectoriesInDomains比在NSHomeDirectory后面添加Document更加安全。因为该文件目录可能在未来发送的系统上发生改变。

第二步:生成在该路径下的文件:

//fileName就是保存文件的文件名
NSString *FileName=[documentDirectory stringByAppendingPathComponent:fileName];

第三步:往文件中写入数据:

//将NSData类型对象data写入文件,文件名为FileName
[data writeToFile:FileName atomically:YES];
//最后从文件中读出数据:
NSData data=[NSData dataWithContentsOfFile:FileName options:0 error:NULL];

4. SQLite:采用数据库来存储数据。

SQLite (http://www.sqlite.org/docs.html) 是一个轻量级的关系数据库。iOS SDK很早就支持了SQLite,在使用时,只需要加入 libsqlite3.dylib 依赖以及引入 sqlite3.h 头文件即可。但是,原生的SQLite API在使用上相当不友好,在使用时,非常不便。于是,开源社区中就出现了一系列将SQLite API进行封装的库,而FMDB (https://github.com/ccgus/fmdb) 则是开源社区中的优秀者。

  1. 什么是FMDB
    FMDB是iOS平台的SQLite数据库框架
    FMDB以OC的方式封装了SQLite的C语言API

  2. FMDB的优点
    使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码
    对比苹果自带的Core Data框架,更加轻量级和灵活
    提供了多线程安全的数据库操作方法,有效地防止数据混乱

  3. FMDB的github地址
    https://github.com/ccgus/fmdb

二、核心类

FMDB有三个主要的类

  1. FMDatabase
    一个FMDatabase对象就代表一个单独的SQLite数据库
    用来执行SQL语句

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

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

三、打开数据库

通过指定SQLite数据库文件路径来创建FMDatabase对象

//获得数据库文件的路径 NSString *doc=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *fileName=[doc stringByAppendingPathComponent:@"student.sqlite"];
 FMDatabase *db = [FMDatabase databaseWithPath:path];
if (![db open]) {
 NSLog(@"数据库打开失败!");
}

文件路径有三种情况

  1. 具体文件路径
      如果不存在会自动创建

  2. 空字符串@""
      会在临时目录创建一个空的数据库
      当FMDatabase连接关闭时,数据库文件也被删除

  3. nil
      会创建一个内存中临时数据库,当FMDatabase连接关闭时,数据库会被销毁

四、执行更新

在FMDB中,除查询以外的所有操作,都称为“更新”
create、drop、insert、update、delete等

使用executeUpdate:方法执行更新
- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments

示例
[db executeUpdate:@"UPDATE t_student SET age = ? WHERE name = ?;", @20, @"Jack"]
五、执行查询
查询方法
- (FMResultSet *)executeQuery:(NSString*)sql, ...
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments

示例
// 查询数据
FMResultSet *rs = [db executeQuery:@"SELECT * FROM t_student"];

// 遍历结果集
while ([rs next]) {
 NSString *name = [rs stringForColumn:@"name"];
 int age = [rs intForColumn:@"age"];
 double score = [rs doubleForColumn:@"score"];
}
使用FMDatabaseQueue

线程安全:在多个线程中同时使用一个FMDatabase实例是不明智的。现在你可以为每个线程创建一个FMDatabase对象。 不要让多个线程分享同一个实例,它无法在多个线程中同时使用。 若此,程序会时不时崩溃,或者报告异常,让人崩溃。所以,不要初始化FMDatabase对象,然后在多个线程中使用。

请使用 FMDatabaseQueue。以下是使用方法:

//首先创建队列。
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
//这样使用。
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
FMResultSet *rs = [db executeQuery:@"select * from foo"];

while([rs next]) { … }}];
//像这样,轻松地把简单任务包装到事务里:
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];

if (whoopsSomethingWrongHappened) { *rollback = YES; return; }

// etc… [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]]; }];

FMDatabaseQueue 后台会建立系列化的G-C-D队列,并执行你传给G-C-D队列的块。这意味着 你从多线程同时调用调用方法,GDC也会按它接收的块的顺序来执行。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容