Reasons for NSManagedObjectMergeError error on [NSManagedObjectContext save:] - iphone

I have a application that combines threading and CoreData.
I am using one global NSPersistentStoreCoordinator and a main NSManagedObjectContextModel.
I have a process where I have to download 9 files simultaneously, so I created an object to handle the download (each individual download has its own object) and save it to the persistentStoreCoordinator.
In the [NSURLConnection connectionDidFinishLoading:] method, I created a new NSManagedObject and attempt to save the data (which will also merge it with the main managedObjectContext).
I think that it is failing due to multiple process trying to save to the persistentStoreCoordinator at the same time as the downloads are finishing around the same time.
What is the easiest way to eliminate this error and still download the files independently?

The NSManagedObjectContext instances know how to lock the NSPersistentStoreCoordinator. Since you are already using one NSManagedObjectContext per thread that is most likely not the issue.
It would help to know what the error is that you are getting. Unroll the NSError and look at its -userInfo. If the userInfo dictionary contains the key NSDetailedErrors. The value associated with this key will be an array that you can loop over and look at all the errors inside. That will help to determine what is going on.
It is quite possible that the error can be something as simple as validation or a missing required value and has nothing to do with the actual threading.

Related

Core Data & iCloud (iOS5)

After adding a new NSManagedObject to my Core Data store I tried calling:
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
and got the following exception (weirdly I had no error and the result was also positive!)
2013-03-15 18:32:09.753 Nick copy[28782:3407] CoreData: Ubiquity: An exception occured during a log file export: NSInternalInconsistencyException save notification contents: NSConcreteNotification 0x3891b0 {name = _NSSQLCoreTransactionStateChangeNotification; object = (URL: file://localhost/var/mobile/Applications/FCAF7FC6-7DC8-4E0B-A114-38778255CA90/Documents/MyApp.sqlite); userInfo = {
"_NSSQLCoreActiveSaveRequest" = "";
"_NSSQLCoreTransactionType" = 2;
"_NSSQLCoreTransientSequenceNumber" = 1;
}}
I can catch all exceptions from the "save" method and the App runs fine. Just wondering if this is really save to do, because it feels totally unsafe.
EDIT: Another exception when trying to delete an Object:
Catched Exception: Failed to process pending changes before save. The context is still dirty after 100 attempts. Typically this recursive dirtying is caused by a bad validation method, -willSave, or notification handler.
Is it safe? Probably not. The error shows that the underlying ubiquity system failed to create a SQL log file. That probably means that it failed to create the transaction log that iCloud would use to sync changes. Catching it and continuing means that your data probably saved locally, depending on the details of the framework code. But it almost certainly means that the changes will not be synced by iCloud. Worse, you could well be in a situation where future saves will also fail for the same reason.
I'm not completely sure about the second exception but it's very likely to be a side-effect of the first one.
If you're attempting to use Core Data's built-in iCloud support on iOS5, this is just the beginning of the weird, inexplicable errors. I've done a fair amount of iCloud/Core Data work and I really can't recommend using it with iOS 5. Even iOS 6 is dicey at best, but problems are less likely than on iOS 5.
Unfortunately I can't find the thread anymore, but it told me I had to make sure to always use NSManagedObject classes only in the thread/dispatch_queue in which they are created.
The problem is, if you do access it from a different queue, it might work or crash after a random interval.
I made sure I call NSManagedObject from a dedicated dispatch_queue only and have not logged any weird exceptions since then.

Why am I getting a merge error when saving an NSManagedObjectContext this 'clean'?

