问题描述
我在具有3个上下文的应用程序中使用coredata:
I use coredata in my app, with 3 contexts:
-
__ masterManagedObjectContext-> 是具有NSPersistentStoreCoordinator并将数据保存到磁盘的上下文。
__masterManagedObjectContext -> is the context that has the NSPersistentStoreCoordinator and save the data to disk.
_mainManagedObjectContext-> 是应用程序在任何地方使用的上下文
_mainManagedObjectContext -> is the context used by app, everywhere
dispatchContext-> 上下文,该方法用于后台方法,在该方法中,我可以访问Web服务,并可以访问所有coredata插入/更新内容。
dispatchContext -> context used in background method, where i have my webservice access and all coredata insertion/update stuff.
我将放置一些代码来实现我的解决方案:
I'll put some code to realize my solution:
应用初始化代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions //a app começa aqui
{
NSPersistentStoreCoordinator *coordinator = [self newPersistentStoreCoordinator];
__masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[__masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainManagedObjectContext setUndoManager:nil];
[_mainManagedObjectContext setParentContext:__masterManagedObjectContext];
return YES;
}
创建新商店关联者的方法
Method to create a new store cordinator
- (NSPersistentStoreCoordinator *)newPersistentStoreCoordinator
{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"example.sqlite"];
NSError *error = nil;
NSPersistentStoreCoordinator *pC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self newManagedObjectModel]];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![pC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return pC;
}
- (NSManagedObjectModel *)newManagedObjectModel
{
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"example" withExtension:@"momd"];
NSManagedObjectModel *newManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return newManagedObjectModel;
}
带有上下文规范(基本代码)的线程调用:
Thread call with context specification (essential code):
@try
{
dispatchContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[dispatchContext setUndoManager:nil];
[dispatchContext setParentContext:__masterManagedObjectContext];
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:dispatchContext];
if(dispatchContext != nil)
{
[NSThread detachNewThreadSelector:@selector(parseDataWithObjects) toTarget:self withObject:nil];
}
else
{
NSLog(@"context IS NIL");
}
}
背景方法:
- (void)parseDataWithObjects
{
[dispatchContext lock];
...
webservice data parse, and core data inserting/updating (+/- 5MB)
...
[dispatchContext save:&error];
[dispatchContext unlock];
[__masterManagedObjectContext save:nil];
}
在所有用户界面中均采用此方法来访问核心数据。
This method is caled in all UI to access coredata data.
- (NSManagedObjectContext *)managedObjectContext
{
return _mainManagedObjectContext;
}
一个调用示例:
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
...fetching, update, ...
现在,我的真正的问题:
Now, my really problem:
每当要保存主上下文时( [__ masterManagedObjectContext save:nil];
,在后台),当我尝试访问主上下文( _mainManagedObjectContext
)时,应用程序冻结(可能是锁?)。
Whenever the master context is to be saved ([__masterManagedObjectContext save:nil];
, on background), when I try to access the main context (_mainManagedObjectContext
), the app freezes (maybe a lock?).
保存过程需要很长时间(因为有很多数据(大约6mb))。保存时,该应用程序运行缓慢,如果在此过程运行时访问某些数据,我的应用程序将永远冻结(我需要强制退出)。
The saving process takes a long time (because are a lots of data (aprox. 6mb)). While saving, the app turns slow and if i access some data while this process is running, my app freezes forever (i need to force quit).
另一个问题是合并上下文。想象一下,在其他viewController中使用主上下文并保存该上下文,一切正常,直到我关闭应用程序为止。当我再次打开该应用程序时,什么也没保存。
Another problem is to merge the contexts. Imagine, using the main context in other viewController, and saving that context, everything works fine until i close de app. When i open the app again, nothing was saved.
我在做什么错?到现在为止,这种情况使我感到困惑。
有人可以帮助我吗?我真的很感激:)
What i'm doing wrong? Until now, this contexts are confusing to me.Someone can help me? I really appreciate :)
----------
编辑:
在弗洛里安·库格勒回应之后,现在我只有2个背景,每个背景都有相同的协调人。
Following Florian Kugler response, now i have only 2 context, every one with the same coordinator.
初始化我的应用程序时,我将此方法称为:
-(void) createContexts
{
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"example" withExtension:@"momd"];
NSManagedObjectModel *newManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"example.sqlite"];
NSError *error = nil;
pC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:newManagedObjectModel];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![pC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
mainManagedObjectContext.persistentStoreCoordinator = pC;
}
- (void)mergeChanges:(NSNotification*)notification {
[mainManagedObjectContext performBlock:^{
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}];
}
- (void)saveMasterContext
{
[mainManagedObjectContext performBlock:^{
[mainManagedObjectContext save:nil];
}];
}
要开始导入数据(在后台),我使用代码:
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];
[NSThread detachNewThreadSelector:@selector(parseDataWithObjects) toTarget:self withObject:nil];
我的背景方法:
- (void)parseDataWithObjects
{
[self resetTime];
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.persistentStoreCoordinator = pC;
...
[backgroundContext save:&error];
}
恢复...
- 第一-创建我的主上下文
- 第二-定义后台上下文通知,在保存后合并更改
。 - 第3个-我调用了后台方法
- 第4个-我保存了我的背景上下文
- 1st - I create my main context
- 2nd - I define background context notification, to merge the changesafter save.
- 3rd - I call the background method
- 4rd - I save my background context
而且性能确实更好。但该应用程序冻结了一点,我认为是在 mergeChanges上。我做错了吗?
And the performance is really better. But the app freezes a little, i think is on "mergeChanges". I'm doing something wrong?
推荐答案
使用 NSPrivateQueueConcurrencyType
或 NSMainQueueConcurrencyType
您应该将在这些上下文上执行的所有操作包装在 performBlock:
中。这样可以确保这些命令在正确的队列上执行。例如,当您保存主上下文时:
When using the NSPrivateQueueConcurrencyType
or NSMainQueueConcurrencyType
you should wrap everything you do on these contexts in performBlock:
. This makes sure, that these commands get executed on the right queue. For example when you save the master context:
[__masterManagedObjectContext performBlock:^{
[__masterManagedObjectContext save];
}];
此外,如果将其设置为,则不必从调度上下文手动合并更改掌握您的主要环境的孩子。一旦保存了子上下文,所做的更改将被推送到父上下文中。
Furthermore you don't have to merge the changes manually from the dispatch context if you set it up as a child to your master context. As soon as you save the child context the changes will be pushed into the parent context.
另一个问题是,您正在一个线程上使用NSConfinementConcurrencyType初始化上下文,然后在其他线程上使用它。使用这种并发类型,在要使用它的线程上初始化上下文非常重要。
Another problem is that you are initializing a context with the NSConfinementConcurrencyType on one thread and then use it on a different thread. With this concurrency type it is very important that you initialize the context on the thread you are going to use it.
但是,我建议您完全不要使用NSConfinementConcurrencyType。一种可能的选择是使用 NSPrivateQueueConcurrencyType
设置调度上下文:
However, I would suggest you don't use NSConfinementConcurrencyType at all. One possible alternative would be to setup your dispatch context with NSPrivateQueueConcurrencyType
:
NSManagedObjectContext* dispatchContext = [[NSManagedObjectContext] alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
dispatchContext.parentContext = __masterManagedObjectContext;
[dispatchContext performBlock:^{
[self parseDataWithObjects];
}];
执行此操作时无需获取锁,您在该块中所做的所有操作都会被处理
When you do this there is no need to acquire locks, everything you do within the block will be processed on a private serial queue.
您已经提到保存需要很长时间,并且您的应用变成了
You already mentioned that saving takes quite long and that your app becomes unresponsive.
如果您计划导入大量的嵌套上下文(私有<-main&-调度),则您的托管对象上下文设置不是最佳选择。调度上下文中的数据。这将始终阻塞主线程相当长的时间,因为您在分派上下文中进行的所有更改必须先复制到主上下文中,然后才能保存在根上下文中。
Your managed object context setup with three nested contexts (private <- main <- dispatch) is not the best choice if you plan to import larger amounts of data in the dispatch context. This will always block the main thread for significant amounts of time, because all the changes you make in the dispatch context have to be copied into the main context before they can be saved in the "root" context.
我最近写了一篇关于此的文章,。在中,我将详细解释为什么安装过程在主线程上花费了很多时间。
I recently wrote an article about this, comparing the performance of different core data setups. In a follow up post I explain in greater detail why this setup takes so much time on the main thread.
对于导入大量数据,使用具有共同的持久性存储协调器的独立管理对象上下文要快得多。您使用 NSMainQueueConcurrencyType
创建一个上下文(用于所有与UI相关的东西),并使用 NSPrivateQueueConcurrencyType
创建一个上下文(用于导入数据)。您将相同的持久性存储协调器分配给了他们。
For importing large amounts of data it is MUCH faster to use independent managed object contexts with a common persistent store coordinator. You create one context with NSMainQueueConcurrencyType
(which you use for all UI related stuff), and another one with NSPrivateQueueConcurrencyType
(for importing data). You assign the same persistent store coordinator to both of them.
// assuming you have persistendStoreCoordinator
NSManagedObjectContext* mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
mainContext.persistentStoreCoordinator = persistentStoreCoordinator;
NSManagedObjectContext* backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.persistentStoreCoordinator = persistentStoreCoordinator;
此设置无法提供嵌套上下文的自动更改传播,但这非常简单通过保存通知来完成。注意再次使用 performBlock:
,以便合并发生在正确的线程上:
This setup doesn't provide the automatic change propagation you get with nested contexts, but this is very easy to do via the save notification. Notice the use of performBlock:
again, so that the merging happens on the right thread:
// ...
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];
// ...
- (void)mergeChanges:(NSNotification*)notification {
[mainContext performBlock:^{
[mainContext mergeChangesFromContextDidSaveNotification:notification];
}];
}
希望这会有所帮助!
这篇关于具有3个MOC解决方案的iOS CoreData(应用程序在保存过程中冻结)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!