How do I create a child NSManagedObjectContext? - objective-c

I've seen a few videos / threads that say it's possible to create 'children' MOCs -- MOCs that use other MOCs as their persistant stores. Useful, for example, in a context where you're threading your application, and want to have a single master MOC that can save / rollback the changes that the child threads create. (From what I understand, a MOC and it's managedObjects MUST all be used on the same thread)
The question is, how do I create a child MOC? I can't track down the WWDC videos I was watching that introduced them, and everything I"ve seen has been talking about how to use them ONCE they're made. I can easily alloc a new MOC, but how do I set it's persistent store to be another MOC? The reference doesn't show any functions that do that!

Create a new MOC for which you are in total control of the synchronization. This is the same as calling init and the same behavior as pre-parent/child relationships.
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSConfinementConcurrencyType];
You parent a MOC to another MOC by setting its property:
moc.parentContext = someOtherMocThatIsNowMyParent;
Here, the child chooses the parent. I'm sure my kids wish they were NSManagedObjectContexts. A parent context must be of either NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType.
You can create a MOC that is "bound" to a private queue:
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
which means you should only access it via the performBlock or performBlockAndWait API. You can call those methods from any thread as they will ensure proper serialization of the code in the block. For example...
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = someOtherMocThatIsNowMyParent;
[moc performBlock:^{
// All code running in this block will be automatically serialized
// with respect to all other performBlock or performBlockAndWait
// calls for this same MOC.
// Access "moc" to your heart's content inside these blocks of code.
}];
The difference between performBlock and performBlockAndWait is that performBlock will create a block of code, and schedule it with Grand Central Dispatch to be executed asynchronously at some time in the future, on some unknown thread. The method call will return immediately.
performBlockAndWait will do some magic synchronization with all the other performBlock calls, and when all the blocks that have been presented prior to this one are done, this block will execute. The calling thread will pend until this call has completed.
You can also create a MOC that is "bound" to the main thread, just like private concurrency.
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSMainQueueConcurrencyType];
moc.parentContext = someOtherMocThatIsNowMyParent;
[moc performBlock:^{
// All code running in this block will be automatically serialized
// with respect to all other performBlock or performBlockAndWait
// calls for this same MOC. Furthermore, it will be serialized with
// respect to the main thread as well, so this code will run in the
// main thread -- which is important if you want to do UI work or other
// stuff that requires the main thread.
}];
which means you should only access it directly if you know you are on the main thread, or via the performBlock or performBlockAndWait API.
Now, you can use the "main concurrency" MOC either via the performBlock methods, or directly if you know you are already running in the main thread.

Initialize child MOC then:
[_childMOC performBlockAndWait:^{
[_childMOC setParentContext:parentMOC];
}];

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.

Core Data Multithreading: Code Examples

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.

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.