thanks in advance for any help. I've spent today battling this and I think that there is something seriously wrong with my understanding of how the framework works.
I'm working on a core data application where the entities have a parent/child relationship. The application creates an NSManagedObjectContext (MOC) on startup. When the application is run for the first time it uses an async block to import the contents of a plist into a second MOC (the root node is obtained from the main MOC using the URI and -managedObjectIDForURIRepresentation:), just before the block finishes it saves the second context.
In my data controller I subscribe to NSManagedObjectContextDidSaveNotification and the following code is run when the notification is sent:
- (void)backgroundContextDidSave:(NSNotification *)notification {
if(![notification.object isEqual:self.managedObjectContext]){
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification]; ;
}
}
I've done a sanity check on this code and sure enough, when the second MOC saves, this is called from the thread executing the block, it is then deferred, and run from the main thread. The notification object contains all objects imported in the second MOC, including the two we're going to be dealing with next.
When this has finished I run the following code which is in a method of the NSManagedObject subclass the objects belong to, it is simply meant to remove a child from its parent:
TreeEntry *oldParent=self.parent; //keep a pointer to the old parent around so we can delete self from the children
// These next four lines are a sanity check to make sure that both objects are on the same MOC we're saving
NSManagedObjectContext *selfContext=self.managedObjectContext;
NSManagedObjectContext *parentContext=self.parent.managedObjectContext;
NSManagedObjectContext *sharedContext=[[DataController sharedDataController] managedObjectContext];
assert([selfContext isEqual:parentContext] && [selfContext isEqual:sharedContext]);
// now we fault the two objects to make sure we can not possibly have them or any changes
// to them in the state of the main MOC, by this time the second MOC is long gone
[sharedContext refreshObject:self.parent mergeChanges:NO];
[sharedContext refreshObject:self mergeChanges:NO];
// up to this point, sharedContex.insertedObjects, sharedContext.updatedObects and sharedContext.deletedObjects
// have all contained no objects at all. None of the above was necessary as the MOC held no changes at all
[sharedContext saveChanges]; // we save it to, well, just to make sure I guess, I may be going crazy
// Now we carry out two changes to the objects, problem occurs if only one change is carried out,
// I'm showing both to show that there relationship is being kept consistent and valid
self.parent=nil;
[oldParent removeChild:self];
// When the next line is run the save fails with a merge conflict
[sharedContext saveChanges];
The last save fails with a Cocoa error 133020, which is a merge fail. The two NSMergeConflicts in the error relate to the entries we're dealing with (self and self.parent).
I just don't understand how that can be. The objects have no state when they are modified so they MUST be getting loaded from the store. Two simple changes are made and then when they are saved straight afterwards a merge conflict occurs. How can that be? nothing else has messed with the store and we've just loaded the objects from it.
I know I can change the merge policy but I don't want to do it without understanding what's going on.
Any ideas? I'm sure it's just that my mental model if what's going on is wrong, but I've not been able to set it right all day!
Ok, it WAS a fundamental misunderstanding on my part of how the framework works, or to be more accurate, the NSManagedStoreCoordinator cache.
When I save the background context the changes go to disk, but apparently the NSManagedStoreCoordinator (which both contexts share) doesn't update or invalidate its cache.
When I refresh the objects in the main MOC the data used to re-populate them comes from the cache, which still has the old data. It doesn't re-load from the disk. The solution was to use [MOC setStalenessInterval:0.0] to force re-loading from disk.

How to get objects after CoreData Context merged

