前言
CoreData不是数据库,而是对象模型,它提供了对象-关系映射(ORM)功能,能将OC对象保存到SQLite数据库文件中,也可以将数据库数据还原成OC对象;不需要掌握SQL语法也可以操作数据库,有点类似.Net的EF框架。
一、CoreData需要使用几个对象进行操作。NSManagedObjectModel对象,加载模型文件,读取app中的所有实体信息;NSPersistentStoreCoordinator对象,添加持久化库(这里采取SQLite数据库);NSManagedObjectContext对象,拿到这个上下文对象操作实体,进行CRUD操作。如果你是在创建项目的时候就勾选了CoreData,那么会在AppDelegate文件中自动生成相关代码,这里采取自定义封装类的方法,独立封装成单例类。
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@interface Database : NSObject
+ (Database *)database;
- (void)saveContext;
@property (nonatomic, strong, readonly) NSManagedObjectContext *objectContext;
@property (nonatomic, strong, readonly) NSManagedObjectModel *objectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator *store;
@end
#import "Database.h"
@implementation Database
@synthesize objectContext = _objectContext;
@synthesize objectModel = _objectModel;
@synthesize store = _store;
- (void)saveContext {
NSError *saveError = nil;
NSManagedObjectContext *context = self.objectContext;
if ([context hasChanges] && ![context save:&saveError]) {
if (saveError) {
NSLog(@"save error -- %@", [saveError userInfo]);
abort();
}
}
}
+ (Database *)database {
static Database *database;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
database = [[Database alloc] init];
});
return database;
}
// 创建模型对象
- (NSManagedObjectModel *)objectModel {
if (!_objectModel) {
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"ZHModel" withExtension:@"momd"];
_objectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
}
return _objectModel;
}
// 创建管理模型的上下文对象
- (NSManagedObjectContext *)objectContext {
if (!_objectContext) {
_objectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_objectContext setPersistentStoreCoordinator:self.store];
}
return _objectContext;
}
// 创建持久化存储协调器
- (NSPersistentStoreCoordinator *)store {
if (!_store) {
// 设置sqlite本地文件存放地址;
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"ZHModel.sqlite"];
NSError *error = nil;
NSLog(@"path -- %@", storeURL);
// 需要数据迁移时,所设置的选项;
NSDictionary *optionDic = @{
NSMigratePersistentStoresAutomaticallyOption: @(YES),
NSInferMappingModelAutomaticallyOption: @(NO)
};
_store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.objectModel];
// 此处将NSPersistentStoreCoordinator对象作为对象模型与本地sqlite文件中间的协调器;
[_store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:optionDic error:&error];
if (error) {
NSLog(@"store error -- %@", [error userInfo]);
abort();
}
}
return _store;
}
// sqlite文件存放目录;
- (NSURL *)applicationDocumentsDirectory {
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
@end
二、CoreData并不是线程安全的,对于ManagedObject以及ManagedObjectContext的访问都只能在对应的线程上进行,而不能夸线程。并行方案也有很多种,性能各有不同,一种是Notification方式(简单来说,就是不同的线程使用不同的context进行操作,当一个线程的context发生变化后,利用notification来通知另一个线程Context,另一个线程调用mergeChangesFromContextDidSaveNotification来合并变化);另一种是child/parent context方式(ChildContext和ParentContext是相互独立的。只有当ChildContext中调用Save了以后,才会把这段时间来Context的变化提交到ParentContext中,ChildContext并不会直接提交到NSPersistentStoreCoordinator中, parentContext就相当于它的NSPersistentStoreCoordinator),当ParentContext执行save后,才会把Context的变化保存到本地文件。今天我所讨论的是Notification方式,给出几个关键的地方,Notification方式是共用NSPersistentStoreCoordinator的;
self.mainContext = [Database database].objectContext;// 运行在主队列的context
self.privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];// 运行在私有队列的context
[self.privateContext setPersistentStoreCoordinator:_mainContext.persistentStoreCoordinator]; // 两者共用着一个persistentStoreCoordinator
1、并行的解决方案之Notification第一步,子线程中的context添加数据并保存,执行insertData方法。
// 在私有队列中添加保存数据,添加成功后会发出NSManagedObjectContextDidSaveNotification通知;
- (void)insertData {
TestEntity2 *testEntity = [NSEntityDescription insertNewObjectForEntityForName:@"TestEntity2" inManagedObjectContext:self.privateContext];
testEntity.title = [NSString stringWithFormat:@"entity%u", arc4random() % 100];
[self.privateContext performBlock:^{
NSError *saveError = nil;
if ([self.privateContext hasChanges]) {
[self.privateContext save:&saveError];
}
}];
}
2、privateContext保存成功后,发出NSManagedObjectContextDidSaveNotification通知,mainContext通过mergeChangesFromContextDidSaveNotification合并context的变化。
// 接收到数据变化保存的通知,主队列执行合并操作。
mergeObject = [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification * _Nonnull note) {
if ([note.object isEqual:self.privateContext]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.mainContext performBlock:^{
[self.mainContext mergeChangesFromContextDidSaveNotification:note];
[self fetchData];
}];
});
}
}];
总结
持久化存储框架CoreData,FMDB,两者在项目中应用广泛,但不同之处也很明显。
1、CoreData不是线程安全的,而FMDB是线程安全的(使用FMDatabaseQueue);
2、我的理解CoreData数据迁移的不同,比如数据库表的结构删除字段或者是修改字段名,由于SQLite不支持删除数据表的列以及修改字段名,所以FMDB需要编写SQL语句逻辑(重新创建新表、然后填充旧数据、删除旧表以及修改新表名称),CoreData则在新的对象模型中做新旧表的字段对应和删除对象模型的属性。
3、本人喜欢使用FMDB,原因很简单就是喜欢比较透明的编码方式。