I am trying to add external data into SQLite / update existing data using Core Data.
Basically, I am given a JSON from external web service and I am using following piece of code to find out whether I should add new or update existing object in DB.
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
[fetch setEntity:[NSEntityDescription entityForName:#"name" inManagedObjectContext:context]];
[fetch setPredicate:[NSPredicate predicateWithFormat:#"id = %#", [data valueForKey:#"key"]]];
NSArray *results = [context executeFetchRequest:fetch error:nil];
if (results.count == 1)
{
// update existing
}
else
{
// add new
}
The problem is: sometimes this code leads to an exception:
Thread 1: EXC_??? (11) (code=0, subcode=0x0)
The exception is raised in NSManagedObjectContext executeFetchRequest:error:
If I continue execution of my app everything seems ok.
Should I worry about this exception?
I mean it's kind of annoying to have it but more important to know what is the cause and what are consequences of this exception?
Some additional detail (just in case it's relevant):
Code above gets executed multiple times in a loop (about 250 thousand times).
Code runs on main thread (yeah, I know, but it's kind of prototype).
Context was created on main thread.
External data is created in a background thread
[EDIT] Some more detail:
executeFetchRequest:error returns initialized array even when the exception was raised.
there is no error set when I provide error parameter to executeFetchRequest:error
That's not safe.
You should check the return of the method to make sure you was handed an array back
NSArray *results = [context executeFetchRequest:fetch error:nil];
if (!results) {
// An error occurred you should probably use the out error
}
Also CoreData seems to throw exceptions internally but handles them, so if you have an exception breakpoint set it will most likely get caught at random points from the CoreData stack - I'm saying this from past experience not sure if it's documented anywhere but it is mentioned in this video Debugging Tips - Mike Hay
Related
I've got CoreData concurrency violation assertions enabled in my project, which have been great for tracking down and fixing errors in our usage of it. However, I occasionally get issues on fetch requests where I can't determine what we're doing wrong.
All of our managed contexts are private queue concurrency types other than one that is a main queue concurrency type. This is a block of code that occasionally triggers the concurrency violation:
- (NSArray *)fetchFromCacheEntitiesWithClass:(Class)entityClass
predicate:(NSPredicate *)predicate
sortDescriptors:(NSArray *)sortDescriptors
managedContext:(NSManagedObjectContext *)managedContext
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:[entityClass entityName]];
request.predicate = predicate;
request.sortDescriptors = sortDescriptors;
request.fetchBatchSize = 30;
__block NSArray *results = nil;
[managedContext performBlockAndWait:^{
NSError *error = nil;
results = [managedContext executeFetchRequest:request error:&error];
NSAssert(!error, #"fetch had an error, investigate why");
}];
return results;
}
The entityName method exists on all of our CoreData classes, is generated from MOGenerator, and I've double checked that it's correct for all of our classes. The violation in this snippet happens on the executeFetchRequest call.
One thing to note is that we've only seen this occur in the simulator, never on a device. It has me leaning towards a simulator bug, though I don't normally like to assign blame to tools since developer error is so much more common.
I'm using the
-com.apple.CoreData.ConcurrencyDebug
argument on launch to debug concurrency in my CoreData app.
During app launch, I perform an asynchronous fetch on the main thread's managed object context.
// set up the async request
NSError * error = nil;
[MOC executeRequest:asyncFetch error:&error];
if (error) {
NSLog(#"Unable to execute fetch request.");
NSLog(#"%#, %#", error, error.localizedDescription);
}
This code is called from the main thread, but executeRequest: enqueues it to another thread, which I understand to be the correct behavior.
The concurrency debugger doesn't like this, saying (I reckon) that I'm doing something wrong here. I've also tried wrapping this in [MOC performBlock:] which also works, but also causes a multithreading violation. In both cases I get this :
[NSManagedObjectContext __Multithreading_Violation_AllThatIsLeftToUsIsHonor__
Am I using async fetches incorrectly, or is the concurrency debugger wrong here?
EDIT : I've also tried wrapping it in MOC performBlock which should ensure that it gets called from the main thread. In any case, the call is enqueued from the main thread, but executed elsewhere.
EDIT : here's the fetch request:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"MyEntity"];
NSPredicate * pred = [NSPredicate predicateWithFormat:#"boolProperty == YES"];
fetchRequest.predicate = pred;
NSSortDescriptor * sort = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
fetchRequest.sortDescriptors = #[sort]; fetchRequest.propertiesToFetch = #[#"prop1", #"prop2", #"prop3", #"prop4"];
NSPersistentStoreAsynchronousFetchResultCompletionBlock resultBlock = ^(NSAsynchronousFetchResult *result) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kFetchCompleteNotification object:result];
});
};
NSAsynchronousFetchRequest *asyncFetch = [[NSAsynchronousFetchRequest alloc]
initWithFetchRequest:fetchRequest
completionBlock:resultBlock];
Then I receive the results from the notification:
- (void)fetchCompletedNote:(NSNotification *)note {
NSAsynchronousFetchResult * result = note.object;
if (![cachedResults isEqualToArray:result.finalResult]){
cacheResults = result.finalResult;
[self.collectionView reloadData];
}
}
I think this is Apple's bug.
I filed bug report:
https://openradar.appspot.com/30692722
and added example project that reproduces issue:
https://github.com/jcavar/examples/tree/master/TestAsyncFetchCoreData
Also, if you don't want to disable flag just because of this issue you may want to swizzle __Multithreading_Violation_AllThatIsLeftToUsIsHonor__ method on NSManagedObjectContext just for this part of code.
You need to revert that after request is executed so you get violations for real issues.
The concurrency debugger is telling you that you are accessing MOC from the wrong thread/queue. You can only call -executeRequest: error: on the thread/queue that the context belongs to. If this is a NSMainQueueConcurrencyType then you need to be on the main thread. Otherwise, if it is a NSPrivateQueueConcurrencyType you need to use either -performBlock: or -performBlockAndWait: to run the execute on the correct queue.
I added a screenshot, see above. The request is enqueued from the main thread, but executed on another.
Ok, a couple of things:
Is that the line that is breaking/crashing or are you seeing the error output?
Your error handling is incorrect. You should be looking at the result of the -executeRequest: error: and if the result is nil then you are in an error state. The error variable can be populated even with a success.
I note that the code you posted in your screenshot is different than the code you posted previously. Did you add the -performBlock: or just not include it originally?
In my app, I use Core Data foundation to store my data (some images). I need to fetch images from my data base and display them in a table view. When my app is loading, it will fetch some images from database. However, I am quite confused that sometimes the method
[context executeFetchRequest: request error: &error]
would fail to fetch data from my database while sometimes it works well (I am quite sure the data is indeed in the database). I add some test code here:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Album" inManagedObjectContext:context]];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"number" ascending:YES];
request.sortDescriptors = #[sortDescriptor];
NSArray *matches = [context executeFetchRequest:request error:&error];
for (int i = 0; i < 10; i++) {
if ([matches count]) {
break;
}
matches = [context executeFetchRequest:request error:&error];
NSLog(#"try one more fetch for my data");
}
If the database is empty, the loop will exist after trying 10 times. But when my database is not empty, from NSLog method, I find that sometimes it fetched data successfully after trying 2 times and sometimes after 5 times, and sometimes after trying 10 times it still failed to fetch data. To be more specific, this phenomenon only happens when the first few [context executeFetchRequest: request error: &error] get executed. After that, the method works well.
So I want to know why does this happen, can anyone help me?
Your question is very generic. You should add details (what do you mean with fail, share your model, share the fetch request, etc)
Few tips here.
First of all executeFetchRequest:error: takes an error as param. So you have check it for errors.
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
// log error here
} else {
// log results here
}
Second, if you are using a SQLite persistent store you can see logs Core Data creates in the following way: XCode4 and Core Data: How to enable SQL Debugging.
Finally, if you are using a SQLite persistent you can also inspect saved data. A possible path could be:
/Users/NAME/Library/Application Support/iPhone
Simulator/VERSION/Applications/APP ID/Documents/DB FILE.sqlite
In addition to Dan Shelly's comment, are you performing any background operation to display images?
I'm still coding my RSS reader and I have gotten to the point where I'd like things to go smoother by background filling my Feeds at once with the newest Posts.
The problem is that it crashes my app quite badly with messages such as:
2013-10-02 21:06:25.474 uRSS[97209:a0b] *** Terminating app due to uncaught
exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource
must return a cell from tableView:cellForRowAtIndexPath:'
(stacktrace)
I came to the conclusion that I am not running thread safe here and I then discovered this kind of CoreData snippets:
//Core Data's NSPrivateQueueConcurrencyType and sharing objects between threads
[context performBlock:^{
// fetch request code
NSArray *results = [context executeFetchRequest:request error:nil];
dispatch_async(dispatch_get_main_queue(), ^(void) {
Class *firstObject = [results objectAtIndex:0];
// do something with firstObject
});
}];
// Assume we have these two context (They need to be set up. Assume they are.)
NSManagedObjectContext *mainMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType] autorelease];
NSManagedObjectContext *backgroundMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
// Now this can safely be called from ANY thread:
[backgroundMOC performBlock:^{
NSArray *results = [context executeFetchRequest:request error:nil];
for (NSManagedObject *mo in results) {
NSManagedObjectID *moid = [mo objectID];
[mainMOC performBlock:^{
NSManagedObject *mainMO = [mainMOC objectWithID:moid];
// Do stuff with 'mainMO'. Be careful NOT to use 'mo'.
}];
}
}];
Now, what I would like to know is the following:
should the backgroundMOC be defined as a Class member property, or everytime the method that uses it is invoked?
what if this method is itself invoked asynchronously (the RSS parsing method create the objects on the fly)?
How may I securely notify my UITAbleView that my MOC's been updated so that it can refresh without crashing?
Does this only apply to fetches, or also to objects insertions, deletions, etc?
Where could I find a working example of this concept successfully applied?
1) backgroundMOC should be defined in the scope, where you use it. Say, if you use context inside of SomeClass, it's good to define it as property of SomeClass. However, usually many classes share same context (for example, it's quite OK to share mainMOC between all your viewControllers) so i suggest to define mainMOC and backgroundMOC in your AppDelegate or some other singleton.
2) It's OK. However, it's bad idea to create contexts every time — see 1 and initialize them once in singleton.
3) Take a look at NSFetchedResultsController. It's exactly what you need to setup your tableView and track CoreData changes.
4) Yes
5) Cannot really point you to working example. Find something out on developer.apple.com =)
Also remarks:
1) Your class cannot be named Class
2) Use existingObjectWithID:error:, not objectWithID: — check this answer, it was really annoying issue in my experience
3) Read about NSManagedObjectContext concurrency patterns
What kind of errors can -[NSManagedObjectContext executeFetchRequest:error:] and -[NSFetchedResultsController performFetch:] return to the user and how should they be handled? I cannot find anything in the documentation about the possible errors for these methods. Also none of the error codes defined in CoreData/CoreDataErrors.h seem to apply to fetching.
Right now my error handling for Core Data fetches is just a NSAssert like this:
NSError *fetchError = nil;
NSArray *fetchedResults = [context executeFetchRequest: request error: &fetchError];
NSAssert( fetchedResults, #"Error fetching: %#", fetchError );
While testing I never had this assertion fail, but that doesn’t mean that this cannot fail. What are the best practices to handle those errors gracefully?
When executing a fetch request, I populate the expected array with an empty array instead of leaving it nil.
NSError *error;
NSArray *array = [context executeFetchRequest:request error:&error];
if (array == nil)
{
NSLog(#"Error retrieving array of values %#", error);
array = [NSArray array];
}
If you ever want to test your error handling, this answer details how to implement an NSPeristentStore that will give you an error every time.
Core Data Unit Testing - Unsure how to trigger error case in executeFetchRequest:error:
You already found CoreDataErrors.h, also see the Core Data Constants Reference
Possible errors that could occur would be SQLite query errors for example. I vaguely remember seeing something like this, when I used an operation in a predicate that was translated into something not supported by the SQLite version backing core-data.
If caught during development you use the NSError purely to debug. If this happens at run time in an already released application an option would be to fail gracefully and if possible be ask the user to specify a different search format.
First thing is to capture the error, but it entirely depends on the context of your code as to when you want to handle it gracefully or when you want to assert and stop anything else happening if things are going wrong.
Also remember that fetchedResults can return not nil, with no results (count == 0), which is not an error but you obviously might want to code against that.
NSError *fetchError = nil;
NSArray *fetchedResults = [context executeFetchRequest: request error: &fetchError];
if (fetchError) {
NSLog(#"Error with fetch: %#",error);
// Assert or do whatever is required ..
}
// Continue as normal ..
I always pass these errors up to the NSResponder chain, thus:
NSManagedDocument _document;
NSManagedObjectContext _moc;
NSError *error = nil;
NSArray *result = [_managedObjectContext executeFetchRequest:fr error:&error];
if (fetchedResults == nil && error) {
[_document presentError:error];
// or, if this isn't a document-based app, you can do
// [NSApp presentError:error];
// or, if this method is in an IBAction you can just do
// [sender presentError:error];
// and it'll just do the right thing
}
The default implementation in NSManagedDocument does an okay job of presenting these errors, except for situations when you are saving a doc and get multiple validation errors, in which case you need to write something special.
When in doubt, present NSError as soon as possible, and if you find yourself hard-coding a lot of retval checking for NSError, you problem might be more in what you're sending the error-returning function in the first place.
NSErrors, in general, are for users to resolve; NSExceptions are what the framework uses to let developers know what they need to deal with.