问题描述
我使用我的多线程核心数据时遇到了我想我应该仔细看看我在做什么和如何。请让我知道以下是否应该工作。
I've been having problems with my multi-threaded Core Data enabled app, and I figured I should take a hard look at what I'm doing and how. Please let me know if the following should work.
我有一个单例 DataManager
类处理核心数据东西。它有一个属性 managedObjectContext
,为每个线程返回一个不同的MOC。所以,给定 NSMutableDictionary * _threadContextDict
(字符串线程名称到上下文)和 NSMutableDictionary * _threadDict
,它看起来像这样:
I have a singleton DataManager
class that handles the Core Data stuff. It has a property managedObjectContext
that returns a different MOC for each thread. So, given NSMutableDictionary *_threadContextDict
(string thread names to contexts) and NSMutableDictionary *_threadDict
(string thread names to threads), it looks something like this:
-(NSManagedObjectContext *)managedObjectContext
{
if ([NSThread currentThread] == [NSThread mainThread])
{
MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
return delegate.managedObjectContext; //MOC created in delegate code on main thread
}
else
{
NSString *thisThread = [[NSThread currentThread] description];
{
if ([_threadContextDict objectForKey:thisThread] != nil)
{
return [_threadContextDict objectForKey:thisThread];
}
else
{
NSManagedObjectContext *context = [[NSManagedObjectContext alloc]init];
MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
[context setPersistentStoreCoordinator:delegate.persistentStoreCoordinator];
[_threadContextDict setObject:context forKey:thisThread];
[_threadDict setObject:[NSThread currentThread] forKey:thisThread];
//merge changes notifications
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification object:context];
return context;
}
}
}
}
mergeChanges
方法,我将来自传入通知的更改合并到生成通知的所有上下文。它看起来像这样:
In the mergeChanges
method, I merge the changes from the incoming notification to all contexts except the one that generated the notification. It looks like this:
-(void)mergeChanges:(NSNotification *)notification
{
MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = delegate.managedObjectContext;
[context performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification)
withObject:notification waitUntilDone:YES];
for (NSString *element in [_threadContextDict allKeys])
{
if (![element isEqualToString:[[NSThread currentThread] description]])
{
NSThread *thread = [_threadDict objectForKey:element];
NSManagedObjectContext *threadContext = [_threadContextDict objectForKey:element];
[threadContext performSelector:@selector(mergeChangesFromContextDidSaveNotification)
onThread:thread withObject:notification waitUntilDone:YES];
}
}
}
MOC,它是通过调用 saveContext
方法对此共享 DataManager
调用保存
对从上述属性获得的上下文:
Whenever I save changes on a MOC, it's done with a call to a saveContext
method on this shared DataManager
, which calls save
on a context obtained from the aforementioned property:
-(void)saveContext
{
NSManagedObjectContext *context = self.managedObjectContext;
NSError *err = nil;
[context save:&err];
//report error if necessary, etc.
}
的核心数据多线程规则,我觉得这应该工作。我为每个线程使用一个单独的上下文,但所有的同一持久存储。但是当我使用这个,我得到了很多合并冲突,即使我的线程不工作在相同的对象( NSManagedObject
子类)。我只是从网络下载数据,解析结果,并将其保存到Core Data。
Given my understanding of the Core Data multithreading rules, I feel like this should work. I'm using a separate context for each thread, but the same persistent store for all of them. But when I use this, I get a lot of merge conflicts, even though my threads aren't working on the same objects (NSManagedObject
subclasses). I'm just downloading data from the network, parsing the results, and saving them to Core Data.
我做错了什么?我尝试使用 NSLock
实例锁定一些东西,但是我只是挂起。
Am I doing something wrong? I've tried using NSLock
instances to lock around some things, but then I just get hangs.
UPDATE / RESOLUTION :我能够通过添加一个简单的事情:一个方法来删除我的字典中的线程/ MOC对,当我完成它。在每个调用 dispatch_async
的每个块的结尾,我在这里做核心数据的东西,我调用 [self removeThread]
,它从字典中删除当前线程及其MOC。我也只合并更改到主线程MOC。有效地,这意味着每次我做一个后台线程,我得到一个新的新的MOC。
UPDATE/RESOLUTION: I was able to make this work by adding one simple thing: a way to remove a thread/MOC pair from my dictionary when I'm finished with it. At the end of each block in each call to dispatch_async
where I do Core Data stuff, I call [self removeThread]
, which removes the current thread and its MOC from the dictionary. I also only merge changes to the main thread MOC. Effectively, this means that every time I do work on a background thread, I get a fresh new MOC.
我也通过添加一个数字 userInfoDict
,而不是调用 description
。
I also distinguish threads by adding a number to userInfoDict
, instead of calling description
. The number is obtained by a readonly property on my class that returns a higher number each time it's called.
推荐答案
有所有应有的尊重,你的方法是一场噩梦,它应该更糟的是调试它解决任何东西,如果有一个问题。第一个问题是:
With all due respect, your approach is a nightmare, and it should be even worse to debug it to solve anything if there is a problem with it. First problem is this:
没有单独的对象管理在不同线程上使用不同实体的核心数据操作。单例是棘手的处理,特别是在多线程环境,甚至是一个更糟的方法使用它与核心数据。
Do not have a singleton object that manages core data manipulation with different entities on different threads. Singletons are tricky to deal with, especially on multithreading environment, and is even a worse approach to use it with core data.
第二件事,不要使用NSThread来处理多线程。有更多的现代API。使用大中央调度或NSOperation / NSOperationQueue。自引入块(iOS 4)以来,苹果鼓励人们从NSThread移动。为了将来参考,不要使用对象的描述,使用它的方式。描述通常/主要用于调试目的。这里的信息不应该用于比较。甚至不是指针值(这就是为什么你应该使用isEqual而不是==)。
Second thing, do not use NSThread to work on multithreading. There are more modern APIs. Use Grand central dispatch or NSOperation/NSOperationQueue. Apple has encouraged people to move from NSThread since the introduction of blocks (iOS 4). And for future reference, do not use the description of an object the way you are using it. Descriptions are usually/mostly used for debugging purposes. The information there should not be used to compare. Not even the pointer value (which is why you should use isEqual instead of ==).
这是您需要了解的核心数据和多线程的信息:
This is what you need to know about core data and multithreading:
- 为每个线程创建一个上下文。核心数据模板已经为您创建了主线程上下文。在后台线程的执行开始(在块内部,或者你的NSOperation子类的main方法),初始化你的上下文。
- 一旦您的上下文初始化,并具有正确的persistentStoreCoordinator,请监听NSManagedObjectContextObjectsDidChangeNotification。侦听通知的对象将在保存上下文的同一线程中接收通知。因为这不同于主线程,所以使用接收上下文正在使用的线程上的合并上下文进行合并调用。假设你在一个不同于主线程的线程中使用一个上下文,并且要与主线程合并,你需要在主线程中调用合并方法。你可以使用
dispatch_async(dispatch_get_main_queue(),^ {// code here}); - 不要在其managedObjectContext所在的线程外使用NSManagedObject。 li>
- Create one context per thread. The core data template has already created a main thread context for you. At the start of the execution of the background thread (inside the block, or on the main method of your NSOperation subclass), initialize your context.
- Once your context is initialize, and has the right persistentStoreCoordinator, listen to the NSManagedObjectContextObjectsDidChangeNotification. The object listening to the notification will receive the notification in the same thread the context was being saved. Since this is different than the main thread, do the merge call with the merging context on the thread the receiving context is being used. Let's say that you are using a context inside a thread different than the main thread, and you want to merge with the main thread, you need to call the merge method inside the main thread. You can do that withdispatch_async(dispatch_get_main_queue(), ^{//code here});
- Do not use an NSManagedObject outside the thread where its managedObjectContext lives.
使用这些和其他简单的规则,在多线程环境下管理核心数据更容易。你的方法更难实现,更糟的是调试。对您的架构进行一些更改。根据正在使用的线程(而不是集中式)管理上下文。不要保持对其范围之外的上下文的引用。创建第一个上下文后,在线程上创建上下文并不昂贵。你可以重用相同的上下文,只要它在同一块/ NSOperation执行内。
With these and other, simple rules, managing core data under a multithreading environment is easier. Your approach more difficult to implement, and worse to debug. Make some changes to your architecture. Manage the context depending on the thread you are working with (instead of centralized). Do not keep references to context outside of their scope. Once your first context is created, it is not expensive to be creating contexts on your threads. You can reuse the same context, as long as it's inside the same block/NSOperation execution.
这篇关于核心数据多线程:代码示例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!