Core Data & iCloud (iOS5) - iphone

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.

Related

Getting Serious Application Error With Core Data

I got the following error when deleting an object from my app. Any ideas on how to fix it?
I'm still trying to recreate the process to find out exactly where it is crashing.
Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. CoreData could not fulfill a fault for '0x5adc8b0 <x-coredata://2B90C6EC-E046-4508-A1B1-6C4B19BF830D/Session/p1>' with userInfo {
NSAffectedObjectsErrorKey = (
"<Session: 0x6f7c830> (entity: Session; id: 0x5adc8b0 <x-coredata://2B90C6EC-E046-4508-A1B1-6C4B19BF830D/Session/p1> ; data: <fault>)"
);
*** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x5adc8b0 <x-coredata://2B90C6EC-E046-4508-A1B1-6C4B19BF830D/Session/p1>''
A) If you are subscribing to save notifications, Core Data is pointing to a failure there.
B) The "could not fulfill a fault" message is often related to deleting an object, after which some other part of your app tries to access ANY property on it. Yes, even ones where faults were previously fulfilled. The whole object is unusable after a delete.
This occurred in my code when I passed a managed object down to a custom NSOperation class. My NSOperation would load other objects through its to-many relationship, process them, then delete those objects. If it tried to do a search on more objects in the to-many relationship, it would sometimes crash with the "CoreData could not fulfill a fault" exception. Then I remembered reading about "thread confinement" in the Core Data Programming Guide so I changed my code to pass a key down to my NSOperation class. The NSOperation class would then do its own search in its own context to find its own instance of the core data class. I stopped getting the exception after that. (I still have to use save notifications.)

Setter failing selectively in NSManagedObject

I have a NSManagedObject which I'm trying to instantiate with given values. I access the setters like so:
object.couchID = (NSString *)[dictObject objectForKey:#"_id"];
...and this works fine on my machine, but my partner gets this error when he runs it on his machine:
'-[NSCFString type]: unrecognized selector sent to instance 0x4e465e0'
About 90% of the setters (all formatted in the same way) work on my partner's machine, but a good 10% fail with that error. All of them work on my machine.
We're running the exact same code (according to SVN (yes, I know)), and fetching the same data from the same server, so everything seems like it should work.
We've checked the objects being passed, and they're the same. Commenting out the setter allows the code to get through to the next troublesome setter, but of course we need it to actually work. How else should we troubleshoot? Thanks in advance.
Update 1: Unlocked the Tumbleweed badge for that one... guess it's too sticky to touch? Any thoughts or guesses are welcome. And hey, you could earn 50 points.*
Update 2: the mixed-good-news is that checking out a fresh version from source control results in the same problem on my machine, so a) it's definitely something in the code, and b) I can more actively troubleshoot. Thanks for all your suggestions so far, I'm going to go through them all again.
I ran into something similar at work the other day. I suspect that one of you has a stale .momd file inside the app bundle, and that it's not being replaced when it gets upgraded. I suspect this is a bug in Xcode 4, though I haven't totally verified it yet. If your partner deletes the app completely and then installs the app, does the error go away?
You may need to create a temporary variable whose value is object cast to whatever the actual class is, e.g.
MyClass *c = (MyClass *)object; // if object is in fact a MyClass instance
c.couchID = (NSString *)[dictObject objectForKey:#"_id"];
I have seen cases where the compiler cannot make mental leap and realize that your attribute is the class you know it is. The solution for me in these cases has been to be more explicit. Does this make sense? It's worth a shot at least, no? :-)
if this code fails on your partner's machine:
someManagedObject.couchID = #"some hardcoded string";
seems like you have a dangling pointer: i would check that someManagedObject is properly retained and still a valid object when you try to call the -setCouchID method on it.
I have had nearly the same problem when trying to draw a CATiledLayer with data in NSManagedObjects. What should be a valid object barfs with an "unrecognised selector" exception
It nearly always happens because theres no retain on the object external to the point where you are trying to set or get the property. Being in a separate thread seemed to have a relationship too.
After fruitlessly trying to get round this with [NSManagedObjectContext lock] and retain on the context within the new thread I eventually just threw the contents of my fetch into a mutable set to try and keep a grip on it which seems to work on iOS but not on OS X so well.
So a couple of possibilities
Are you doing this not in the main
thread and does the MOC have a retain
within that thread. Check the docs
for [NSManagedObjectContext lock]. But essentially each thread working with the context needs its own retain on the context.
Try throwing it into a container
while you operate on it. Make it a
bit stickier. Sorry if that sounds
like voodoo but it is.

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.

Reasons for NSManagedObjectMergeError error on [NSManagedObjectContext save:]

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.

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...