Deadlock in fetch routine - objective-c

i am stuck with my first GCD and first core-data using application =)
two views access the same data (which is handled by a single DAO).
if i wait for the current view to finish loading its content no problem occors when changing view.
however: if i change the view (its tabbased) while one controller tries to fetch data from my model, the new controller tries the same and the threads 'collide' and my application freezes.
the freeze occurs in this line of code of my DAO:
NSArray *results = [managedObjectContext executeFetchRequest:fetch error:&error];
reloadAllMonth() accesses the fetch routine of my DAO
how i load the data in the first controller:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[self reloadAllMonth];
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self.allMonthTable reloadData];
});
in the second viewcontroller the first thing i do is update my DAO, this of course uses (beneath others) the very same fetch routine i called before:
[self.dataHandler updateData];
i have tried two approaches so far:
first using a c-semaphore:
-(NSArray *)fetchAllMonthExpenses{
//#return: array of all expenses in month (day & month type)
NSNumber *monthNumber = [self getMonthNumber:[NSDate date]];
NSEntityDescription *exp = [NSEntityDescription entityForName:#"Expense" inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetch = [[NSFetchRequest alloc]init];
[fetch setEntity:exp];
[fetch setPredicate:[NSPredicate predicateWithFormat:#"month == %#",monthNumber]];
NSError *error = nil;
sem_wait(&isLoading);
NSArray *results = [self.managedObjectContext executeFetchRequest:fetch error:&error];
sem_post(&isLoading);
return results;
}
second using the synchronized directive
-(NSArray *)fetchAllMonthExpenses{
//#return: array of all expenses in month (day & month type)
NSNumber *monthNumber = [self getMonthNumber:[NSDate date]];
NSEntityDescription *exp = [NSEntityDescription entityForName:#"Expense" inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetch = [[NSFetchRequest alloc]init];
[fetch setEntity:exp];
[fetch setPredicate:[NSPredicate predicateWithFormat:#"month == %#",monthNumber]];
NSError *error = nil;
#synchronized(self.class){
NSArray *results = [self.managedObjectContext executeFetchRequest:fetch error:&error];
return results;
}
}
sadly both of the approaches did not work, the application freezes whatever i do.
so my question is: what am i doing wrong (as i mentioned first time using threads), what am i missing, where should i look?
this has been keeping me busy for 2 days now and i cant seem to wrap my head around it :/

An NSManagedObjectContext and all of the NSManagedObjects inside it are not thread safe.
Whatever thread you use to create the context, that needs to be the only thread where you do anything relating to that context. Even just reading values from one of the managed object must be done on that thread and not on any other thread.
If you need two threads which both deal with the same database, you've got two options:
use dispatch_sync() to jump into the other thread momentarily to perform all read/write operations on the managed objects and/or the context
Or:
create a second NSManagedObjectContext in the other thread for the same database, and keep any changes made to the two contexts in sync.
The first option is much easier, but may remove much of the benefits of threading. The second option is harder, but it can be done, and there is a fairly good API for keeping two contexts on different threads in sync.
Lookup the Core Data Programming Guide for more details.

Related

Not getting data from Core Data

I am using Core Data to store some information for my app.
I have a .xcdatamodeld file containing 8 entities, and I extract them on different views.
In one of the viewControllers, I call three of them. Like this:
AppDelegate *appDelegate = (AppDelegate *) [[UIApplication sharedApplication]delegate];
managedObjectContext = appDelegate.managedObjectContext;
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entiAll = [NSEntityDescription entityForName:#"AllWeapons" inManagedObjectContext:moc];
NSFetchRequest *frAll = [[NSFetchRequest alloc] init];
[frAll setEntity:entiAll];
NSError *error = nil;
arrAll = [moc executeFetchRequest:frAll error:&error];
displayArray = [[NSMutableArray alloc]initWithArray:arrAll];
NSEntityDescription *entiRange = [NSEntityDescription entityForName:#"WeaponsRanged" inManagedObjectContext:moc];
NSFetchRequest *frRanged = [[NSFetchRequest alloc] init];
[frRanged setEntity:entiRange];
NSError *errorRanged = nil;
arrRange = [moc executeFetchRequest:frRanged error:&errorRanged];
NSLog(#"%i, %i", [arrRange count], [[moc executeFetchRequest:frRanged error:&errorRanged] count]);
NSEntityDescription *entiMelee = [NSEntityDescription entityForName:#"WeaponsMelee" inManagedObjectContext:moc];
NSFetchRequest *frMelee = [[NSFetchRequest alloc] init];
[frMelee setEntity:entiMelee];
NSError *errorMelee = nil;
arrMelee = [moc executeFetchRequest:frMelee error:&errorMelee];
NSLog(#"%i, %i", [arrMelee count], [[moc executeFetchRequest:frMelee error:&errorMelee] count]);
The problem is that the middle one (the one filling the arrRange-array) doesn't work..
arrAll logs out with all correct data, arrMelee logs out with all the correct data (x4 for some reason, don't know if this is related :S), but arrRange logs out as an empty array.
[arrRange count]; gives me 0, even though I know there is lots of data there.
I ran this code on the simulator, and found the .sqlite file, opened it in Firefox's SQLite Manager, and saw the correct data, 40 rows.
I went into the appDelegate, where I fill the CoreData when necessary, and saw that the method which downloads the data in JSON-format successfully sends it to the sqlite aswell.
Here I fill the CoreData with data from the json:
[self deleteAllObjects:#"WeaponsRanged"];
NSManagedObjectContext *context = [self managedObjectContext];
for(NSDictionary *item in jsonWeaponRanged)
{
WeaponsRanged *wr = [NSEntityDescription insertNewObjectForEntityForName:#"WeaponsRanged"
inManagedObjectContext:context];
///***///
wr.recoil = [item objectForKey:#"Recoil"];
///***///
NSError *error;
if(![context save:&error])
NSLog(#"%#", [error localizedDescription]);
}
And if I here do NSLog(#"%# - %#", wr.recoil, [item objectForKey:#"Recoil"]); I get the correct data. (Same data on both)
So. The correct data is obviously in the core. But my NSFetchRequest or something is failing. I am pretty noob at Objective-C, so it might be my bad code-grammar striking again. I realize I should use things again etc, not creating new objects all the time.. But cmon, this is my first app.. And if that is actually the problem, I might learn. But I'm stuck.
SOMETIMES I get data, sometimes I don't. It's weird. I re-launched the app, and got data from it, and now I don't.. I haven't found a pattern yet..
Anyone?
Or is there another way to request data from the entity?
I have some suggestions, too big for a comment.
1) after you create the WeaponsRanged, try reading them back:
for(NSDictionary *item in jsonWeaponRanged)
{
WeaponsRanged *wr = [NSEntityDescription insertNewObjectForEntityForName:#"WeaponsRanged"
inManagedObjectContext:context];
NSLog(#"IS WR Realized? %#", wr ? #"YES" : #"NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO WR");
///***///
wr.recoil = [item objectForKey:#"Recoil"];
///***///
NSError *error;
if(![context save:&error])
NSLog(#"%#", [error localizedDescription]);
}
// Now lets see if we can retrieve them:
{
NSEntityDescription *entiRange = [NSEntityDescription entityForName:#"WeaponsRanged" inManagedObjectContext:context];
NSFetchRequest *frRanged = [[NSFetchRequest alloc] init];
[frRanged setEntity:entiRange];
NSError *errorRanged = nil;
arrRange = [context executeFetchRequest:frRanged error:&errorRanged];
NSLog(#"Wrote %i items, read back %i items", [jsonWeaponRanged count], [arrRange count] );
}
2) In the viewController reading WeaponsRanged, add an assert before the fetch on mod:
NSLog(#"IS moc set? %#", moc ? #"YES" : #"NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO MOC");
EDIT:
3) Spread statements everywhere you access the MOC:
assert([NSThread isMainThread]);
[If you haven't used asserts before google and read up on the topic. These are a powerful tool for developers to find out about potential problems way before they manifest themselves in the gui or elsewhere. They are normally compiled out for release/distribution builds.]
This will force an exception if the thread is not the main thread, and then let you track down the reason by following the stack trace.
Nevermind! It was my own damn fault (yet again..).
The problem occured before the code I presented, and it turns out the data was never in the .sqlite-file when the problem was present.
This is what I had:
I collected data from the internet through json-request. I have told the app to check the "version" of the data through the internet, and if the data is outdated, then re-download it.
First, I download all data, then I add them to their own entity in Core Data. After downloading, I clear the current Core Data entity of the downloaded data. So on the top of each add-method it said i.e [self deleteAllObjectsOfEntity:#"WeaponsRanged"];, My whole problem was that in the addMelee-method, it ALSO said [self deleteAllObjectsOfEntity:#"WeaponsRanged"]; instead of #"WeaponsMelee", thus deleting all ranged weapons, and later adding melee to the melee entity. And that also proves that the other problem I mentioned of arrMelee logging out four times as much data as it should was caused by this.
The reason it sometimes worked was that the downloading is not happening in any ordered mode. So the addRanged was sometimes called before the addMelee. If ranged comes first, it clears the arrRanged, and fills it up with correct data, and THEN melee comes, and clears it out. When melee was called first, it cleared arrRanged and filled additional data to arrMelee, and THEN ranged comes and tries to clear an empty entity, and then fills it up with correct data.
The solution was obviously to change the entity deleted when adding it, as it was the wrong one.
Sorry.... :)

NSSortdescriptor ineffective on fetch result from NSManagedContext

I'm trying to sort my NSFetchRequest result using a NSSortdescriptor using a key pointing to a NSDate value. My fetch results come out totally random for no clear reason.
The NSManagedObjectContext I'm using is updated with a save from a nested child context created on a subclass of NSOperation. I know all this is done successfully because I can get all the data needed from the parent (main) context. Fetching from it just wont sort on date!
Strange thing is; predicates for selecting the entities (called "Tweet") between two dates works just fine!
Here's some code to illustrate my problem:
NSSortDescriptor* timeDescriptor = [NSSortDescriptor
sortDescriptorWithKey:#"time"
ascending:NO
selector:#selector(compare:)];
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:#"Tweet"];
[request setSortDescriptors:[NSArray arrayWithObjects:timeDescriptor, nil]];
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"((time >= %#) AND (time <= %#))",startDate,endDate];
[request setPredicate:predicate];
NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[context setParentContext:[[NSApp delegate] managedObjectContext]];
[context performBlock:^{
NSError* error = nil;
NSArray* results = nil;
results = [context executeFetchRequest:request error:&error];
// Results here are not ordered correctly
// 2nd try sorting results using fetched array (works!)
results = [results sortedArrayUsingDescriptors:[NSArray arrayWithObjects:timeDescriptor, nil]];
// This works too but not needed anymore
/*results = [results sortedArrayUsingComparator:^(id obj1, id obj2) {
Tweet* tweet1 = (Tweet*)obj1;
Tweet* tweet2 = (Tweet*)obj2;
//return [tweet1.time compare:tweet2.time]; // ascending
return [tweet2.time compare:tweet1.time]; // descending
}];*/
if ([results count] > 0) {
for (uint i = 0; i < [results count]; i++) {
Tweet* tweet = [results objectAtIndex:i];
NSDate* date = Tweet.time;
NSLog(#"tweet date: %#", date);
}
}
}];
Can anybody tell me why the NSSortDescriptor isn't working for my fetches?
Thanks!
-- Update --
It seems the NSSortDescriptor works fine when I fetch from the main (parent) managedObjectContext on the main thread without using the performBlock method. This still doesn't help me do sorted fetches on a NSPrivateQueueConcurrencyType managedObjectContext. Creating the NSFetchRequest, NSSortDescriptor and NSPredicate inside the performBlock doesn't fix the problem either.
I hit the problem as well.
I've found out that unless the data is saved all the way back to Persistent Store, the sorting won't work if the data in the master context is dirty, i.e. modified.
For example, if the contexts are clean, without pending changes, the sorting works.
If I only change one attribute of an entity in the parent context, then the sorting in the private queue child context doesn't work. That's very unfortunate. I also do sorting with array method now but it's not that fast as sorting in the NSFetchRequest, especially since my data is already indexed by that key. It would've been much faster to sort it in the fetch request.
My guess is that since there are unsaved changes in the context and NSFetchRequest goes to the SQLite database itself, where the changes do not yet exist (context not saved), it can't sort on the database level at all.
But overall, it's very confusing and smells like a bug.
I had exactly the same issue. I have solved the problem by setting the includesPendingChanges property in the NSFetchRequest instance to NO.
When using the default compare: selector, you can simplify the descriptor:
NSSortDescriptor* timeDescriptor = [NSSortDescriptor
sortDescriptorWithKey:#"time"
ascending:NO];
But that's an aside. I think the key is the fact that you're updating from a nested child context. Validate that your objects have permanent object ids; they might not have received them yet, and thus that might be the issue with your fetch. If it is, then try calling objectPermanentIDsForObjects: prior to saving the nested child context.

NSManagedObjectContext's registeredObjects won't return anything unless a fetch is executed beforehand

Cheers,
I'm experiencing a problem with core data, I guess I'm just looking in the wrong direction again.
My managedObjectContext will return an empty NSSet if I call registeredObjects on it. If I execute a fetch beforehand however, it will return the same objects that as the fetch did just a moment ago.
There's no multithreading going on.
Here's what I do:
[self setupContext]; // This will set up managedObjectContext, which is a property of this class
// Fetching...
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *foo = [NSEntityDescription entityForName:#"Foo" inManagedObjectContext:managedObjectContext];
[request setEntity:foo];
NSError *fetchError = nil;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:request error:&ftchError];
NSLog(#"Fetch returned %i objects.", [fetchResults count]);
[request release];
// Done fetching...
NSArray *allObjects = [[managedObjectContext registeredObjects] allObjects];
NSLog(#"Context contains %i objects...", [allObjects count]);
The store contains 30 objects. If I run the code above, both NSLogs will report five objects. If I remove the fetch part between the two comments, it will report zero objects for the whole context.
Note that I am at no point commiting or otherwise changing the contexts contents.
Do I need to force the context into refreshing itself first? I've never done this before though and I don't recall registeredObjects failing on me like this on other occasions in the first place.
Any suggestions appreciated!
Toastor
You may be confused about what registeredObjects means. This is the set of objects that are currently in the NSManagedObjectContext. This is not the set of objects in the store, just the ones in the context. If you haven't fetched or otherwise registered the objects in the context, then they won't be in registeredObjects.

NSFetchedResultsController predicate is a little too real time...?

So I've got an NSFetchedResultsController that I activate on ViewDidLoad with the managedobjectcontext that has been passed on from the appdelegate on load.
I put a predicate on some field let's call it "sectionNumber" and say it needs to equal 1 in my predicate.
NSFetchResultsController works fine until I add a new object to the MOContext...
I use MyMO *newObj = [NSEntityDescription insertnewentity]...
start filling the different fields
[newobj setName:#"me"];
[newobj setAge:12];
etc...
Once I put [newobj setSectionNumber:1] - it finds it at that very instant and causes the app to crash with different weird errors that all lead to EXC_BAD_ACCESS.
All of this happens on the MAIN THREAD.
Any ideas why? How could one get around that?
UPDATE:
It only happens when I use my saveMOC method which is called at the end of an NSXMLParser specific thread I spawned off. The saveMOC is called on a successful parse with the [self performSelectorOnMainThread].... If i just added the extra managedobject via ViewDidLoad (just to check weather this is related somehow to to threading) the problem does NOT occur.
So it's obviously something with the new thread even tho the selector should have been run on the main thread.
UPDATE #2:
This is my spawned thread for the XML Parser:
-(void)getAndParseXML {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
DLog(#"Online storage");
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:theUrl];
XMLTranslator *translator = [[XMLTranslator alloc] init];
[parser setDelegate:translator];
if ([parser parse]) {
//success call MOC change routine on main thread
DLog(#"success parsing");
[self performSelectorOnMainThread:#selector(saveMOC:) withObject:translator waitUntilDone:NO];
} else {
DLog(#"error: %#",[parser parserError]);
}
[parser setDelegate:nil];
[parser release];
DLog(#"XML parsing completed");
[pool release];
}
Then this is my saveMOC:
-(void)saveMOC:(XMLTranslator*)translator {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
for (NSDictionary *dict in [translator retrievedData]) {
APost *newPost = [NSEntityDescription insertNewObjectForEntityForName:#"APost"
inManagedObjectContext:managedObjectContext];
//parse time into NSDate
[newPost setTime:[formatter dateFromString:[dict objectForKey:#"time"]]];
//author, category, description
[newPost setAuthor:[dict objectForKey:#"author"]];
[newPost setCategory:[dict objectForKey:#"category"]];
[newPost setContent:[dict objectForKey:#"description"]];
//create a post id so that the validation will be alright
[newPost setPostid:[NSNumber numberWithInteger:[[dict objectForKey:#"postid"] integerValue]]];
[newPost setSectionDesignator:sectionDesignator];
}
This saveMoc method continues and has a [managedobjectcontext save:&error] and more... but it's not relevan to our case as my method crashes I've discovered thru commenting one line after another at the point where I set the sectionDesignator since it equals to the current predicate in my NSFetchedResultsController.
The problem is most likely in the NSFetchedResultsController delegate methods or the lack thereof.
When you add a new object to any context and then save the context, that changes the persistent store which triggers the FRC on any thread to begin an update of the tableview. All the index paths change, especially if you set a value for an attribute used as a sectionNameKeyPath. If the table ask for a cell during the update, it will cause a crash because the table can ask for a cell at a index path rendered invalid by the insertion of the new managed object.
You need to make sure you implement the FRC's delegate methods and that you send the table a beginUpdate message to freeze it while the FRC changes all its index paths.
I am sorry to admit that the problem this whole time was releasing an array that held the sort descriptors in the fetch request that was used within the FRC.
Looking at alot of examples I released that array tho unlike the examples I created my array with [NSArray arrayWithObject:.............];
So there was an overrelease each time the fetch request was accessed more than once.
Feel free to close this. Thank you everybody for your help. I discovered this when peter wrote to look at the whole stack and not just one frame.
I have further analyzed the problem and have realized it occurs inside the loop.
I have further understood that it only happens when I have more than one object, meaning that one FRC takes over after an object insertion into MOC and tries to come back to the for loop, it tries to access an object or a reference that's not there. I haven't found what object causes it and how to retain it properly.
Any suggestions?
Consider the following:
for (int i=0; i<2; i++) {
NSLog(#"%i",i);
APost *thePost = [NSEntityDescription insertNewObjectForEntityForName:#"HWBPost" inManagedObjectContext:managedObjectContext];
[thePost setCategory:#"CAAA"];
[thePost setContent:#"SSSSSS"];
[thePost setSectionDesignator:sectionDesignator];
}
If I change the for loop to i<1 meaning it only runs once, the app does NOT crash. As soon as it is more than one object insertion the app crashes.

How to switch UITableView's NSFetchedResultsController (or its predicate) programmatically?

I have a UITableView that displays a subset of a large number of entities named "Documents". The subset is defined by another entity "Selection". Selections are named, ordered list of documents.
It Works fine, except when I want to change the displayed selection at run time. I get only a blank list.
Basically, I need to change the predicate that my NSFetchedResultsController holds so that the new predicate uses the another Selection. I couldn't make it work. My last attempt is to get rid of the NSFetchedResultsController altogether and reallocate it:
- (void) displaySelection:(Selection *)aSet
{
self.currentSelection = aSet;
self.fetchedResultsController = nil;
// methods here don't all use the property but directly the ivar, so we must trigger the getter
[self fetchedResultsController];
[self.tableView reloadData];
}
And of course, the NSFetchedResultsController getter does the right thing:
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil) { return fetchedResultsController; }
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"DocInSelection" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"selection.identifier like %#", currentSelection.identifier];
[fetchRequest setPredicate:predicate];
<snip>
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
<snip>
return fetchedResultsController;
}
This code works the first time, because the Initial Selection is set. When displaySelection: is called, though, the tableview becomes blank.
A very similar question was asked at NSFetchedResultsController fetch request - updating predicate and UITableView
And the answer was to get rid of the NSFetchedResultsController. I don't want to do that, because NSFetchedResultsController brings a lot of useful goodies here (eg caching, partial loading...). The question still stands: how to "switch" data in a UITableView backed by a NSFetchedResultsController, where "switch" means having a different predicate, or even (not in my case) a different entity.
Note for the sake of completeness, that since the many-to-many relationship from Selection to Document is ordered, it is handled through an in-between lightweight entity called DocInSelection, which has an "ordering" property and two many-to-one relationships to Document and Selection.
Thanks for any suggestion.
Since NSFetchedResultsController(FRC) is an object, you can store instances of it like any other object.
One useful technique is to initialize and store several FRC in a dictionary and then set the tableview controller's fetchedResultController attribute to the FRC you need at the moment. This is useful for situations such as having a segmented control to sort on different attributes or entities in the same table. This technique has the advantage of maintaining the individual FRC caches which can speed fetches up significantly.
Just make sure to send the tableview itself a beginUpdates before you swap controllers and then an endUpdates when you are done. This prevents the table from asking for data in the narrow window when the FRC are being swapped out. Then call reloadData.
After I posted my question, I tried a variant of the code the OP of the other question showed. It works for me. Here it is:
- (void) displaySelection:(Selection *)aSet
{
if (aSet != self.currentSelection) {
self.currentSelection = aSet;
NSFetchRequest *fetchRequest = [[self fetchedResultsController] fetchRequest];
NSPredicate *predicate = nil;
NSEntityDescription *entity = nil;
entity = [NSEntityDescription entityForName:#"DocInSelection" inManagedObjectContext:managedObjectContext];
predicate = [NSPredicate predicateWithFormat:#"selection.identifier like %#", currentSelection.identifier];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[NSFetchedResultsController deleteCacheWithName:#"Root"];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[self.tableView reloadData];
}
While this may work there's a note in the iOS Reference Library that troubles me:
Important: You must not modify the
fetch request. For example, you must
not change its predicate or the sort
orderings.
Source: NSFetchedResultsController Class Reference
This additional note doesn't exist in the iOS 3.2 Reference Library.
Just wanted to point this out.
An important note: if you "overwrite" a fetchController object make sure you clear its .delegate first - otherwise you'll get crashes when deleting rows, etc as the old fetchController and its delegate get events.