数据存储
如何存储数据 :
文件:1、沙盒 2、Plist 3、NSKeyedArchiver归档 / NSKeyedUnarchiver解档
NSUserDefaults
数据库 :1、SQLite3 2、FMDB 3、Core Data
沙盒:
iOS本地化存储的数据保存在沙盒中。
Documents:iTunes会备份该目录。一般用来存储需要持久化的数据。
Library/Caches:缓存,iTunes不会备份该目录。内存不足时会被清除,应用没有运行时,可能会被清除,。一般存储体积大、不需要备份的非重要数据。
Library/Preference:iTunes同会备份该目录,可以用来存储一些偏好设置。
tmp: iTunes不会备份这个目录,用来保存临时数据,应用退出时会清除该目录下的数据
获取沙盒文件:
// 获取Documents目录的路径
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
// 得到Document目录下的fileName文件的路径
NSString *filePath = [documentPath stringByAppendingPathComponent:fileName];
//获取Library/Caches目录路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
//获取Library/Caches目录下的fileName文件路径
NSString *filePath = [path stringByAppendingPathComponent:fileName];
//获取temp路径
NSString *tmp = NSTemporaryDirectory();
//获取temp下fileName文件的路径
NSString *filePath = [tmp stringByAppendingPathComponent:fileName];
NSDocumentDirectory: 第一个参数代表要查找哪个文件,是一个枚举。
NSUserDomainMask: 也是一个枚举,表示搜索的范围限制于当前应用的沙盒目录。。
YES (expandTilde): 第三个参数是一个BOOL值。iOS中主目录的全写形式是/User/userName,这个参数填YES就表示全写,填NO就是写成‘‘~’’。
plist:
可以把字典或数组直接写入到文件中。另外,NSString、NSData、NSNumber等类型,也可以使用writeToFile:atomically:方法直接将对象写入文件中,只是Type为空。
存储:
// 要保存的数据
NSDictionary *dict = [NSDictionary dictionaryWithObject:@"iOS cookbook" forKey:@"bookName"];
// 获取路径.
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
// 写入数据
[dict writeToFile:filePath atomically:YES];
读取:
// 文件路径
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
// 解析数据
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSString *result = dict[@"bookName"];
NSLog(@"%@", result);
NSKeyedArchiver归档 / NSKeyedUnarchiver解档:
如果想对一个自定义对象进行归档解档,首先要让对象遵守NSCoding协议
其中:NSCoding协议有2个方法:
- (void)encodeWithCoder:(NSCoder *)aCoder
归档时调用这个方法,在方法中使用encodeObject:forKey:归档变量。
- (instancetype)initWithCoder:(NSCoder *)aDecoder
解档时调用这个方法,在方法中使用decodeObject:forKey读出变量
NSUserDefaults:
一般使用它来进行一些设置的记录,比如用户ID,开关是否打开等设置。通过键值对的方式记录设置。
NSUserDefaults可以存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要存储其他类型,则需要转换为前面的类型,才能用NSUserDefaults存储。
FMDB:
FMDB封装了SQLite的C语言API,更加面向对象。
FMDB中的三个类:
FMDatabase:可以理解成一个数据库。
FMResultSet:查询的结果集合。
FMDatabaseQueue:运用多线程,可执行多个查询、更新。线程安全。
SQL中的五种数据类型:字符型,文本型,数值型,逻辑型和日期型。
FMDB不支持BOOL类型,所以如果要存的话还是integer类型。
创建数据库:
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];
打开数据库:
if (![db open])
{
db = nil;
return;
}
创建表:
使用集合语句来进行表的创建
NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
"create table bulktest2 (id integer primary key autoincrement, y text);"
"create table bulktest3 (id integer primary key autoincrement, z text);"
"insert into bulktest1 (x) values ('XXX');"
"insert into bulktest2 (y) values ('YYY');"
"insert into bulktest3 (z) values ('ZZZ');";
success = [db executeStatements:sql];
查询:
使用executeQuery 来执行查询语句,FMResultSet来进行存储查询的结果。
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
if (oldIDs == nil) oldIDs = [[NSMutableArray alloc] init];
[oldIDs addObject:[s stringForColumnIndex:0]];
}
另外,FMResultSet可以通过如下的方法将数据通过恰当的格式检索出来
intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumn:
objectForColumn:
更新:
通过调用- (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments执行插入、删除或者更新数据。
插入数据或者更新数据。
NSString *sql = ![rs next] ? INSERT_STATEMENT : UPDATE_STATEMENT;
BOOL success = [db executeUpdate:sql withParameterDictionary:[self pr_toDBDictionary]];
if (!success)
{
STLogDBLastError(db);
result = NO;
return;
}
删除数据:
BOOL success = [db executeUpdate:@"DELETE FROM STAppLog WHERE userID = ? AND appLogID = ?;", self.userID, self.appLogID];
if (!success)
{
STLogDBLastError(db);
result = NO;
return;
}
多线程操作数据库:
FMDatabaseQueue内部实现了FMDatabase相关的创建及管理。
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];
FMResultSet *rs = [db executeQuery:@"select * from foo"];
while ([rs next]) {
…
}
}];
事务:
关闭数据库:
[db close];
数据库升级与迁移:
因为项目用的是FMDB,所以数据迁移就用这个FMDBMigrationManager工具类,FMDBMigrationManager 是与FMDB结合使用的一个第三方,可以记录数据库版本号并对数据库进行数据库升级等操作。
操作步骤:
1.新建一个Xcode的项目,并集成FMDB和FMDBMigrationManager(建议使用cocoapods,这里不再多说.)
2.创建一个数据库,这里是我们将要进行升级操作的数据库。
3.将要进行升级操作的数据库与FMDBMigrationManager关联起来
//[self getPath]是数据库路径
FMDBMigrationManager * manager=[FMDBMigrationManager managerWithDatabaseAtPath:[self getPath] migrationsBundle:[NSBundle mainBundle]];
4.创建迁移表 这个表就是用来存储版本号的
BOOL resultState=NO;
NSError * error=nil;
if (!manager.hasMigrationsTable) {
resultState=[manager createMigrationsTable:&error];
}
resultState=[manager migrateDatabaseToVersion:UINT64_MAX progress:nil error:&error];
5.使用自定义类的形式升级,定义一个新的类:Migration,遵循FMDBMigrating协议
.h .m使用方法也很简单,将自定义类对象添加进manager即可。
Migration * migration_2=[[Migration alloc]initWithName:@"Book" andVersion:2 andExecuteUpdateArray:@[@"alter table Book add book_email text"]];//给User表添加email字段
Migration * migration_3=[[Migration alloc]initWithName:@"Book" andVersion:3 andExecuteUpdateArray:@[@"alter table Book add book_address text"]];
[manager addMigration:migration_2];
[manager addMigration:migration_3];
6.加入一个新增数据库的字段(项目中常用的升级操作就是增加数据库字段)
- (void)testupdatePrivateMsg{
NSString *filePath = [self getPath];
NSLog(@"-----filePath:%@",filePath);
FMDBMigrationManager * manager=[FMDBMigrationManager managerWithDatabaseAtPath:filePath migrationsBundle:[NSBundle mainBundle]];
Migration * migration_1=[[Migration alloc]initWithName:@"Book" andVersion:1 andExecuteUpdateArray:@[@"alter table Book add book_email text"]];//给User表添加email字段
Migration * migration_2=[[Migration alloc]initWithName:@"Book" andVersion:2 andExecuteUpdateArray:@[@"alter table Book add book_address text"]];
[manager addMigration:migration_1];
[manager addMigration:migration_2];
BOOL resultState=NO;
NSError * error=nil;
if (!manager.hasMigrationsTable) {
resultState=[manager createMigrationsTable:&error];
}
resultState=[manager migrateDatabaseToVersion:UINT64_MAX progress:nil error:&error];
}
FMDB中的坑:
1. 当你用字符串去保存数字时,如果数字开头是0的时候,保存到数据库的话会把0去掉。这是我往数据库存手势密码发现的。例如手势密码是03678,存到数据库就成了3678。解决办法在下面更新数据的方法里。同时存bool类型需要转成NSNumber类型。
- (void)updateData:(XZUserInfomationData *)data
{
if (!data.gesture_code.length) { // 这个是为了判断可以忽略
data.gesture_code = @"0";
}
[_instance.dbQueue inDatabase:^(FMDatabase *db) {
NSString *sql = [NSString stringWithFormat:@"UPDATE USERDATA SET net_asset = %@, earned_amount = %@, open_newnet = %d, usable_amount = %@, to_callback_principal = %@, to_callback_earned = %@, open_fingerMark = %d, gesture_code = '%@' WHERE user_name = '%@'", data.net_asset, data.earned_amount, data.open_newnet, data.usable_amount, data.to_callback_principal, data.to_callback_earned, data.open_fingerMark, data.gesture_code, data.user_name];
if ([db executeUpdate:sql]) {
XZLog(@"update success");
} else {
XZLog(@"update failed");
}
}];
}
更新gesture_code = '%@'加单引号表明这个是一个字符串而不是一串数字,SQL会自己判断如果不加则会被认为是数字就自动把开头0去掉。
2、FMDB ?,只能是对象,不能是基本数据类型,如果是int类型,就包装成NSNumber
3、SQLite3是采用可移植的C(而非Objective-C)编写的,它不知道什么是NSString,可以用NSString实例生成C字符串:
const char *stringPath = 【pathString UTF8String】
Core Data
数据模型管理类NSManagedObjectModel
通过NSManagedObjectModel,可以将创建的数据模型文件读取为模型管理类对象。实体类似于数据库中的表结构。
持久化存储协调者类NSPersistentStoreCoordinator
NSPersistentStoreCoordinator建立数据模型与本地文件或数据库之间的联系,通过它将本地数据读入内存或者将修改过的临时数据进行持久化的保存。
数据对象管理上下文NSManagedObjectContext
NSManagedObjectContext是进行数据管理的核心类,我们通过这个类来进行数据的增删改查等操作。
如何使用:
创建数据库表:
一般我们需要在工程里创建一个xxx.xcdatamodeld文件。然后在这个文件里面创建表
//从本地加载对象模型
NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"LearnCoreData" ofType:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]];
// 创建沙盒中的数据库
NSPersistentStoreCoordinator* coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"database.sqlite"];
[coord addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:path] options:nil error:nil];
// 数据库关联缓存
NSManagedObjectContext* objContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
objContext.persistentStoreCoordinator = coord;
查询数据:
NSFetchRequest *fetch = [[NSFetchRequest alloc] initWithEntityName:@"User"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"gender=1"]; //查询条件
fetch.predicate = predicate;
NSArray *results = [objContext executeFetchRequest:fetch error:nil];
for (int i = 0; i < results.count; ++i) {
User *selectedUser = results[i];
NSLog(@"name…:%@", selectedUser.name);
}
更新数据:
先查询出来对象,然后更新对象响应的字段,最后调用save函数进行保存
// 数据插入
User *user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:objContext];
user.name = [NSString stringWithFormat:@"name_%d", arc4random_uniform(100)];
user.gender = arc4random_uniform(2);
NSError *error;
[objContext save:&error];
删除对象:
- (void)dd_delete:(DatabaseCompletion)completion
{
NSManagedObjectContext *localContext = [DatabaseHelper shareInstance].store.privateContext;
[self dd_deleteWithContext:localContext completion:completion];
}
- (void)dd_deleteWithContext:(NSManagedObjectContext *)context completion:(DatabaseCompletion)completion
{
[self MR_deleteEntityInContext:context];
[context save:NULL];
}
数据库升级:
面试题收集:
1.数据持久化的几个方案(fmdb用没用过)?
2. 数据库操作FMDB有遇到什么问题吗?
3.FMDB的升级与迁移?
4. 什么是序列化和反序列化,用来做什么?
5. 为什么用本地存储,而不用session,session更加简单啊!?
6.数据库的int类型和OC类型int怎么对应存储?