Core Data Multithreading: Code Examples - objective-c

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.
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;
}
}
}
}
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];
}
}
}
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.
}
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.
Am I doing something wrong? I've tried using NSLock instances to lock around some things, but then I just get hangs.
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.
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:
I have a singleton DataManager
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.
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:
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 with
dispatch_async(dispatch_get_main_queue(), ^{//code here});
Do not use an NSManagedObject outside the thread where its managedObjectContext lives.
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.

Related

NSFecthedResultsController and NSPredicate update issue [duplicate]

I am presenting table view contents using NSFetchedResultsController which has a predicate:
[NSPredicate predicateWithFormat:#"visible == %#", [NSNumber numberWithBool:YES]]
On background thread using separate NSManagedObjectContext I update few of the entities and change theirs visible value from NO to YES. Save, merge changes in main thread's NSManagedObjectContext. But NSFetchedResultsController's fetchedObjects doesn't change. Also controller doesn't call -controller:didChangeObject:... on delegate. If entities are updated on main thread in identical manner (my test app calls the same method), everything works as expected.
Also Notification's NSUpdatedObjectsKey contains those objects.
Currently the only solutions I've found is to call for each of NSUpdatedObjectsKey entities:
NSManagedObjectContext *context = ... // main thread context
[context existingObjectWithID:[object objectID] error:nil]
This issue is only with updated objects which previously didn't match the predicate.
Am I missing something obvious?
Turns out main NSManagedObjectContext didn' t event fire NSManagedObjectContextObjectsDidChangeNotification for updated objects because it is not done for faulted objects.
Generic fix (or keep a track of object IDs that needs this treatment):
NSManagedObjectContext *context = [self managedObjectContext];
for(NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) {
[[context objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[context mergeChangesFromContextDidSaveNotification:notification];
From NSManagedObject Class Reference:
You can invoke this method with the
key value of nil to ensure that a
fault has been fired, as illustrated
by the following example.
You have to call processPendingChanges on your Background-NSManagedObjectContext after you merged the changes from an other NSManagedObjectContext.
See CoreData Programming Guide:
Note that the change notification is sent in NSManagedObjectContext’s
processPendingChanges method. The main thread is tied into the event
cycle for the application so that processPendingChanges is invoked
automatically after every user event on contexts owned by the main
thread. This is not the case for background threads—when the method is
invoked depends on both the platform and the release version, so you
should not rely on particular timing. If the secondary context is not
on the main thread, you should call processPendingChanges yourself at
appropriate junctures.

CoreData import not caught by NSFetchedResultsController [duplicate]

I am presenting table view contents using NSFetchedResultsController which has a predicate:
[NSPredicate predicateWithFormat:#"visible == %#", [NSNumber numberWithBool:YES]]
On background thread using separate NSManagedObjectContext I update few of the entities and change theirs visible value from NO to YES. Save, merge changes in main thread's NSManagedObjectContext. But NSFetchedResultsController's fetchedObjects doesn't change. Also controller doesn't call -controller:didChangeObject:... on delegate. If entities are updated on main thread in identical manner (my test app calls the same method), everything works as expected.
Also Notification's NSUpdatedObjectsKey contains those objects.
Currently the only solutions I've found is to call for each of NSUpdatedObjectsKey entities:
NSManagedObjectContext *context = ... // main thread context
[context existingObjectWithID:[object objectID] error:nil]
This issue is only with updated objects which previously didn't match the predicate.
Am I missing something obvious?
Turns out main NSManagedObjectContext didn' t event fire NSManagedObjectContextObjectsDidChangeNotification for updated objects because it is not done for faulted objects.
Generic fix (or keep a track of object IDs that needs this treatment):
NSManagedObjectContext *context = [self managedObjectContext];
for(NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) {
[[context objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[context mergeChangesFromContextDidSaveNotification:notification];
From NSManagedObject Class Reference:
You can invoke this method with the
key value of nil to ensure that a
fault has been fired, as illustrated
by the following example.
You have to call processPendingChanges on your Background-NSManagedObjectContext after you merged the changes from an other NSManagedObjectContext.
See CoreData Programming Guide:
Note that the change notification is sent in NSManagedObjectContext’s
processPendingChanges method. The main thread is tied into the event
cycle for the application so that processPendingChanges is invoked
automatically after every user event on contexts owned by the main
thread. This is not the case for background threads—when the method is
invoked depends on both the platform and the release version, so you
should not rely on particular timing. If the secondary context is not
on the main thread, you should call processPendingChanges yourself at
appropriate junctures.

NSManagedObjectContext: Is this a good pattern of thread confinement?

With Core Data there's a constant dance around thread safety issues. Executing fetches on one NSManagedObjectContext in two different threads guarantees a deadlock. Apple's Core Data Programming Guide suggests using thread confinement, but doesn't provide a pattern by which you may accomplish this. I have a quick and dirty solution below:
- (NSManagedObjectContext *) managedObjectContext
{
NSManagedObjectContext *moc = objc_getAssociatedObject([NSThread currentThread], _cmd);
if (!moc && self.persistentStoreCoordinator)
{
moc = [[NSManagedObjectContext alloc] init];
moc.mergePolicy = NSOverwriteMergePolicy;
moc.persistentStoreCoordinator = self.persistentStoreCoordinator;
objc_setAssociatedObject([NSThread currentThread], _cmd, moc, OBJC_ASSOCIATION_RETAIN);
}
return moc;
}
This method would appear in a proprietary class that manages the data stack in my app. It associates a MOC with the current thread. This seems like a plausible solution to me, but it also appears fast and loose.
What should I be worried about attempting a solution like this?
You should go the other way around: Use
[NSManagedObjectContext alloc] initWithConcurrencyType:concurrencyType]]
to create a context of
NSMainQueueConcurrencyType - if you need a context linked to controllers and UI objects that are required to be used only on the main thread, or
NSPrivateQueueConcurrencyType - for a context that runs on a private background queue.
Use performBlock or performBlockAndWait for all operations on the context, this ensures that the operations are executed on the queue specified for the context.
See Concurrency Support for Managed Object Contexts in the "Core Data Release Notes for OS X v10.7 and iOS 5.0" for more information.

Hang in mergeChangesFromContextDidSaveNotification (and merge conflicts)

In summary: My app is hanging when I call [myMOC mergeChangesFromContextDidSaveNotification:notification] in a multithreaded scenario.
The detailed situation is this:
My app downloads a whole bunch of data from a server and stores it in Core Data on first launch. It comes in several parts. Parsing the whole thing takes several seconds, and most of that time is spent on two of the chunks. So in order to speed it up, I've parallelized those two chunks. This is what it looks like:
NSArray *arr = [self parseJsonData:downloadedNSData]; //turns NSData into JSON array
//using NSJSONSerialization
NSMutableArray __block *first = [[NSMutableArray alloc]init];
NSMutableArray __block *second = [[NSMutableArray alloc]init];
//put half of arr in first and half in second with a loop
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dipatch_group_create();
dispatch_group_async(group, queue, ^
{
for (id element in first)
{
[MyDataClass parseData:element]; //create NSManagedObject subclass, save
}
[self saveContext];
});
dispatch_group_async(group, queue, ^
{
for (id element in second)
{
[MyDataClass parseData:element]; //create NSManagedObject subclass, save
}
[self saveContext];
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
[self saveContext] calls save on the MOC. This is of course done on multiple threads, so I need to create a separate ManagedObjectContext for each thread. This is accomplished by exposing the MOC via a property on this object (self) that maintains an NSMutableDictionary of thread names (call description on NSThread) to NSManagedObjectContexts. When accessed, if there isn't a MOC for [NSThread currentThread], it creates a new one, adds it to the dictionary, and stores it. When each MOC is created, I subscribe to its change notifications:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification object:createdMOC];
In mergeChanges, I loop through my dictionary of contexts and call mergeChangesFromContextDidSaveNotification on all of them except the one for the thread that this is happening on. More specifically, I use [performSelector:onThread:withObject:waitUntilDone:] to have each MOC do this on the thread it's created for.
I'm also locking using NSLock around both the [myMOC save] and my mergeChanges method. If I didn't lock around those, I got "Cocoa error 133020", which apparently means an error merging changes.
So, this is what my logging tells me is happening:
Thread 1 acquires the lock to save and begins saving
A merge context notification comes on thread 1. Thread 1 acquires the lock for merging changes and begins that process. It merges changes for the MOC for the main thread and then hangs when doing one of the MOCs for the background threads.
Thread 2 starts to save but never acquires the lock for saving, because the other thread is stuck trying to merge changes.
So, why is it hanging when merging changes? Is there a better way to handle this scenario?
Update: I have tried using [persistentStoreCoordinator lock] around my [MOC save] call instead of just a lock with NSLock, to no avail. I also tried adding [NSThread sleepForTimeInterval:2] before the call to [self saveContext] in one of the dispatch_group_async calls, and it didn't help.
Update 2: Perhaps the better question here is why I was getting merge conflicts (Cocoa Error 133020). Is that expected? Am I doing the merge right (merging to all contexts except the one saving)?
Update 3: I've posted another question to address the larger context of how I'm doing the multithreading.
When you're creating an NSManagedObject subclass, you're inserted it into a context and therefore you can only do that on the queue / thread that belongs to the context.
The same goes for calling -save on the context.
It seems to me from your code that you're inserting two objects — each on their own thread. That will not work.
I've posted this question documenting my situation a little better. This current question I think is a little narrow in scope.

delegate for a singleton object

I have a NSObject which is a singleton. Is there any issue of having a delegate for this singleton class? I am worried that it would fail for a singleton type.
Here's my scenario. I have a function (inside this singleton class) that does a async request to pull out a NSDictionary from an API. Basically when this request is done I want to notify a class that the request has finished.
No, a delegate wouldn't fail, but consider using NSNotificationCenter instead:
static NSString *const kMyClassNotificationName = #"myClassNotificationName";
// where you would call a delegate method (e.g. [self.delegate doSomething])
[[NSNotificationCenter defaultCenter] postNotificationName:kMyClassNotificationName object:self userInfo: /* dictionary containing variables to pass to the delegate */];
// where you would set up a delegate (e.g. [Singleton instance].delegate = self)
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doSomething) name:kMyClassNotificationName object:[Singleton instance]];
You have basically three options:
Use a delegate. A singelton is a objetct, so of couse it can have a delegate. If several objects whants to use it and needs to set themselves as delegates, you can reset them each time, but that might get hairy.
Use notifications, as shown by Richard J. Ross III., but seriously: It seems to be strange to me, if you have a singleton, that needs to inform one delegate, but you'd use a broadcasting technology.
use completion blocks, where the calling objects passes a block to the singleton, that gets executed, once the singleton fulfilled a task. See [NSURLConnection sendAsynchronousRequest:queue:completionHandler:] (ok, this is not a singleton, but a class method. The principle is the same),that uses one completion block, or the great AFNetworking, that uses a success and a failure block.
From it's example codes:
[[AFGowallaAPIClient sharedClient] getPath:urlString
parameters:mutableParameters
success:^(__unused AFHTTPRequestOperation
*operation,
id JSON)
{
NSMutableArray *mutableRecords = [NSMutableArray array];
for (NSDictionary *attributes in [JSON valueForKeyPath:#"spots"]) {
Spot *spot = [[[Spot alloc] initWithAttributes:attributes] autorelease];
[mutableRecords addObject:spot];
}
if (block) {
block([NSArray arrayWithArray:mutableRecords]);
}
} failure:^(__unused AFHTTPRequestOperation *operation, NSError *error) {
if (block) {
block([NSArray array]);
}
}];
There is nothing wrong with having a delegate for a singleton, but it does create a lot of edge cases that you need to handle. Such as:
If object A calls setDelegate:, followed immediately by object B calling setDelegate: then object A will never receive delegate calls.
You need to check whether you are the delegate before unsetting the singleton's delegate. Typically in dealloc you call singleton.delegate = nil;. If another object happened to become delegate after you did, then you just caused caused them to unexpectedly stop being delegate.
Singletons with delegates is not a well-established pattern. Your solutions should vary depending on how robust your use case is. Here are some solutions (in order of easiest -> most robust).
Keep it simple
Design your App to never have multiple objects being the singleton's delegate at the same time (this may be impossible).
NSNotification
Use NSNotificationCenter to signal events instead of delegation. See some of the other answers posted in this thread.
Multiple Delegates
Extend your singleton to support multiple delegate. Replace setDelegate: with: addDelegate: and removeDelegate:
#property (atomic) NSMutableArray *delegates;
- (void)addDelegate:(NSObject * <YourProtocol>)foo {
[self.delegates addObject:foo];
}
- (void)removeDelegate:(NSObject * <YourProtocol>)foo {
[self.delegates removeObject:foo];
}
- (void)signalDelegateEvent {
[self.delegates enumerateObjectsUsingBlock:^(id<YourProtocol> obj,
NSUInteger idx,
BOOL *stop) {
// call delegate method `foo` on each delegate
if ( [obj respondsToSelector:#selector(foo)]) {
[obj foo];
}
}];
}
I have used the multi-delegate pattern successfully in many apps. Be careful to think about how multi-threading effects things if you choose this approach.