CoreData Threading. iOS 6 vs 7 - ios

I'm having an issue related to my core data implementation and threading. It works fine on iOS 7 but I can't get the main context to refresh properly on iOS 6.
I have two NSPersistentStoreCoordinators pointing to the same database file. I then have a main context and a background context. All new child contexts are children of the background context.
When I update items on child contexts in background threads on 6 they never merge to the main context until I restart the app and the main context fetches them from the store. On ios7 this all works fine however. How can I ensure the main context is refreshing the merge properly without faulting and reloading the db?
I know iOS 7 turned on asynchrounous sql access by default, but I've done that on 6 with:
NSSQLitePragmasOption : #{#"journal_mode" : #"WAL"}
Here is how my context's are set up:
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[self.mainManagedObjectContext setPersistentStoreCoordinator:self.mainPersistentStoreCoordinator];
[self.mainManagedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
[self.mainManagedObjectContext setUndoManager:nil];
self.backgroundParentContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.backgroundParentContext setPersistentStoreCoordinator:self.backgroundPersistentStoreCoordinator];
[self.backgroundParentContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
[self.backgroundParentContext setUndoManager:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(backgroundContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:self.backgroundParentContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mainContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:self.mainManagedObjectContext];
Here is how I create a child context:
- (NSManagedObjectContext *)newChildManagedObjectContext
{
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[childContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[childContext setParentContext:self.backgroundParentContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification object:childContext];
[childContext setUndoManager:nil];
return childContext;
}
Here are the notification methods:
- (void)mainContextDidSave:(NSNotification *)notification
{
__block NSNotification *strongNotification = notification;
[self.backgroundParentContext performBlockAndWait:^{
[self.backgroundParentContext mergeChangesFromContextDidSaveNotification:strongNotification];
}];
}
- (void)backgroundContextDidSave:(NSNotification *)notification
{
[self.mainManagedObjectContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:NO];
}
- (void)managedObjectContextDidSave:(NSNotification *)notification
{
[self.backgroundParentContext performBlockAndWait:^{
NSError *error = nil;
if (![self.backgroundParentContext save:&error]) {
DNSLog(#"error saving context: %#", error);
}
}];
}
UPDATE:
Well it appears this is just never going to work on 6. According the docs, they have changed quite a bit around merging contexts for 7. So I'm going to have to take a different approach for 6 I guess...

I haven't been using two NSPersistentStoreCoordinators on iOS 6, but merging changes between contexts from different store coordinators was mentioned explicitly during WWDC, so I think it's changed in iOS 7. See the following sessions:
207 - What’s New in Core Data (starting from slide number 145).
211 - Core Data Performance (starting from slide number 91),

Related

Saving context concurrently in Core Data not working in iOS7

I get some data from a couple of web services that are called asynchronously. When I receive their responses, I need to create and save corresponding entities in Core Data with the information received. Since the services callbacks ara asynchronous, and I could be already saving the response of one of the services when I receive the another, I wrote a couple of methods like this:
- (void)createEntity
{
#autoreleasepool {
dispatch_queue_t queue = dispatch_queue_create(kSaveQueue, NULL);
dispatch_async(queue, ^{
// Context for background operations
NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
NSPersistentStoreCoordinator *mainThreadContextPSC = [self.context persistentStoreCoordinator];
[tmpContext setPersistentStoreCoordinator:mainThreadContextPSC];
#try {
// Parse service response and create entity
// Save context
[tmpContext save:nil];
dispatch_async(dispatch_get_main_queue(), ^{
// Notify end of operation
});
}
#catch (NSException *ex) {
NSLog(#"exception: %#", [ex description]);
}
});
}
}
Actually, I have two methods like this, one for let's say EntityA, and another for EntityB, and each one is called when I receive the corresponding service response (serviceA, serviceB). In my tests I see that both tmpContext are always saved in iOS 8, but in iOS 7 it is only the first called which is saved, and the second entity is not persisted in Core Data.
Why does this work in iOS 8 but it doesn't in iOS 7?
Thanks in advance
Your approach to create context with alloc init and then assign the persistent store coordinator is deprecated.
Instead, use the factory method initWithConcurrencyType: and pass NSPrivateQueueConcurrencyType for a background thread. Associate with the parent context by calling setParentContext:.
You can also do background operations by taking advantage of the context's performBlock and performBlockAndWait APIs rather than dropping down to GCD.
Above answer from Mundi is right and good explanations.. I can give you the code I use to create a thread context and save and stop context
+ (NSManagedObjectContext*)startThreadContext {
AppDelegate *theDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = theDelegate.managedObjectContext;
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread]) {
return moc;
}
// get thread dictionary
NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
if ( [threadDictionary objectForKey:#"managedObjectContext"] == nil ) {
// create a context for this thread
NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[newMoc setPersistentStoreCoordinator:[theDelegate persistentStoreCoordinator]];
// Register for context save changes notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:newMoc];
[newMoc setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[newMoc processPendingChanges]; // flush operations for which you want undos
[[newMoc undoManager] disableUndoRegistration];
newMoc.undoManager = nil;
// cache the context for this thread
[threadDictionary setObject:newMoc forKey:#"managedObjectContext"];
}
return [threadDictionary objectForKey:#"managedObjectContext"];
}
+ (void)saveAndStopThreadContext:(NSManagedObjectContext *)context {
// save managed object
NSError* error = nil;
BOOL success = [context save:&error];
if ( !success ) {
ERRLOG(#"[stopThreadContext] failed to save managedObjectContext (err:%#)", error );
}
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:context];
NSThread *thread = [NSThread currentThread];
if (![thread isMainThread]) {
NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
[threadDictionary removeObjectForKey:#"managedObjectContext"];
}
}
And you can use it like this
// get managed object context
NSManagedObjectContext* moc = [CoreDataHelper startThreadContext];
// perform update
[moc performBlock:^{
/*
Do something...
*/
// save and stop thread context
[CoreDataHelper saveAndStopThreadContext:moc];
}];

Same NSPredicate fetch different NSManagedObject in different context after merging

I'm working on a multi-thread app. I need to create a NSManagedObjectContext run on background thread (named it privateContext), do some updating, inserting and deleting things on same NSPersistentStoreCoordinator of mainContext. After privateContext is saved, I merge changes of privateContext with mainContext.
If I create a NSManagedObject on mainContext, it is temporary without any save operation and objectID.URIRepresentation is like x-coredata:///ManagedObject/XXX-XXX-XXX-XXX-XXX.
Then I go to background thread and fetch with same NSPredicate on privateContext. The NSManagedObject I get is now x-coredata://XXX-XXX-XXX-XXX-XXX/ManagedObject/p1, which means it's not temporary.
After I do some updating on this object, save privateContext and merge with mainContext. I fetch with same NSPredicate on mainContext, I get a temporary object finally and all changes are gone.
And if I turn the app off and re-open it, all changes are back, and I'll get a persistent object...
EDIT
My question is how to fetch the right object (not temporary one) on mainContext after merging?
EDIT (codes about privateContext and mainContext)
NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[privateContext setPersistentStoreCoordinator:[mainContext persistentStoreCoordinator]];
[privateContext setUndoManager:nil];
// Inserting, updating and deleting on privateContext
if ([privateContext hasChanges]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(privateContextDidSaveNotification:)
name:NSManagedObjectContextDidSaveNotification
object:privateContext];
NSError *saveError;
if (![privateContext save:&saveError]) {
// Error logs
}
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:privateContext];
}
Here's privateContextDidSaveNotification: selector
- (void)privateContextDidSaveNotification:(NSNotification *)notification
{
[mainContext performBlock:^{
[mainContext mergeChangesFromContextDidSaveNotification:notification];
}];
}

CoreData child contexts, NSFetchedResultsController and main thread

Following this excellent post by Olivier Drobnik, I've implemented the three-layer CoreData stack proposed by CoreData guru Marcus S. Zarra:
The only difference from this diagram and my code is that I only use one Temporary Background MOC, in order to avoid duplicates when inserting objects in several temp MOCs. Here's my context initialisation code:
#pragma mark - NSManagedObjectContexts
+ (NSManagedObjectContext *)privateManagedObjectContext
{
if (!_privateManagedObjectContext) {
// Setup MOC attached to PSC
_privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_privateManagedObjectContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
// Add notification to perform save when the child is updated
_privateContextSaveObserver =
[[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
NSManagedObjectContext *savedContext = [note object];
if (savedContext.parentContext == _privateManagedObjectContext) {
[_privateManagedObjectContext performBlock:^{
NSLog(#"AMBCoreData -> saving privateMOC");
NSError *error;
if (![_privateManagedObjectContext save:&error]) {
NSLog(#"AMBCoreData -> error saving _privateMOC: %# %#", [error localizedDescription], [error userInfo]);
}
}];
}
}];
}
return _privateManagedObjectContext;
}
+ (NSManagedObjectContext *)mainUIManagedObjectContext
{
if (!_mainUIManagedObjectContext) {
// Setup MOC attached to parent privateMOC in main queue
_mainUIManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainUIManagedObjectContext setParentContext:[self privateManagedObjectContext]];
// Add notification to perform save when the child is updated
_mainUIContextSaveObserver =
[[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
NSManagedObjectContext *savedContext = [note object];
if (savedContext.parentContext == _mainUIManagedObjectContext) {
NSLog(#"AMBCoreData -> saving mainUIMOC");
[_mainUIManagedObjectContext performBlock:^{
NSError *error;
if (![_mainUIManagedObjectContext save:&error]) {
NSLog(#"AMBCoreData -> error saving mainUIMOC: %# %#", [error localizedDescription], [error userInfo]);
}
}];
}
}];
}
return _mainUIManagedObjectContext;
}
+ (NSManagedObjectContext *)importManagedObjectContext
{
if (!_importManagedObjectContext) {
// Setup MOC attached to parent mainUIMOC in private queue
_importManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_importManagedObjectContext setParentContext:[self mainUIManagedObjectContext]];
}
return _importManagedObjectContext;
}
This code is pretty straightforward. I'm replicating the above diagram using only the mainUIManagedObjectContext in the NSMainQueueConcurrencyType. Every time the child context, importManagedObjectContext gets saved, a notification is fired and all the parent contexts performs a save in it's current thread.
I've implemented a test view controller with a UITableView and a NSFetchedResultsController attached. This is the code in the viewDidLoad of my test view controller:
- (void)viewDidLoad
{
[super viewDidLoad];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Task"];
[request setSortDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"insertDate" ascending:NO]]];
self.fetchRequest = request;
NSFetchedResultsController *frc =
[[NSFetchedResultsController alloc]
initWithFetchRequest:self.fetchRequest
managedObjectContext:[AMBCoreData mainUIManagedObjectContext]
sectionNameKeyPath:nil
cacheName:nil];
frc.delegate = self;
[self setFetchedResultsController:frc];
[self.fetchedResultsController performFetch:nil];
}
Here I attach the mainUIManagedObjectContext to the NSFetchedResultsController. Later, in my viewDidAppear, I run a loop to insert a few Task entities:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[[AMBCoreData importManagedObjectContext] performBlock:^{
for (int i = 0; i < 5000; i++) {
Task *task = [NSEntityDescription insertNewObjectForEntityForName:#"Task" inManagedObjectContext:[AMBCoreData importManagedObjectContext]];
task.title = [NSString stringWithFormat:#"Task %d", i];
task.insertDate = [NSDate new];
[[AMBCoreData importManagedObjectContext] save:nil];
}];
}
The thing is, I'm inserting 5000 objects and the UI is freezing when the data is populated into the table view. Florian Kugler ran a test with this architecture inserting 15.000 objects and with instruments he got this main thread usage (blue is for main thread, gray for any other threads):
But here's my main thread CPU usage with 5000 objects, profiled using an iPhone 5:
As you can see, my main thread usage is far greater than Florian's and also my UI freezes for a few seconds. My question is, am I doing something wrong? Is this the expected behaviour when using this three-layer MOC architecture with a NSFetchedResultsController and a UITableView? I know that inserting 5000 objects is not the usual behaviour of most apps, so when I've tried with 50 or 100 objects the freeze was inexistent or unnoticeable, but the main thread usage was high (although I admit that in this case it can be due another reasons like waking up the app).
Yes, it is expected, because Main MOC is involved in the saves of its children. It is convenient and kind of okay when children of the UI context don’t do big saves, but often becomes a performance problem if those saves are bigger. You can’t be sure that the UI thread does only minimum job when using this pattern.
For the big saves I would recommend creating a context that is configured directly with the persistent store coordinator. After big save happens, you just refetch and optionally refresh data in the UI context. For more details see my answer here.

Delete request and sync RESTkit with core data and threading

To be clear...
My objectives :-
1. Make a delete request to server (done), I get response but not sure whether its synced with core data DB or not. therefore, I need to know, how to delete a particular object from db using Restkit.
2. ManagedObjectContext confusion -- I am too confused with context and threads.
I am using everywhere for all my operation this context. :-
[RKObjectManager sharedManager].managedObjectStore.mainQueueManagedObjectContext]
But I want to keep the processing on Bg thread and receive the result on main thread to update.
Now there is some concept of child context. How to use it is a puzzle till now for me.
3. If I want to use multithreading, for making server request using Restkit and mapping. How to use managedobjectcontext. (I mean the right way of using it)
You can use below code to manage NSManagedObjectContext in multithreaded
- (void)mergeChanges:(NSNotification*)notification
{
NSLog(#"[mergeChanges] enter");
// save changes to manageObjectContext on main thread
AppDelegate *theDelegate = [[UIApplication sharedApplication] delegate];
[[theDelegate managedObjectContext] performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
NSLog(#"[mergeChanges] leave");
}
- (NSManagedObjectContext*)startThreadContext
{
AppDelegate *theDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] init];
[newMoc setPersistentStoreCoordinator:[theDelegate persistentStoreCoordinator]];
// Register for context save changes notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:newMoc];
return newMoc;
}
- (void)stopThreadContext:(NSManagedObjectContext*)context
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:context];
}
At the beginning of the thread context you can call
-(NSManagedObjectContext*)startThreadContext
and use the new NSManagedObjectContext in the thread and you can remove the NSManagedObjectContext when your thread is finished.
When ever you save the new NSManagedObjectContext it notify the main thread's managed object context to save the changes.

Core Data background thread not updating record

I am having an issue with Core Data in a background GCD thread... I want to update a record, but after fetching it and setting the values it doesn't seem to actually save the updated record.
isUpdate is a BOOL I have setup that tells me whether I am running a first time parse/save or whether it's a record I need to update. In my case, when I update a record it doesn't actually seem to update in my store.
I'm using MagicalRecord helpers. Here's my code:
// Create background context
NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] init];
[backgroundContext setPersistentStoreCoordinator:[NSPersistentStoreCoordinator defaultStoreCoordinator]];
// Save the background context and handle the save notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:backgroundContext];
// Parsing data...
//..
Record *record;
if (!isUpdate) {
record = [NSEntityDescription insertNewObjectForEntityForName:#"Record" inManagedObjectContext:backgroundContext];
} else {
NSPredicate *recordPredicate = [NSPredicate predicateWithFormat:#"SELF.tag == %#", [[node attributeForName:#"tag"] stringValue]];
record = [Record findFirstWithPredicate:recordPredicate];
}
[record setTitle:[[recordNode attributeForName:#"title"] stringValue]];
// Parsing other data...
//..
NSError *error = nil;
// save the context
[backgroundContext save:&error];
if (error) {
NSLog(#"An error occurred: %#", error);
}
And here's the notification:
- (void)backgroundContextDidSave:(NSNotification *)notification {
// Make sure we're on the main thread when updating the main context
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
// merge in the changes to the main context on the main thread
[[NSManagedObjectContext defaultContext] mergeChangesFromContextDidSaveNotification:notification];
}
Your code sounds quite strange to me.
Why do you register NSManagedObjectContextDidSaveNotification notification in the background thread? Maybe I'm wrong but you need to register that notification in a different point in your app.
If you want to make it works you could register that notification in the main thread. For example you could do it in the AppDelegate.
For example in didFinishLaunchingWithOptions: method you cand do
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:backgroundContext];
Then, always within the AppDelegate, you can merge the changes with the method you wrote:
- (void)backgroundContextDidSave:(NSNotification *)notification {
// Make sure we're on the main thread when updating the main context
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:YES];
return;
}
// merge in the changes to the main context on the main thread
[[NSManagedObjectContext defaultContext] mergeChangesFromContextDidSaveNotification:notification];
}
The code performs these steps:
First checks if you are running in the main thread or not.
Since the notification you register could originate from a thread
different from the main one, you need to perform the selector on the
main thread.
Finally performs the merge with the notification that contains the
changes you made in background.
Once done, you can see that the main context is updated with the changes made in the other one.
Edit
Maybe you can try also to change the waitUntilDone to YES.
Hope it helps.
You are mixing two contexts. This code is probably bad:
record = [Record findFirstWithPredicate:recordPredicate];
I assume that this finds record in different context instead of Your backgroundContext. You should change it to something like this:
record = [Record findFirstWithPredicate:recordPredicate inManagedObjectContext:backgroundContext];

Resources