@TOC
IOS数据存储简介
- 在项目开发当中,我们经常会对一些数据进行本地缓存处理。离线缓存的数据一般都保存在APP所在的沙盒之中。一般有以下几种:
存储方式 | 优点 | 缺点 | 适用场景 | 备注 |
---|---|---|---|---|
PList(XML属性列表) | 快速效率高 | 不灵活,只能存储常用的类型 | 只适用于系统自带的一些常用类型才能用 | 备注 |
偏好设置(NSUserDefaults) | 快速效率高,简单易用 | 只能存储常用的类型 | 适用场景 | 备注 |
归档(NSCoding NSKeyedArchiver NSKeyedUnarchiver) | 归档可以实现把自定义的对象存放在文件中 | 必须实现协议,浸入性强 | 适用场景 | 备注 |
SQLITE数据库 | 灵活 | 操作复杂 | 适用场景 | 备注 |
FMDB | 是对sqlite的封装,简单易用,接口比原生的SQLite接口简洁很多,提供一些多线程,缓存,线程池的功能 | 缺点 | 适用场景 | 备注 |
CoreData | 苹果自带的数据存储方式 | 缺点 | 适用场景 | 备注 |
WCDB | 腾讯开发的一款DB工具 | 缺点 | 适用场景 | 备注 |
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];
IOS数据存储方式
1. PList(XML属性列表)
- 在使用plist进行数据存储和读取,只适用于系统自带的一些常用类型才能用,且必须先获取路径相对麻烦
- 可以把字典或数组直接写入到文件中。另外,NSString、NSData、NSNumber等类型,也可以使用writeToFile:atomically:方法直接将对象写入文件中,只是Type为空。
- 实例1
oc代码
//写入文件
NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [doc stringByAppendingPathComponent:@"myself.plist"];
NSDictionary *dict = @{@"name": @"yixiang"};
[dict writeToFile:path atomically:YES];
//读取文件
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
2. 偏好设置(NSUserDefaults)
- 将所有的东西都保存在同一个文件夹下面,且主要用于存储应用的设置信息)
- NSUserDefaults可以存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要存储其他类型,则需要转换为前面的类型,才能用NSUserDefaults存储。
- 实例2
OC代码
//写入文件
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:@"yixiang" forKey:@"name"];
[defaults setInteger:27 forKey:@"age"];
[defaults synchronize];
//读取文件
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
NSString *name=[defaults objectForKey:@"name"];
NSInteger age=[defaults integerForKey:@"age"];
3. 归档(NSCoding NSKeyedArchiver NSKeyedUnarchiver)
- 因为
PList
存储和NSUserDefaults
存储都有一个致命的缺陷,只能存储常用的类型。归档可以实现把自定义的对象存放在文件中。 - 需要保存的对象必须遵守
NSCoding
协议,并且实现该协议中- (void)encodeWithCoder:(NSCoder )aCoder和 - (id)initWithCoder:(NSCoder )aDecoder方法
。 - 实例3
OC代码
//YXPerson.h文件如下:
@interface YXPerson : NSObject<NSCoding>
@property(nonatomic,copy) NSString *name;
@property(nonatomic,assign) int age;
@end
//YXPerson.m文件如下:
#import "YYPerson.h"
@implementation YYPerson
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
-(id)initWithCoder:(NSCoder *)aDecoder{
if (self=[super init]) {
self.name=[aDecoder decodeObjectForKey:@"name"];
self.age=[aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
@end
在ViewController中对它进行写入和读取
//写入对象
YXPerson *p=[[YXPerson alloc]init];
p.name=@"yixiang";
p.age=27;
NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
NSString *path=[docPath stringByAppendingPathComponent:@"person.yixiang"];
[NSKeyedArchiver archiveRootObject:p toFile:path];
//读取对象
YXPerson *p=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
4. SQLITE数据库
上述三种方法都无法存储大批量的数据,有性能的问题。
SQLITE作为程序员都比较熟悉,它是跨平台的数据库工具。下面简单介绍一下,如何打开数据库,新增一张表格,然后对其进行增删改查的操作。
详细SQLite相关性能优化可以参考这篇文章:微信iOS SQLite源码优化实践
实例4
OC代码
- (void)openDB{
//获取数据库文件路径
NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *fileName = [doc stringByAppendingPathComponent:@"students.sqlite"];
//将OC字符串转换为c语言的字符串
const char *cfileName = fileName.UTF8String;
//打开数据库文件(如果数据库文件不存在,那么该函数会自动创建数据库文件)
int result = sqlite3_open(cfileName, &_db);
if (result == SQLITE_OK) {//打开成功
NSLog(@"成功打开数据库");
}else{
NSLog(@"打开数据库失败");
}
}
- (void)createTable{
//创建表
const char *sql = "CREATE TABLE IF NOT EXISTS t_student(id integer PRIMARY KEY AUTOINCREMENT,name text NOT NULL,age integer NOT NULL);";
char *errmsg= NULL;
int result = sqlite3_exec(_db, sql, NULL, NULL, &errmsg);
if (result==SQLITE_OK) {
NSLog(@"创建表成功");
}else{
NSLog(@"创建表失败---%s",errmsg);
}
}
- (void)insertData{
//插入数据
for (int i=0; i<10; i++) {
//拼接sql语句
NSString *name = [NSString stringWithFormat:@"yixiangboy--%d",arc4random_uniform(100)];
int age = arc4random_uniform(20)+10;
NSString *sql = [NSString stringWithFormat:@"INSERT INTO t_student (name,age) VALUES ('%@',%d);",name,age];
//执行SQL语句
char *errmsg = NULL;
sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errmsg);
if (errmsg) {//如果有错误信息
NSLog(@"插入数据失败--%s",errmsg);
}else{
NSLog(@"插入数据成功");
}
}
}
- (void)deleteData{
//删除age小于15的数据
NSString *sql = [NSString stringWithFormat:@"DELETE FROM t_student WHERE age<15"];
char *errmsg = NULL;
sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errmsg);
if (errmsg) {
NSLog(@"删除数据失败");
}else{
NSLog(@"删除数据成功");
}
}
- (void)updateData{
//大于20岁的都置为20岁
NSString *sql = [NSString stringWithFormat:@"UPDATE t_student set age=20 WHERE age>20"];
char *errmsg = NULL;
sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errmsg);
if (errmsg) {
NSLog(@"更新数据失败");
}else{
NSLog(@"更新数据成功");
}
}
- (void)queryData{
const char *sql = "SELECT id,name,age FROM t_student WHERE age<20";
sqlite3_stmt *stmt = NULL;
//进行查询前的准备工作
if(sqlite3_prepare_v2(_db, sql, -1, &stmt, NULL)==SQLITE_OK){//SQL语句没有问题
NSLog(@"查询语句没有问题");
//每调用一次sqlite3_step函数,stmt就会指向下一条记录
while (sqlite3_step(stmt)==SQLITE_ROW) {//找到一条记录
//取出数据
//(1)取出第0个字段的值(int)
int ID=sqlite3_column_int(stmt, 0);
//(2)取出第一列字段的值(text)
const unsigned char *name = sqlite3_column_text(stmt, 1);
//(3)取出第二列字段的值(int)
int age = sqlite3_column_int(stmt, 2);
printf("%d %s %d\n",ID,name,age);
}
}else{
NSLog(@"查询语句有问题");
}
}
5. FMDB
5.1 FMDB 简介
- FMDB 做IOS的也应该都耳熟能详了,项目中都用过。是一个很牛逼的DB工具。
- FMDB 是对SQLIite数据库的C语言接口进行了一层封装,使其满足面向对象的操作,接口比原生的SQLite接口简洁很多。同时也提供一些多线程,缓存,线程池的功能。
- FMDB的三个类:
类 | 功能 | 备注 |
---|---|---|
FMDatabase | 可以理解成一个数据库 | 备注 |
FMResultSet | 查询的结果集合 | 备注 |
FMDatabaseQueue | 运用多线程,可执行多个查询、更新,线程安全 | 备注 |
5.2 FMDB 创建数据库
- FMDatabase创建的时候需要提供一个SQLite数据库文件。我们一般提供一个.db的文件路径即可。
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];
5.3 FMDB 打开数据库,关闭数据库
- 打开数据库:在和数据库进行交互之前,我们需要先打开数据库。使用上一步拿到的数据库文件操作句柄db
if (![db open])
{
db = nil;
return;
}
- 关闭数据库
[db close];
5.4 FMDB 创建表
- 使用集合语句来进行表的创建。
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];
5.5 FMDB 增,删,改,查
5.5.1 查询
- 查询:使用executeQuery 来执行查询语句,FMResultSet来进行存储查询的结果。
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
//retrieve values for each record
}
//可通过如下的方式将结果集里面的数据取出来。
while ([rs next])
{
if (oldIDs == nil) oldIDs = [[NSMutableArray alloc] init];
[oldIDs addObject:[rs stringForColumnIndex:0]];
}
另外,FMResultSet可以通过如下的方法将数据通过恰当的格式检索出来。
intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumn:
objectForColumn:
5.5.2 更新
- 通过调用
- (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;
}
5.5.3 删除
- 通过执行sql语句删除
BOOL success = [db executeUpdate:@"DELETE FROM STAppLog WHERE userID = ? AND appLogID = ?;", self.userID, self.appLogID];
if (!success)
{
STLogDBLastError(db);
result = NO;
return;
}
5.5.4 新增
5.6 FMDB 多线程操作数据库
- 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]) {
…
}
}];
- 执行事务
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];
if (whoopsSomethingWrongHappened) {
*rollback = YES;
return;
}
// etc ...
}];
更多事务相关知识参考这篇博客:数据库事务 ios FMDB
5.7 FMDatabase源码解析
- (BOOL)openWithFlags:(int)flags vfs:(NSString *)vfsName {
#if SQLITE_VERSION_NUMBER >= 3005000
if (_db) {
return YES;
}
int err = sqlite3_open_v2([self sqlitePath], (sqlite3**)&_db, flags, [vfsName UTF8String]);
if(err != SQLITE_OK) {
NSLog(@"error opening!: %d", err);
return NO;
}
if (_maxBusyRetryTimeInterval > 0.0) {
// set the handler
[self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
}
return YES;
#else
NSLog(@"openWithFlags requires SQLite 3.5");
return NO;
#endif
}
- vfs是虚拟文件系统的简称,主要是用来统一不同平台操作系统文件的访问,屏蔽底层硬件介质,提供统一的访问接口。
- 我们可以看到通过 sqlite3_open_v2这个函数,在指定的path上面打开了数据库的句柄:_db.
- 后面,我们就可以拿这个句柄去访问数据库了,比如创建库、创建表、插入数据、更新数据、查询数据等。
5.8 FMResultSet源码解析
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
// 先判断这个句柄是否存在,即操作数据库的入口
if (![self databaseExists]) {
return 0x00;
}
// 是否正在执行查询
if (_isExecutingStatement) {
[self warnInUse];
return 0x00;
}
_isExecutingStatement = YES;
int rc = 0x00;
//定义一个stmt存放结果集
sqlite3_stmt *pStmt = 0x00;
// 主要是做 销毁stmt的工作,防止内存泄漏
FMStatement *statement = 0x00;
FMResultSet *rs = 0x00;
if (_traceExecution && sql) {
NSLog(@"%@ executeQuery: %@", self, sql);
}
if (_shouldCacheStatements) {
statement = [self cachedStatementForQuery:sql];
pStmt = statement ? [statement statement] : 0x00;
[statement reset];
}
if (!pStmt) {
rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
if (SQLITE_OK != rc) {
if (_logsErrors) {
NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
NSLog(@"DB Query: %@", sql);
NSLog(@"DB Path: %@", _databasePath);
}
if (_crashOnErrors) {
NSAssert(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
abort();
}
sqlite3_finalize(pStmt);
_isExecutingStatement = NO;
return nil;
}
}
id obj;
int idx = 0;
int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)
// If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
if (dictionaryArgs) {
for (NSString *dictionaryKey in [dictionaryArgs allKeys]) {
// Prefix the key with a colon.
NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey];
if (_traceExecution) {
NSLog(@"%@ = %@", parameterName, [dictionaryArgs objectForKey:dictionaryKey]);
}
// Get the index for the parameter name.
int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]);
FMDBRelease(parameterName);
if (namedIdx > 0) {
// Standard binding from here.
[self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
// increment the binding count, so our check below works out
idx++;
}
else {
NSLog(@"Could not find index for %@", dictionaryKey);
}
}
}
else {
while (idx < queryCount) {
if (arrayArgs && idx < (int)[arrayArgs count]) {
obj = [arrayArgs objectAtIndex:(NSUInteger)idx];
}
else if (args) {
obj = va_arg(args, id);
}
else {
//We ran out of arguments
break;
}
if (_traceExecution) {
if ([obj isKindOfClass:[NSData class]]) {
NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]);
}
else {
NSLog(@"obj: %@", obj);
}
}
idx++;
[self bindObject:obj toColumn:idx inStatement:pStmt];
}
}
if (idx != queryCount) {
NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)");
sqlite3_finalize(pStmt);
_isExecutingStatement = NO;
return nil;
}
FMDBRetain(statement); // to balance the release below
if (!statement) {
statement = [[FMStatement alloc] init];
[statement setStatement:pStmt];
if (_shouldCacheStatements && sql) {
[self setCachedStatement:statement forQuery:sql];
}
}
// the statement gets closed in rs's dealloc or [rs close];
rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
[rs setQuery:sql];
NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
[_openResultSets addObject:openResultSet];
[statement setUseCount:[statement useCount] + 1];
FMDBRelease(statement);
_isExecutingStatement = NO;
return rs;
}
5.9 FMDatabaseQueue源码解析
- (void)inDatabase:(void (^)(FMDatabase *db))block {
#ifndef NDEBUG
/* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue
* and then check it against self to make sure we're not about to deadlock. */
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
#endif
FMDBRetain(self);
dispatch_sync(_queue, ^() {
FMDatabase *db = [self database];
block(db);
if ([db hasOpenResultSets]) {
NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");
#if defined(DEBUG) && DEBUG
NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]);
for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
NSLog(@"query: '%@'", [rs query]);
}
#endif
}
});
FMDBRelease(self);
}
- FMDatabaseQueue 使用实例
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 1、查询所有的日志。
NSArray *arrLog = [self dd_fetchAppLogs];
});
+ (NSArray *)dd_fetchAppLogs
{
__block NSMutableArray *appLogs = nil;
WSELF;
[[FMDatabaseQueue sharedInstance] inDatabase:^(FMDatabase *db) {
SSELF;
FMResultSet *rs = [db executeQuery:@"SELECT * FROM STAppLog;"];
@onExit {
[rs close];
};
if (rs == nil)
{
STLogDBLastError(db);
return;
}
while ([rs next])
{
@autoreleasepool {
if (appLogs == nil) appLogs = [[NSMutableArray alloc] init];
STAppLog *appLog = [self mj_objectWithKeyValues:rs.resultDictionary];
[appLogs addObject:appLog];
}
}
}];
return appLogs;
}
6. CoreData
6.1 创建数据库表
- 在MesaSQLite设计器中创建表结构,然后将生成的sql复制出来使用。这样可以避免手敲代码产生的错误。
- 将sql保存成文件,然后放到xcode工程中。
6.2 数据模型管理类NSManagedObjectModel
通过NSManagedObjectModel,可以将创建的数据模型文件读取为模型管理类对象。实体类似于数据库中的表结构。
创建NSManagedObjectModel对象
首先、我们需要知道数据模型文件在哪,通过数据模型文件加载NSManagedObjectModel。
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [DDBITrackKitBUNDLE URLForResource:@"Model" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
6.3 持久化存储协调者类NSPersistentStoreCoordinator
- NSManagedObjectContext是进行数据管理的核心类,我们通过这个类来进行数据的增删改查等操作。
- PersistentStoreCoordinator对象的创建需要用到ManagedObjectModel对象,根据objectModel的模型结构创建持久化的本地存储。同时PersistentStoreCoordinator对象需要知道数据库文件在哪里,以便打开对一个的数据库。
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// 指定一个数据库文件路径
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"DDBITrackKit.sqlite"];
NSError *error = nil;
// 通过ManagedObjectModel对象创建NSPersistentStoreCoordinator对象。
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
// 指定底层的存储方式为SQLite数据库。
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
//
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
6.4 数据对象管理上下文NSManagedObjectContext
- 创建NSManagedObjectContext对象
操作数据需要用到Object的句柄,每个句柄管理了对应的ObjectModel对象。一般我们会创建一个主句柄。然后使用子句柄执行多线程操作。
需要注意的点是:各个线程创建的子句柄相互之间是不能直接进行通信的,后果是有可能会崩溃,以及查询不出数据、或者查询出来错误的数据。如果需要通信,那么需要把实体(Entity)的ObjectID传递给另一个线程,然后这个线程里的context执行对应的查询,然后再改变查询出来的object的数据。比较繁琐。
保存数据的时候。可以调用子句柄的save:函数,然后会在我们的通知中心里拿到这个数据改变的通知。
- 创建主句柄
- (NSManagedObjectContext *)mainManagedObjectContext
{
if (_mainManagedObjectContext != nil) {
return _mainManagedObjectContext;
}
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_mainManagedObjectContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
return _mainManagedObjectContext;
}
- 创建多线程的子句柄
- (NSManagedObjectContext *)privateContext
{
// 设置一个 persistent store coordinator 和两个独立的 contexts 被证明了是在后台处理 Core Data 的好方法。
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.persistentStoreCoordinator = [self persistentStoreCoordinator];
return context;
}
- 创建数据更改的通知
[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
NSManagedObjectContext *moc = self.mainManagedObjectContext;
if (note.object != moc) {
[moc performBlock:^{
[moc mergeChangesFromContextDidSaveNotification:note];
}];
}
}];
6.5 查询数据
- 通过NSEntityDescription来查询所需要的实体对象。
+ (instancetype)findOrCreateWithIdentifier:(id)identifier inContext:(NSManagedObjectContext *)context
{
id object = nil;
NSString *entityName = NSStringFromClass(self);
if (identifier)
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"appLogID = %@", identifier];
fetchRequest.fetchLimit = 1;
fetchRequest.returnsObjectsAsFaults = NO;
NSError *error = nil;
id object = [[context executeFetchRequest:fetchRequest error:&error] lastObject];
if (object == nil) {
object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
}
}
else
{
object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
}
return object;
}
+ (NSArray *)dd_fetchAllAppLog
{
NSManagedObjectContext *context = [DatabaseHelper shareInstance].store.privateContext;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"DDBILog"];
[request setReturnsObjectsAsFaults:NO];
NSError *error;
NSArray *arrResult = [context executeFetchRequest:request error:&error];
return arrResult;
}
6.6 更新数据
- 先查询出来对象,然后更新对象响应的字段,最后调用save函数进行保存
- (void)dd_saveOrUpdateWithContext:(NSManagedObjectContext *)localContext completion:(DatabaseCompletion)completion
{
DDBILog *tAppLog = [DDBILog findOrCreateWithIdentifier:self.appLogID inContext:localContext];
tAppLog.accuracy = self.accuracy;
[localContext save:NULL];
if (completion) {
completion(YES, nil);
}
}
6.7 删除对象
- (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];
}