iOS 数据持久化方案大致可以分为四种。

  1. 属性列表(plist)
  2. 对象归档、解档
  3. NSUserDefault
  4. 数据库

了解数据持久化首先需要了解一个应用目录(沙盒机制)下的文件目录结构。

1
2
3
4
5
- Documents //应用会将数据存储在这个文件夹里
- Library
- Caches // 存放一些缓存数据
- Preferences //NSUserDefaults数据就是存在这个文件夹
- tmp //存放临时文件
1
2
// 获取Documents文件目录
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;

一些宏定义

1
2
3
4
#define DATABASE_PATH [PATH_OF_DOCUMENT stringByAppendingPathComponent:@"anbao.db"]
#define PATH_OF_APP_HOME NSHomeDirectory()
#define PATH_OF_TEMP NSTemporaryDirectory()
#define PATH_OF_DOCUMENT [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]

可以被序列化的类型介绍

1
2
3
4
5
6
7
8
9
10
NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;

plist文件

plist文件可以将某些特定的类,以XML的格式保存起来,比较常见的plist的文件Info.plist

图片显示的就是plist文件所对应的XML文件格式。

1.获取文件
在项目的根目录下存在一个demo.plist

1
2
3
4
5
6
7
8
9
10
11
NSString *path = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"plist"];

// 判断文件是否存在
if([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSLog(@"%@",path);

//..
}
else {
NSLog(@"文件不存在!");
}

2.读取文件内容

1
2
NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
NSLog(@"%@", result);

输出

1
2
3
2016-10-13 21:54:06.831 SaveDemo[986:103991] {
demoKey = demoValue;
}

3.写入文件

1
2
3
4
//写入
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:@"inputValue" forKey:@"inputKey"];
[dict writeToFile:path atomically:YES];

归档、解档

归档是iOS序列化的一种方式,我们需要实现的一个协议NSCoding协议

1.协议说明

NSCoding协议才是归档和解档的一个重要协议,其中需要实现两个方法:

1
2
- (void) encodeWithCoder:(NSCoder *)aCoder;
- (id) initWithCoder:(NSCoder *)aDecoder

2.NSKeyedArchiver 和 NSKeyedUnarchiver

1
2
// 将 UserBean 类归档到一个NSData实例中
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:userBean];

自定义类的对象是无法直接写入文件或者存入 NSUserDefault 中的,此时就需要对该对象进行归档。之后得到的NSData数据之后就可以进一步的使用 文件的I/O 或者 NSUserDefault 存入本地。

1
2
// UserBean 对象的解档
UserBean *userBean = [NSKeyedUnarchiver unarchiveObjectWithData:data];

NSUserDefault

应该算是比较常用的轻量级持久化方法,对于存一些当前用户的信息、状态等都是不错的选择。之前提到的沙盒机制,对于 NSUserDefault 的数据存放在 Library/Preferences 文件下。对于 app 的更新 NSUserDefault 的数据是不会删除的,除非用户卸载 app。

1.存入对象要求

  • 遵循NSCoding协议的对象才能存入NSUserDefault,如:UIImage 例外。
  • 对于比较大的数据不建议使用NSUserDefault。(据说NSUserDefault的 I/O 比较慢)

2.存取

1
2
3
4
//存入 NSUserDefaults 
[[NSUserDefaults standardUserDefaults] setObject:@"demoValue" forKey:@"demoKey"];
//读取 数据
NSLog(@"%@",[[NSUserDefaults standardUserDefaults] valueForKey:@"demoKey"]);

对于存基本型的几个方法:

1
2
3
4
5
6
7
8
9
- (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName;
- (void)setFloat:(float)value forKey:(NSString *)defaultName;
- (void)setDouble:(double)value forKey:(NSString *)defaultName;
- (void)setBool:(BOOL)value forKey:(NSString *)defaultName;
//读取
- (NSInteger)integerForKey:(NSString *)defaultName;
- (float)floatForKey:(NSString *)defaultName;
- (double)doubleForKey:(NSString *)defaultName;
- (BOOL)boolForKey:(NSString *)defaultName;

数据库

数据库这里想简单介绍一下 FMDB 第三方库。FMDB 是 iOS 平台的SQLite 数据库框架,它是封装了 SQLite 的 C语言 API。

主要有三个类:

  • FMDatabase (用来执行SQL)
  • FMResultSet (查询结果集)
  • FMDatabaseQueue (多线程中执行操作,线程安全)

1.封装成工具类

FMDBTools.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <Foundation/Foundation.h>
#import "FMDatabase.h"

@interface FMDBTools : NSObject

@property (strong,nonatomic) FMDatabase *fmdb;

+ (instancetype) shareFMDBTools;

//打开数据库
- (void) fmDatabaseOpen;

//关闭数据库
- (void) fmDatabaseClose;

@end

FMDBTools.m

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
26
27
28
29
30
31
32
33
34
#import "FMDBTools.h"


@implementation FMDBTools

+ (instancetype) shareFMDBTools {
static FMDBTools *shareFMDBTools = nil;
static dispatch_once_t predicate;

dispatch_once(&predicate, ^{
shareFMDBTools = [[self alloc] init];
});
return shareFMDBTools;
}

- (FMDatabase *) fmdb {
if(!_fmdb){
// NSString * doc = PATH_OF_DOCUMENT;
// NSString * path = [doc stringByAppendingPathComponent:DATABASE_PATH];
NSLog(@"databaseWithPath - %@",DATABASE_PATH);
_fmdb = [FMDatabase databaseWithPath:DATABASE_PATH];
}
return _fmdb;
}

- (void) fmDatabaseClose{
[self.fmdb close];
}

- (void) fmDatabaseOpen{
[self.fmdb open];
}

@end

2.建立数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void) createXXXTable{

NSFileManager * fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:DATABASE_PATH] == NO) {
// create table

if ([[FMDBTools shareFMDBTools].fmdb open]) {
NSString * sql = @"CREATE TABLE 'tb_videostatus' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , 'userId' INTEGER, 'status' INTEGER NOT NULL DEFAULT 0,)";
BOOL res = [[FMDBTools shareFMDBTools].fmdb executeUpdate:sql];
if (!res) {
NSLog(@"error when creating db table");
} else {
NSLog(@"succ to creating db table");
}
[[FMDBTools shareFMDBTools].fmdb close];
} else {
NSLog(@"error when open db");
}
}
else {
NSLog(@"数据库已存在!");
}
}

3.数据库操作

插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if ([[FMDBTools shareFMDBTools].fmdb open]) {

if([self queryVideoStatusTableWithVideoId:videoId CourseId:courseId UserId:userId]){
[[FMDBTools shareFMDBTools].fmdb close];
return ;
}

NSString * sql = SQL插入语句
BOOL res = [[FMDBTools shareFMDBTools].fmdb executeUpdate:sql];
if (!res) {
NSLog(@"error to insert data");
} else {
NSLog(@"succ to insert data");
}
[[FMDBTools shareFMDBTools].fmdb close];
}

更新

1
2
3
4
5
6
7
8
9
10
11
12
if ([[FMDBTools shareFMDBTools].fmdb open]) {

NSString * sql = SQL更新语句;

BOOL res = [[FMDBTools shareFMDBTools].fmdb executeUpdate:sql];
if (!res) {
NSLog(@"error to update data");
} else {
NSLog(@"succ to update data");
}
[[FMDBTools shareFMDBTools].fmdb close];
}

以上只是一些简单的数据库操作,关于多线程中的操作再之后的涉及到再做总结。

总结

数据持久化对于 app 来说还是很重要的,设计一个合理的持久化方案可以让 app 体验更佳。总结的不恰当之处,还请见谅。