一、CoreData结构
可以用两张图来表示:
NSManagedObject
数据库对象,一个NSManagedObject
对应一张表,NSManagedObject
的一个属性对应数据表的一个字段。数据库的增删查改就是操作NSManagedObject
,通过xCode->Editor->Create NSManagedObject Subclass…来创建对应表的对象model
NSManagedObjectContext
NSManagedObject
操作的上下文,NSManagedObject
的操作会先缓存在上下文中,还未存到磁盘中
- (NSManagedObjectContext *)managedObjectContext{
if (__managedObjectContext != nil) {
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
生成NSManagedObjectContext
时需要设置NSPersistentStoreCoordinator
有三种类型
- NSConfinementConcurrencyType (或者不加参数,默认就是这个)
- NSMainQueueConcurrencyType (表示只会在主线程中执行)
- NSPrivateQueueConcurrencyType (表示可以在子线程中执行)
通过 setParentContext 方法,可以设置另外一个 NSManagedObjectContext 为自己的父级,这个时候子级可以访问父级下所有的对象,而且子级 NSManagedObjectContext 的内容变化后,如果执行save方法,会自动的 merge 到父级 NSManagedObjectContext 中,也就是子级save后,变动会同步到父级 NSManagedObjectContext。当然这个时候父级也必须再save一次,如果父级没有父级了,那么就会直接向NSPersistentStoreCoordinator中写入,如果有就会接着向再上一层的父级冒泡……
NSPersistentStoreCoordinator
用来管理保存数据到磁盘这个操作
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator{
if (__persistentStoreCoordinator != nil) {
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"HelloApp.sqlite"];
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
生成NSPersistentStoreCoordinator
需要参数:文件保存路径、NSManagedObjectModel
NSManagedObjectModel
生成这个类的来源就是在 xCode 里的.xcdatamodeld文件,我们可以可视化的对这个文件进行操作,实际上这个文件也就相当于数据库的 schema,这个文件编译后就是.momd或.mom文件。
Model Vesion:版本升级,Editor->Add Model Version,生成新的momd文件来进行版本升级,并且重新生成相关类
- (NSManagedObjectModel *)managedObjectModel{
if (__managedObjectModel != nil) {
return __managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"HelloApp" withExtension:@"momd"];
__managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return __managedObjectModel;
}
二、多线程里的CoreData
在多线程中,每个线程都会有一个上下文NSManagedObjectContext
,而NSManagedObjectContext
可以共享一个NSPersistentStoreCoordinator
或者,每个NSManagedObjectContext
都对应一个NSPersistentStoreCoordinator
,所以会有以下几种方式:
1、
这种方式,最简单明了,在子线程的privatecontext里操作完数据库对象后,将操作缓存merger到主线程的maincontext,再由maincontext通过NSPersistentStoreCoordinator
存到本地磁盘。但是存到本地磁盘中是一个耗时的IO操作,对于主线程来说,这是不能忍的,所以不能用这种方式
2、
这个方式在跟第一个方式的区别在于,主线程上的maincontext与NSPersistentStoreCoordinator
交互之家再插了一层子线程的privatecontext,context之间的传递是很快的,这样可以有效地避免IO阻塞主线程,而且childContext调用save方法,其parentContext不用任何merge操作,CoreData自动将数据merge到parentContext当中,这样可以保证每个context的数据同步
3、
这种情况下,privatecontext与maincontext共同连接NSPersistentStoreCoordinator
,子线程中创建privateContext,进行数据增删改查操作,直接save到本地数据库,同时发出通知NSManagedObjectContextDidSaveNotification
,在主线程mainContext的mergeChangesFromContextDidSaveNotification:notification
方法,将所有的数据变动merge到mainContext中,这样就保持了两个Context中的数据同步。由于大部分的操作都是privateContext在子线程中操作的,所以这种设计是UI线程耗时最少的一种设计,但是它的代价是需要多写mergeChanges的方法。
三、MagicRecord源码解析
+ (void) setupCoreDataStackWithStoreAtURL:(NSURL *)storeURL
{
if ([NSPersistentStoreCoordinator MR_defaultStoreCoordinator] != nil) return;
NSPersistentStoreCoordinator *coordinator = [NSPersistentStoreCoordinator MR_coordinatorWithSqliteStoreAtURL:storeURL];
[NSPersistentStoreCoordinator MR_setDefaultStoreCoordinator:coordinator];
[NSManagedObjectContext MR_initializeDefaultContextWithCoordinator:coordinator];
}
先生成一个默认的NSPersistentStoreCoordinator,再生成默认的context,如下:
+ (void) MR_initializeDefaultContextWithCoordinator:(NSPersistentStoreCoordinator *)coordinator;
{
NSAssert(coordinator, @"Provided coordinator cannot be nil!");
if (MagicalRecordDefaultContext == nil)
{
// NSPrivateQueueConcurrencyType
NSManagedObjectContext *rootContext = [self MR_contextWithStoreCoordinator:coordinator];
[self MR_setRootSavingContext:rootContext];
//NSMainQueueConcurrencyType
NSManagedObjectContext *defaultContext = [self MR_newMainQueueContext];
[self MR_setDefaultContext:defaultContext];
[defaultContext setParentContext:rootContext];
}
}
MagicRecord生成了两个context:
- rootContext:NSPrivateQueueConcurrencyType,用以保存数据的上下文
- defaultContext:NSMainQueueConcurrencyType,用以主线程的上下文
defaultContext的父context是rootContext:RootSavingContext,可以看出MagicRecord默认用的是第二种模式,很简单就可以新建一个NSManagedObject并且保存
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSManagedObjectContext *current_context = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
Person *person = [Person MR_createEntityInContext:current_context];
person.name = @"jack";
[current_context MR_saveToPersistentStoreWithCompletion:^(BOOL contextDidSave, NSError * _Nullable error) {
NSLog(@"finish save");
}];
});
2016-07-25 20:26:29.454 MagicR[19718:3903431] Created new private queue context: <NSManagedObjectContext: 0x6100001da220>
2016-07-25 20:26:29.454 MagicR[19718:3903443] → Saving <NSManagedObjectContext (0x6100001da220): Untitled Context> on a background thread
2016-07-25 20:26:29.460 MagicR[19718:3903375] → Saving <NSManagedObjectContext (0x6080001daa90): MagicalRecord Default Context> on the main thread
2016-07-25 20:26:29.462 MagicR[19718:3903431] → Saving <NSManagedObjectContext (0x6180001da6d0): MagicalRecord Root Saving Context> on a background thread
2016-07-25 20:26:29.466 MagicR[19718:3903375] finish save
从MagicRecord的日志就可以看出来,整个过程如下:
- 在子线程新建了一个current_context,并且设置他的父context为主线程的context(default context),然后Person在子线程context改变
- 将current_context的变动merge到父线程的context也就是defaultcontext,主线程的context同样merge到父线程的也就是rootcontext
- rootcontext在子线程将变动保存到磁盘
如果想用第三种方式的话可以这样:在修改之后发出NSManagedObjectContextDidSaveNotification
通知主线程的context。而主线程的context通过mergeChangesFromContextDidSaveNotification
来合并修改
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSManagedObjectContext *current_context = [NSManagedObjectContext MR_newPrivateQueueContext];
[current_context setPersistentStoreCoordinator:[NSPersistentStoreCoordinator MR_defaultStoreCoordinator]];
Person *person = [Person MR_createEntityInContext:current_context];
person.name = @"jack";
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(contextChanged:)
name:NSManagedObjectContextDidSaveNotification
object:current_context];
[current_context MR_saveToPersistentStoreWithCompletion:^(BOOL contextDidSave, NSError * _Nullable error) {
NSLog(@"finish save");
}];
});
- (void)contextChanged:(NSNotification*)notification{
[[NSManagedObjectContext MR_newPrivateQueueContext] mergeChangesFromContextDidSaveNotification:notification];
}
这样就能保证子线程的Context与主线程的context内容修改可以同步