I tried to save data and merge with CoreData and multi-thread for iPhone app.
But I can't get managed objects in the main thread after merging.
I wrote code just like this:
[managedObjectContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
[self performSelectorOnMainThread:#selector(didMerged:) withObject:objectIds waitUntilDone:YES];
So I tried to pass objectIds to get NSManagedObject instances in the main thread which were generated in another thread. At first I tried "objectWithId" method but it generated fault objects. Then I tried "existingObjectWithID" method but it generated objects partly and others were nil with following Error:
[Error] Error Domain=NSCocoaErrorDomain Code=133000 "Operation could not be completed. (Cocoa error 133000.)"
What is wrong? Is there any way how to retrieve all objects by objectIds after merging in another thread?
Thank you.
There are two types of object ids. Before you save a NSManagedObject it has temporary object id. After saving, it will get its final id. So you may use the wrong id...
Read Managed Object IDs and URIs here: https://developer.apple.com/documentation/coredata/nsmanagedobjectid
It seems your context merge failed.
developer documentation on error 133000
NSManagedObjectReferentialIntegrityError = 133000
NSManagedObjectReferentialIntegrityError
Error code to denote an attempt to fire a fault pointing to an object that does not exist.
The store is accessible, but the object corresponding to the fault cannot be found.
Available in Mac OS X v10.4 and later.
Declared in CoreDataErrors.h.
First, you need to unroll your errors. Change the output to:
NSLog(#"Error: %#\n%#", [error localizedDescription], [error userInfo]);
That will give you a lot more information.
Second, if you are working with a single context in multiple threads you are doing it wrong. You need to review the documentation on Core Data and threading. The basic rule is: One Context Per Thread; Period. If you need to manage data across multiple threads look into watching the save notifications from the background threads on the main thread. I would suggest reviewing my articles on the Mac Developer Network for examples of this.

Core Data - How to check if a managed object's properties have been deallocated?

I've created a program that uses core data and it works beautifully.
I've since attempted to move all my core data methods calls and fetch routines into a class that is self contained. My main program then instantiates that class and makes some basic method calls into that class, and the class then does all the core data stuff behind the scenes. What I'm running into, is that sometimes I'll find that when I grab a managed object from the context, I'll have a valid object, but its properties have been deallocated, and I'll cause a crash. I've played with the zombies and looked for memory leaks, and what I have gathered is it seems that the run loop is probably responsible for deallocating the memory, but I'm not sure.
Is there a way to determine if that memory has been deallocated and force the core data to get it back if I need to access it? My managedObjectContext never gets deallocated, and the fetchedResultsController never does, either.
I thought maybe I needed to use the [managedObjectContext refreshObject:mergeData:] method, or the [managedObjectContext setRetainsRegisteredObjects:] method. Although, I'm under the impression that last one may not be the best bet since it will be more memory intensive (from what I understand).
These errors only popped up when I moved the core data calls into another class file, and they are random when they show up.
Any insight would be appreciated.
-Ryan
Sounds to me like you are not retaining objects you want to keep hanging around. If you are doing something like this:
NSArray *array = [moc executeFetchRequest:request error:&error];
you do not own the returned array and it will most likely disappear when the current autorelease pool is drained. This will occur when the run loop finishes processing the current event.
All this is speculation. If you want a proper answer, you need to post your code.
It's hard to know what the problem is based on your description, but you might want to look at the Core Data memory management guide. You shouldn't have to worry about memory management for managed objects and their entities (they're fetched and faulted automatically). When you talk about "properties," do you mean custom properties backed by ivars? If so, these should be released in didTurnIntoFault and allocd as needed (probably in the accessor).
I was struggling with a similar issue. I'm using a managed object class and want to set its properties dependent on user input. But the sometimes the properties and sometimes the whole managed object were deallocated.
After reading the Apple documentation http://developer.apple.com/library/IOs/#documentation/Cocoa/Conceptual/CoreData/Articles/cdMemory.html the chapter "The Role of the Managed Object Context" I learned that managed objects are released each run loop completes.
And there is the golden advice to set
[myMangedObjectContext setRetainsRegisteredObjects:YES];
(I had to set it in the init method (initWithNibName for me) of my view controller.)
You should also regard to retain only the objects you need to as explained in the documentation. But read it yourself.
If I'm not right please correct me.
I also made a class that handles all my CoreData fetching and stuff. I ran into a couple of gotcha's, so here are some tips. (If I am making any memory management errors in these examples, please let me know.)
Two things:
1) Made a "fetchFiredObject" method in the CoreData handler class. So when I want to get a managedObject that has all its variables and is a "fully feathered bird" so to speak, instead of doing:
aManagedObject *myManagedObject = [myCoreDataHandler.managedObjectStorageArray objectAtIndex:1];
int x = myManagedObject.someVariable.intValue;
instead I do:
aManagedObject *myManagedObject = [myCoreDataHandler fetchFiredObjectAtIndex:1];
int x = myManagedObject.someVariable.intValue;
And in myCoreDataHandler's fetchFiredObjectAtIndex:i method, we're going into the array, finding the object key at index i, then doing a fetchRequest for that object key, and returning the freshly-fetched managedObject so that it won't have been faulted or deallocated, etc. :D
2) When I create a new child viewController, I populate its "myCoreDataHandler" value from the parent upon creation. However, this happens on a subsequent line of code after the line of code that creates the new viewController. Therefore, any code in the child's viewDidLoad that tries to use myCoreDataHandler's methods will return empty objects because viewDidLoad completes before the parent's next line of code where it sets the values of globals in the child object. So make sure you are not accessing your "Core Data handling object" from within viewDidLoad or anything local methods called by viewDidLoad! Instead call them from the parent after creating the new viewController.

CoreData performance about context saving

I finished converting my app to use the CoreData layer for a small datawarehouse I want to use. I have some concerns about the performance and how to best use it. In particular:
I have a lot of runs where I read from disk attributes within files: each attribute should generate a new object, unless an object of that type and that value already exists. So, for each file I read, I: execute a fetch to check if that managed object already exists; if yes finish, otherwise I create the object, assign value and save context.
Currently, I save the context once for each time I create a new object, so it happens more or less ten times (for the ten attributes) for each file read (which can be hundreds). Would be better to reduce the context saving points, maybe once for file instead of once for attribute? I do not know the overhead of this operation so I don't know if is ok to do this so often, or how to find out the time spent on this (maybe with the instruments? Don't really know how).
There isn't any need to save after setting each attribute.
Normally, you only save a managed object when the code is done with it as saving resets the undo. In the set up you describe, you could safely generate hundreds of managed objects before saving them to permanent store. You can have a large number (thousands) of lightweight (text attributes) objects in memory without putting any strain on the iPhone.
The only problem on the iPhone is that you never know when the app will be suspended or shut down. This makes saves more common than on other platforms. However, not to the extent you now use.
Core Data Performance section of the guide might help you plan. Instruments allows you to see the details of Core Data performance.
However, I wouldn't do anything until you've tested the app with a great deal of data and found it slow. Premature optimization is the source of all evil. Don't waste time trying to prevent a problem you may not have.
To prevent a "sudden application stop" problem you can implement something like that method:
- (void)saveContext {
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
LogError(#"Unresolved error %#, %#", error, [error userInfo]);
// abort();
}
}
}
and use it inside two methods of your app delegate:
- (void)applicationWillTerminate:(UIApplication *)application;
and
- (void)applicationDidEnterBackground:(UIApplication *)application;
Thought it may not be the 100% solution, but in most of the cases it will do the work...