iOS___oc 本地持久化详解
概论
所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据。在iOS开发中,有很多数据持久化的方案,接下来我将尝试着介绍一下5种方案:
1、plist文件(属性列表)
2、preference(偏好设置)
3、NSKeyedArchiver (归档)
4、SQLite3
5、CoreData
沙盒
在介绍各种存储方法之前,有必要说明一下沙盒机制。iOS程序默认情况下只能访问程序自己的目录 ,这个目录被称为“沙盒”。
1、结构
既然沙盒就是一个文件夹,那就看看里面有什么吧。沙盒的目录结构如下:
"应用程序包"
Documents
Library
Caches
Preferences
tmp
NSString *path = [[NSBundle mainBundle] bundlePath];
NSLog(@
"%@"
, path);
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@
"%@"
, path);
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; NSLog(@ "%@" , path);
|
NSString *path = NSTemporaryDirectory();
NSLog(@
"%@"
, path);
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@
"123.plist"
];
NSArray *array = @[@
"123"
, @
"456"
, @
"789"
];
[array writeToFile:fileName atomically:YES];
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@
"%@"
, result);
|
2、注意
2.1、偏好设置是专门用来保存应用程序的配置信息的,一般不要在偏好设置中保存其他数据。
2.2、如果没有调用synchronize方法,系统会根据I/O情况不定时刻的保存到文件中。所以如果需要立即写入文件的就必须调用synchronize方法。
2.3、偏好设置会将所有数据保存到同一个文件中。即可preference目录下的一个以此应用包名来命名的plist文件。
NSKeyedArchiver
归档在iOS中是另一种形式的序列化,只要遵循了NSCoding协议的对象都可以通过实现序列化。由于决大多数支持存储数据的Foundation和Cocoa Touch类都遵循了NSCoding协议,因此,对于大多数类来说,归档相对而言还是比较容易实现的。
1、遵循NSCoding协议
NScoding协议声明了两个方法,这两个方法都是必须实现的。一个用来说明如何将对象编码到归档中,另一个说明如何进行接档来获取一个新对象
特别注意:
如果需要归档的类是某个自定义类的子类时,就需要在归档和解档之前先实现父类的归档和解档方法。即 [super encodeWithCoder:aCoder] 和 [super initWithCoder:aDecoder] 方法;
2、使用
需要把对象归档是调用NSKeyedArchiver的工厂方法archiveRootObject: toFile: 方法。
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@
"person.data"
];
Person *person = [[Person alloc] init];
person.avatar = self.avatarView.image;
person.name = self.nameField.text;
person.age = [self.ageField.text integerValue];
[NSKeyedArchiver archiveRootObject:person toFile:file];
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@
"person.data"
];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
if
(person) {
self.avatarView.image = person.avatar;
self.nameField.text = person.name;
self.ageField.text = [NSString stringWithFormat:@
"%ld"
, person.age];
}
/**
* 打开数据库并创建一个表
*/
- (void)openDatabase {
//1.设置文件名
NSString *filename = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@
"person.db"
];
//2.打开数据库文件,如果没有会自动创建一个文件
NSInteger result = sqlite3_open(filename.UTF8String, &_sqlite3);
if
(result == SQLITE_OK) {
NSLog(@
"打开数据库成功!"
);
//3.创建一个数据库表
char *errmsg = NULL;
sqlite3_exec(_sqlite3,
"CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)"
, NULL, NULL, &errmsg);
if
(errmsg) {
NSLog(@
"错误:%s"
, errmsg);
}
else
{
NSLog(@
"创表成功!"
);
}
}
else
{
NSLog(@
"打开数据库失败!"
);
}
}
执行指令
使用 sqlite3_exec() 方法可以执行任何SQL语句,比如创表、更新、插入和删除操作。但是一般不用它执行查询语句,因为它不会返回查询到的数据。
/**
* 往表中插入1000条数据
*/
- (void)insertData {
NSString *nameStr;
NSInteger age;
for
(NSInteger i = 0; i < 1000; i++) {
nameStr = [NSString stringWithFormat:@
"Bourne-%d"
, arc4random_uniform(10000)];
age = arc4random_uniform(80) + 20;
NSString *sql = [NSString stringWithFormat:@
"INSERT INTO t_person (name, age) VALUES('%@', '%ld')"
, nameStr, age];
char *errmsg = NULL;
sqlite3_exec(_sqlite3, sql.UTF8String, NULL, NULL, &errmsg);
if
(errmsg) {
NSLog(@
"错误:%s"
, errmsg);
}
}
NSLog(@
"插入完毕!"
);
}
查询指令
前面说过一般不使用 sqlite3_exec()方法查询数据。因为查询数据必须要获得查询结果,所以查询相对比较麻烦。示例代码如下:
-
sqlite3_prepare_v2() : 检查sql的合法性
-
sqlite3_step() : 逐行获取查询结果,不断重复,直到最后一条记录
-
sqlite3_coloum_xxx() : 获取对应类型的内容,iCol对应的就是SQL语句中字段的顺序,从0开始。根据实际查询字段的属性,使用sqlite3_column_xxx取得对应的内容即可。
-
sqlite3_finalize() : 释放stmt
/**
* 从表中读取数据到数组中
*/
- (void)readData {
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1000];
char *sql =
"select name, age from t_person;"
;
sqlite3_stmt *stmt;
NSInteger result = sqlite3_prepare_v2(_sqlite3, sql, -1, &stmt, NULL);
if
(result == SQLITE_OK) {
while
(sqlite3_step(stmt) == SQLITE_ROW) {
char *name = (char *)sqlite3_column_text(stmt, 0);
NSInteger age = sqlite3_column_int(stmt, 1);
//创建对象
Person *person = [Person personWithName:[NSString stringWithUTF8String:name] Age:age];
[mArray addObject:person];
}
self.dataList = mArray;
}
sqlite3_finalize(stmt);
}
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@
"person.db"
];
FMDatabase *database = [FMDatabase databaseWithPath:path];
if
(![database open]) {
NSLog(@
"数据库打开失败!"
);
}
-
具体文件路径,如果不存在会自动创建
-
空字符串@"",会在临时目录创建一个空的数据库,当FMDatabase连接关闭时,数据库文件也被删除
-
nil,会创建一个内存中临时数据库,当FMDatabase连接关闭时,数据库会被销毁
//常用方法有以下3种:
- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
//示例
[database executeUpdate:@
"CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)"
];
//或者
[database executeUpdate:@
"INSERT INTO t_person(name, age) VALUES(?, ?)"
, @
"Bourne"
, [NSNumber numberWithInt:42]];
- (FMResultSet *)executeQuery:(NSString*)sql, ...
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
//1.执行查询
FMResultSet *result = [database executeQuery:@
"SELECT * FROM t_person"
];
//2.遍历结果集
while
([result next]) {
NSString *name = [result stringForColumn:@
"name"
];
int age = [result intForColumn:@
"age"
];
}
[queue inDatabase:^(FMDatabase *database) {
[database executeUpdate:@
"INSERT INTO t_person(name, age) VALUES (?, ?)"
, @
"Bourne_1"
, [NSNumber numberWithInt:1]];
[database executeUpdate:@
"INSERT INTO t_person(name, age) VALUES (?, ?)"
, @
"Bourne_2"
, [NSNumber numberWithInt:2]];
[database executeUpdate:@
"INSERT INTO t_person(name, age) VALUES (?, ?)"
, @
"Bourne_3"
, [NSNumber numberWithInt:3]];
FMResultSet *result = [database executeQuery:@
"select * from t_person"
];
while
([result next]) {
}
}];
[queue inTransaction:^(FMDatabase *database, BOOL *rollback) {
[database executeUpdate:@
"INSERT INTO t_person(name, age) VALUES (?, ?)"
, @
"Bourne_1"
, [NSNumber numberWithInt:1]];
[database executeUpdate:@
"INSERT INTO t_person(name, age) VALUES (?, ?)"
, @
"Bourne_2"
, [NSNumber numberWithInt:2]];
[database executeUpdate:@
"INSERT INTO t_person(name, age) VALUES (?, ?)"
, @
"Bourne_3"
, [NSNumber numberWithInt:3]];
FMResultSet *result = [database executeQuery:@
"select * from t_person"
];
while
([result next]) {
}
//回滚
*rollback = YES;
}];