Updating core data entry - objective-c

I'm working with core data to save scores for a game, and need help updating an entry when a score is beaten. This is where I'm at. I write the data the first time the app launches like this:
LevelData *levelOne = [NSEntityDescription insertNewObjectForEntityForName:#"LevelData" inManagedObjectContext:context];
levelOne.levelNum = #"1";
levelOne.topScore = #"0";
levelOne.isPassed = #"No";
if (![context save:&error]) {
NSLog(#"coudlnt save: %#", [error localizedDescription]);
}
I then read out the data at the end of the level like this:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"levelNum == 1"];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"LevelData" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (LevelData *info in fetchedObjects) {
NSLog(#"Is Passed: %#", info.levelNum);
NSLog(#"Top Score: %#", info.topScore);
NSLog(#"Is Passed: %#", info.isPassed);
}
What I'm stuck doing is updating the topScore entry and writing it back to the data store without creating a new entry, but updating the existing one.
And help/example would be so so helpful and much appreciated.
Thanks, Kyle

Well, updating the entry from the data store is easy. Fetch, update, save. As you seem to have a handle on. The real issue with core data is defining that initial data store. The design of core data does not bring in the concept of a database, so typically, one does not exist until the backend implements it for you (after your application calls its first save).
What I have seen developers typically do to work around this is set up AppDelegate to implement a quick and dirty hack: save an empty data base. They then comment out this code; retrieve the file from disk, rename it, modify it, add it to the project's support files.
Now, revisiting AppDelegate, they test on application launch the existence of the datastore on the client's machine. If none exists, they know to inject the default data store from their support files.. basically copying it to the appropriate path and renaming it appropriately as well.
This gets overly complicated with versioning. Another approach to this is to write a plist of the defaults into the main bundle and import this on the first launch and store one key in user defaults to let me know if this is necessary. It requires a little more boilerplate code, but it is a great way to get things set up. And you can encode versioning data to support upgrade merging a lot easier. If you can handle core data well, manipulating XML shouldn't be much more difficult.

Related

Keep getting "'executeFetchRequest:error: A fetch request must have an entity.'" Even though I clearly set an entity :\

And yes, I've set up all the proper entities in my xcdatamodelId. I've been using CoreData for months now and have never run into this problem just yet. Is there something else I'm possible missing?
This is my code to pull data.
- (NSArray *)getCDRuneSets {
NSError *error;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Rune" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *cdRuneSets = [context executeFetchRequest:fetchRequest error:&error];
return cdRuneSets;
}
And I have an entity named "Rune" in my xcdatamodelId. Any ideas on what I could possibly be doing wrong? :\
Edit:
I also have my AppDelegate run this piece of code to instantiate the my CoreDataBank's (It's a StaticSingleton) context
CoreDataBank *bank = [CoreDataBank getBank];
[bank setContext:[self managedObjectContext]];
Another possible solution to this incase anyone finds it... Make sure your entity name in your model matches your specified entity in entityForName
I had an s in my model (as in plural) and not in my code :(

cocoa, table view with core data datasource

I am absolutely new to cocoa development so please don't chastise me for asking this.
Do I have to uses bindings? I know it saves a lot of code, but since I'm learning I'd really prefer to set everything up in the code.
My goal is to populate a table view with the entities I fetch from core data. I've created a custom controller class for my main window view. I also have some code to fetch my category entites
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Category"
inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjectsArray = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if (fetchedObjectsArray == nil) {
NSLog(#"Error occured fetching categories: %#", error);
}
for (Category *cat in fetchedObjectsArray) {
NSLog(#"categories %#", cat.name);
}
I know I have to implement the delegates for my table view, but I'm kinda lost on where to put my fetch code so the table view 1. has access to it and 2. uses the data returned from my fetch request.
You put the fetch code at that location of your program at which fetching is useful.
You store the data in a way that is convenient for you, probably a property typed array in your controller.
After fetching the data, you tell the table view, that there is new data to display by sending a reloadData (et al.) message to it.
The data source of the table view, probably your controller will receive messages defined within the data source protocol to transport the date into the view. Since this is code written by you, too, it is totally under your controller, how you get the data. (See step 2).

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

Deadlock in fetch routine

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.

Remove an Object/Row from a Core Data Entity

There are a couple answers similar to this (Like deleting an entire entity). But none that have worked for me. I am just looking for simple way to fetch the object I want to delete using NSPredicate and then delete only that object. I only want to delete one object/row. It should be fairly simple code, nothing too complicated. I would provide my code but I am pretty sure it won't help because it isn't even close to being complete or even working.
EntityToDelete *entity = (EntityToDelete *)[NSEntityDescription insertNewObjectForEntityForName:#"EntityToDelete" inManagedObjectContext:self.managedObjectContext];
NSError *error;
[self.managedObjectContext deleteObject:entity];
if (![self.managedObjectContext save:&error]) {
}