iOS数据持久化存储
本文中的代码托管在github上:https://github.com/WindyShade/DataSaveMethods
相对复杂的App仅靠内存的数据肯定无法满足,数据写磁盘作持久化存储是几乎每个客户端软件都需要做的。简单如“是否第一次打开”的BOOL值,大到游戏的进度和状态等数据,都需要进行本地持久化存储。这些数据的存储本质上就是写磁盘存文件,原始一点可以用iOS本身支持有NSFileManager这样的API,或者干脆C语言fwrite/fread,Cocoa Touch本身也提供了一些存储方式,如NSUserDefaults,CoreData等。总的来说,iOS平台数据持久存储方法大致如下所列:
- Raw File APIs
- UserDefault
- NSCoding => NSKeyedArchived
- Plist File
- SQLite(使用C语言)
- CoreData
一、Raw File APIs
ObjC是C的一个超集,所以最笨的方法我们可以直接用C作文件读写来实现数据存储:
1. 写入文件
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// File pathconst char * pFilePath = [_path cStringUsingEncoding:NSUTF8StringEncoding];// Create a new fileFILE * pFile = fopen(pFilePath, "w+");if (pFile == NULL) { NSLog(@"Open File ERROR!"); return;}const char * content = [_textField.text cStringUsingEncoding:NSUTF8StringEncoding];fwrite(content, sizeof(content), 1, pFile);fclose(pFile); |
2. 读取文件
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// File pathconst char * pFilePath = [_path cStringUsingEncoding:NSUTF8StringEncoding];// Create a new fileFILE * pFile = fopen(pFilePath, "r+");if (pFile == NULL) { NSLog(@"Open File ERROR!"); return;}int fileSize = ftell(pFile);NSLog(@"fileSize: %d", fileSize);char * content[20];fread(content, 20, 20, pFile);NSString * aStr = [NSString stringWithFormat:@"%s", &content];if (aStr != nil && ![aStr isEqualToString:@""]) { _textField.text = aStr;}fclose(pFile); |
二、NSUserDefaults
但是既然在iOS平台作开发,我们当然不至于要到使用C的原生文件接口这种地步,下面就介绍几种iOS开发中常用的数据本地存储方式。使用起来最简单的大概就是Cocoa提供的NSUserDefaults了,Cocoa会为每个app自动创建一个数据库,用来存储App本身的偏好设置,如:开关音效,音量调整之类的少量信息。NSUserDefaults是一个单例,生命后期由App掌管,使用时用 [NSUserDefaults standardUserDefaults] 接口获取单例对象。NSUserDefaults本质上是以Key-Value形式存成plist文件,放在App的Library/Preferences目录下,对于已越狱的机器来说,这个文件是不安全的,所以**千万不要用NSUserDefaults来存储密码之类的敏感信息**,用户名密码应该使用**KeyChains**来存储。
1.写入数据
|
1
2
3
4
5
6
7
|
// 获取一个NSUserDefaults对象NSUserDefaults * aUserDefaults = [NSUserDefaults standardUserDefaults];// 插入一个key-value值[aUserDefaults setObject:_textField.text forKey:@"Text"];// 这里是为了把设置及时写入文件,防止由于崩溃等情况App内存信息丢失[aUserDefaults synchronize]; |
2.读取数据
|
1
2
3
|
NSUserDefaults * aUserDefaults = [NSUserDefaults standardUserDefaults]; // 获取一个key-value值NSString * aStr = [aUserDefaults objectForKey:@"Text"]; |
使用起来很简单吧,它的接口跟 NSMutableDictionary 一样,看它的头文件,事实上在内存里面也是用dictionary来存的。写数据的时候记得用 synchronize 方法写入文件,否则 crash了数据就丢了。
三、Plist
上一节提到NSUserDefaults事实上是存成Plist文件,只是Apple帮我们封装好了读写方法而已。NSUserDefaults的缺陷是存储只能是Library/Preferences/<Application BundleIdentifier>.plist 这个文件,如果我们要自己写一个Plist文件呢? 使用NSFileManger可以很容易办到。事实上Plist文件是XML格式的,如果你存储的数据是Plist文件支持的类型,直接用NSFileManager的writToFile接口就可以写入一个plist文件了。 ### Plist文件支持的数据格式有: NSString, NSNumber, Boolean, NSDate, NSData, NSArray, 和NSDictionary. 其中,Boolean格式事实上以[NSNumber numberOfBool:YES/NO];这样的形式表示。NSNumber支持float和int两种格式。
读写Plist文件
1. 首先创建plist文件:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 文件的路径 NSString * _path = [[NSTemporaryDirectory() stringByAppendingString:@"save.plist"] retain]; // 获取一个NSFileMangerNSFileManager * aFileManager = [NSFileManager defaultManager];if (![aFileManager fileExistsAtPath:_path]){ // 文件不存在,创建之 NSMutableDictionary * aDefaultDict = [[NSMutableDictionary alloc] init]; // 插入一个值,此时数据仍存在内存里 [aDefaultDict setObject:@"test" forKey:@"TestText"]; // 使用NSMutableDictionary的写文件接口自动创建一个Plist文件 if (![aDefaultDict writeToFile:_path atomically:YES]) { NSLog(@"OMG!!!"); } [aDefaultDict release]; } |
2. 写入文件
|
1
2
3
4
5
6
|
// 写入数据NSMutableDictionary * aDataDict = [NSMutableDictionary dictionaryWithContentsOfFile:_path];[aDataDict setObject:_textField.text forKey:@"TestText"]; if (![aDataDict writeToFile:_path atomically:YES]) { NSLog(@"OMG!!!"); } |
3. 读取文件
|
1
2
3
4
5
|
NSMutableDictionary * aDataDict = [NSMutableDictionary dictionaryWithContentsOfFile:_path];NSString * aStr = [aDataDict objectForKey:@"TestText"];if (aStr != nil && aStr.length > 0) { _textField.text = aStr;} |
四、NSCoding + NSKeyedArchiver
上面介绍的几种方法中,直接用C语言的接口显然是最不方便的,拿出来的数据还得自己进行类型转换。NSUserDefaults和Plist文件支持常用数据类型,但是不支持自定义的数据对象,好像Cocoa提供了NSCoding和NSKeyArchiver两个工具类,可以把我们自定义的对象编码成二进制数据流,然后存进文件里面,下面的Sample为了简单我直接用cocoa的接口写成plist文件。 如果要使用这种方式进行存储,首先自定义的对象要继承NSCoding的delegate。
|
1
2
3
4
5
6
7
8
9
10
11
12
|
@interface WSNSCodingData : NSObject<NSCoding>然后继承两个必须实现的方法encodeWithCoder:和initWithCoder: - (void)encodeWithCoder:(NSCoder *)enoder { [enoder encodeObject:data forKey:kDATA_KEY]; } - (id)initWithCoder:(NSCoder *)decoder { data = [[decoder decodeObjectForKey:kDATA_KEY] copy]; return [self init]; } |
这里data是我自己定义的WSNSCodingData这个数据对象的成员变量,由于数据在使用过程中需要持续保存在内存中,所以类型为copy,或者retain也可以,记得在dealloc函数里面要realease。这样,我们就定义了一个可以使用NSCoding进行编码的数据对象。
保存数据:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
- (void)saveData { if (aData == nil) { aData = [[WSNSCodingData alloc] init]; } aData.data = _textField.text; NSLog(@"save data...%@", aData.data); // 这里init的NSMutableData是临时用来存储数据的 NSMutableData * data = [[NSMutableData alloc] init]; // 这个NSKeyedArchiver则是进行编码用的 NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; [archiver encodeObject:aData forKey:DATA_KEY]; [archiver finishEncoding]; // 编码完成后的NSData,使用其写文件接口写入文件存起来 [data writeToFile:_path atomically:YES]; [archiver release]; [data release]; NSLog(@"save data: %@", aData.data);} |
读取数据:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
- (void)loadData { NSLog(@"load file: %@", _path); NSData * codedData = [[NSData alloc] initWithContentsOfFile:_path]; if (codedData == nil) return; // NSKeyedUnarchiver用来解码 NSKeyedUnarchiver * unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:codedData]; // 解码后的数据被存在一个WSNSCodingData数据对象里面 aData = [[unarchiver decodeObjectForKey:DATA_KEY] retain]; [unarchiver finishDecoding]; [unarchiver release]; [codedData release]; if (aData.data != nil) { _textField.text = aData.data; }} |
所以其实使用NSCoding和NSKeyedArchiver事实上也是写plist文件,只不过对复杂对象进行了编码使得plist支持更多数据类型而已。
五、 SQLite
如果App涉及到的数据多且杂,还涉及关系查询,那么毋庸置疑要使用到数据库了。Cocoa本身提供了CoreData这样比较重的数据库框架,下一节会讲到,这一节讲一个轻量级的数据库——SQLite。 SQLite是C写的的,做iOS开发只需要在工程里面加入需要的框架和头文件就可以用了,只是我们得用C语言来进行SQLite操作。 关于SQLite的使用参考了这篇文章:http://mobile.51cto.com/iphone-288898.htm但是稍微有点不一样。
1. 在编写SQLite代码之前,我们需要引入SQLite3头文件:
|
1
|
#import <sqlite3.h> |
2. 然后给工程加入 libsqlite3.0.dylib 框架。 3. 然后就可以开始使用了。首先是打开数据库:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
- (void)openDB { NSArray * documentsPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask , YES); NSString * databaseFilePath = [[documentsPaths objectAtIndex:0] stringByAppendingPathComponent:@"mydb"]; // SQLite存的最终还是文件,如果没有该文件则会创建一个 if (sqlite3_open([databaseFilePath UTF8String], &_db) == SQLITE_OK) { NSLog(@"Successfully open database."); // 如果没有表则创建一个表 [self creatTable]; }} |
3.关闭数据库,在dealloc函数里面调用:
|
1
2
3
|
- (void)closeDB { sqlite3_close(_db);} |
4.创建一个表:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
- (void)creatTable { char * errorMsg; const char * createSql="create table if not exists datas (id integer primary key autoincrement,name text)"; if (sqlite3_exec(_db, createSql, NULL, NULL, &errorMsg) == SQLITE_OK) { NSLog(@"Successfully create data table."); } else { NSLog(@"Error: %s",errorMsg); sqlite3_free(errorMsg); }} |
5. 写入数据库
|
1
2
3
4
5
6
7
8
9
10
|
- (void)saveData { char * errorMsg; // 向 datas 表中插入 name = _textFiled.text 的数据 NSString * insertSQL = [NSString stringWithFormat:@"insert into datas (name) values('%@')", _textField.text]; // 执行该 SQL 语句 if (sqlite3_exec(_db, [insertSQL cStringUsingEncoding:NSUTF8StringEncoding], NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"insert ok."); }} |
6. 读取数据库
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
- (void)loadData { [self openDB]; const char * selectSql="select id,name from datas"; sqlite3_stmt * statement; if (sqlite3_prepare_v2(_db, selectSql, -1, &statement, nil)==SQLITE_OK) { NSLog(@"select ok."); } while (sqlite3_step(statement) == SQLITE_ROW) { int _id = sqlite3_column_int(statement, 0); NSString * name = [[NSString alloc] initWithCString:(char *)sqlite3_column_text(statement, 1) encoding:NSUTF8StringEncoding]; NSLog(@"row>>id %i, name %@",_id,name); _textField.text = name; } sqlite3_finalize(statement);} |
五、CoreData
大型数据存储和管理。 XCode自带有图形化工具,可以自动生成数据类型的代码。 最终存储格式不一定存成SQLite,可以是XML等形式。 (未完待续。。。)
iOS数据持久化存储的更多相关文章
- iOS数据持久化存储:归档
在平时的iOS开发中,我们经常用到的数据持久化存储方式大概主要有:NSUserDefaults(plist),文件,数据库,归档..前三种比较经常用到,第四种归档我个人感觉用的还是比较少的,恰恰因为用 ...
- 转载 -- iOS数据持久化存储
作者:@翁呀伟呀 授权本站转载 概论 所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据.在iOS开发中,有很多数据持久化的方案,接下来我将尝试着介绍一下5种方 ...
- iOS数据持久化存储之属性列表
属性列表(plist) iOS提供了一种plist格式的文件(属性列表)用于存储轻量级的数据,属性列表是一种XML格式的文件,拓展名为plist.如果对象是NSString.NSDictionary. ...
- IOS数据持久化存储之SQLite3第三方库FMDB的使用
SQLite是一种小型的轻量级的关系型数据库,在移动设备上使用是非常好的选择,无论是Android还是IOS,都内置了SQLite数据库,现在的版本都是SQLite3.在IOS中使用SQLite如果使 ...
- iOS数据持久化存储之归档NSKeyedArchiver
归档是一种很常用的文件储存方法,几乎任何类型的对象都能够被归档储存(实际上是一种文件保存的形式),收集了网上的一些资料并结合自己的一些经验,总结如下. 一.使用archiveRootObject进行简 ...
- iOS开发——数据持久化Swift篇&使用Core Data进行数据持久化存储
使用Core Data进行数据持久化存储 一,Core Data介绍 1,Core Data是iOS5之后才出现的一个数据持久化存储框架,它提供了对象-关系映射(ORM)的功能,即能够将对象转化成 ...
- iOS 数据持久化(扩展知识:模糊背景效果和密码保护功能)
本篇随笔除了介绍 iOS 数据持久化知识之外,还贯穿了以下内容: (1)自定义 TableView,结合 block 从 ViewController 中分离出 View,轻 ViewControll ...
- iOS开发笔记-swift实现iOS数据持久化之归档NSKeyedArchiver
IOS数据持久化的方式分为三种: 属性列表 (plist.NSUserDefaults) 归档 (NSKeyedArchiver) 数据库 (SQLite.Core Data.第三方类库等 归档(又名 ...
- IOS数据持久化之归档NSKeyedArchiver
IOS数据持久化的方式分为三种: 属性列表 (自定义的Property List .NSUserDefaults) 归档 (NSKeyedArchiver) 数据库 (SQLite.Core Data ...
随机推荐
- JavaScript设计模式基础之闭包(终)
对于前端程序员来说闭包还是比较难以理解的, 闭包的形成与变量的作用域以及变量的生产周期密切相关,所以要先弄懂变量的作用域和生存周期. 1.变量作用域 变量的作用域,就是指变量的有效范围,通常我们指的作 ...
- 在已编译安装nginx上动态添加模块
一.添加nginx模块 找到安装nginx的源码根目录,如果没有的话下载新的源码 wget http://nginx.org/download/nginx-1.8.1.tar.gz 查看ngixn版本 ...
- laravel的安装与启动
今天,我就来给大家分享下laravel的安装 https://pkg.phpcomposer.com 这是官网的中国镜像 第一步: 点链接进来执行下面的三条语句 执行完后,查看下当前目录底下有个 c ...
- go 和make的用法 区别
Doand Make are two verbs which frequently confuse students of English. Learn the Difference between ...
- AVL树总结
定义:一棵AVL树或者是空树,或者是具有下列性质的二叉搜索树:它的左子树和右子树都是AVL树,且左右子树的高度之差的绝对值不超过1 AVL树失衡旋转总结: 假如以T为根的子树失衡.定义平衡因子为 H( ...
- 【LeetCode】Count and Say(报数)
这道题是LeetCode里的第38道题. 题目要求: 报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数.其前五项如下: 1. 1 2. 11 3. 21 4. 1211 5. 111 ...
- Leetcode 407.接雨水
接雨水 给定一个 m x n 的矩阵,其中的值均为正整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水. 说明: m 和 n 都是小于110的整数.每一个单位的高度都大于0 且小 ...
- [UOJ#130][BZOJ4198][Noi2015]荷马史诗
[UOJ#130][BZOJ4198][Noi2015]荷马史诗 试题描述 追逐影子的人,自己就是影子. ——荷马 Allison 最近迷上了文学.她喜欢在一个慵懒的午后,细细地品上一杯卡布奇诺,静静 ...
- BZOJ1922 [Sdoi2010]大陆争霸 【最短路】
题目 在一个遥远的世界里有两个国家:位于大陆西端的杰森国和位于大陆东端的 克里斯国.两个国家的人民分别信仰两个对立的神:杰森国信仰象征黑暗和毁灭 的神曾·布拉泽,而克里斯国信仰象征光明和永恒的神斯普林 ...
- farm
farm 时间限制:C/C++ 4秒,其他语言8秒 空间限制:C/C++ 262144K,其他语言524288K 64bit IO Format: %lld 题目描述 White Rabbit has ...