I'm hoping someone can help me debugging an incredibly frustrating Core Data crash. I initially load data into two entities (representing 'speakers' and 'titles') I then load a third entity for 'sessions' and try to set relationships to 'speakers' and 'titles'. (from session to speaker and title is one-to-one. from speaker/title back to session is one-to-many). I use a integer key to set up each relationship.
Since a given speaker and title can point to multiple sessions, I wrote a function that searches for the appropriate managed object and returns the object. I then set it for the relationship.
This works fine for the speaker relationship but crashes consistently and horribly for the SECOND title. I've rewritten the code a couple of times in different ways and I always end up with the same problem. And the problem exists regardless of what title comes second. So I've gotta be doing something just fundamentally wrong but following along to the Core Data chapters in More iPhone 3 Development nothing jumps out to me. I'm hoping someone might see what I'm missing (and have been for days). (One last note: the crash occurs whether I save the managedObjectContext within the for loop or outside. Always on the second session). My endless thanks and first born child to whoever can help me with this.
Here's the relevent code that saves the session entity:
for (NSDictionary *session in self.sessions){
NSManagedObject *newSession = [NSEntityDescription insertNewObjectForEntityForName:[sessionEntity name] inManagedObjectContext:self.managedObjectContext];
[newSession setValue:[session valueForKey:#"ID"] forKey:#"id"];
[newSession setValue:[session valueForKey:#"notes"] forKey:#"notes"];
[newSession setValue:[session valueForKey:#"length"] forKey:#"length"];
//get the speaker value;
[newSession setValue:[self setupSpeaker:[session valueForKey:#"speaker"]] forKey:#"speaker"];
NSLog(#"now doing title");
//now get the title value;
[newSession setValue:[self setupTitle:[session valueForKey:#"title"]] forKey:#"title"];
NSLog(#"I got back this title:%#", [newSession valueForKey:#"title"]);
}
if (![self.managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
Here's the code that finds the appropriate speaker and title entity for the relationship (I realize it's pretty redundant)
-(NSManagedObject *) setupSpeaker:(NSNumber *)id {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Speaker" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id==%#",id];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *items=[self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
if ([items count]>=1){
return [items objectAtIndex:0];
}else{
return 0;
}
}
-(NSManagedObject *) setupTitle:(NSNumber *)id {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Title" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSLog(#"now looking for: %#", id);
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id==%#",id];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *items=[self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
if ([items count]>=1){
NSLog(#"found %#", id);
return [items objectAtIndex:0];
}else{
return 0;
}
}
And finally here's what the logs says on crash:
2010-02-20 16:48:17.134 iconf[1438:207] now looking for: 1
2010-02-20 16:48:17.136 iconf[1438:207] found 1
2010-02-20 16:48:17.156 iconf[1438:207] I got back this title:<NSManagedObject: 0x3b11a10> (entity: Title; id: 0x3d4a3c0 <x-coredata://B76F62BD-AC82-4335-9013-7529C2471F9C/Title/p6> ; data: {
id = 1;
session = (
0x3d51640 <x-coredata:///Session/t2765697F-14C9-4282-A067-10A2413732B834>
);
title = "Bill Gates Speaks";
})
2010-02-20 16:48:17.158 iconf[1438:207] now doing title
2010-02-20 16:48:17.158 iconf[1438:207] now looking for: 2
2010-02-20 16:48:17.159 iconf[1438:207] found 2
2010-02-20 16:48:17.161 iconf[1438:207] I got back this title:<NSManagedObject: 0x3b16fd0> (entity: Title; id: 0x3d4d7a0 <x-coredata://B76F62BD-AC82-4335-9013-7529C2471F9C/Title/p12> ; data: {
id = 2;
session = (
0x3b1b320 <x-coredata:///Session/t2765697F-14C9-4282-A067-10A2413732B835>
);
title = "Lecture on Frogs";
})
2010-02-20 16:48:17.161 iconf[1438:207] *** -[NSManagedObject compare:]: unrecognized selector sent to instance 0x3b11a10
2010-02-20 16:48:17.162 iconf[1438:207] Serious application error. Exception was caught during Core Data change processing: *** -[NSManagedObject compare:]: unrecognized selector sent to instance 0x3b11a10 with userInfo (null)
2010-02-20 16:48:17.163 iconf[1438:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSManagedObject compare:]: unrecognized selector sent to instance 0x3b11a10'
2010-02-20 16:48:17.163 iconf[1438:207] Stack: (
It sounds like your data model (should) look like this:
Speaker <-->> Session
Title <-->> Session
Where both Speaker and Title each have a to-many relationship to Session.
Based on this:
I use a integer key to set up each
relationship.
and your code, it looks like you are managing the relationships manually. Why are you doing this?! It is complicated and unnecessary. Set up and use real relationships in your Core Data data model.
Also, do not use "id" for an attribute name since it is a keyword in objective-C.
Thanks, below is a copy of the data model. You are right about the basic layout.
http://www.freeimagehosting.net/image.php?debf5866c1.jpg
The reason I'm setting the relationships manually is because this is the first time through the program so I'm doing the initial load of data from three separate plists (one for speaker, one for title, one for session). Is there a better way to do this? I could be fundamentally not understanding core data but it seems as though if I just created a new title entity each time I created the session entity I'd have a one-to-one relationship from title to session rather than the one-to-many relationship that I want. Thus I put in the id variables (which I've now renamed with no change in the error) to act as a key for the first load into core data. After that I'd of course use core data to manage all that. Is there a better way to do this?
Related
I am using core data with two different entieties
userinformation. - firstname ,lastname , city etc
phonenumber - number
user information and phone number between i have given To many realtionship
-(NSArray*)getAllPhoneBookRecords
{
// initializing NSFetchRequest
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
//Setting Entity to be Queried
NSEntityDescription *entity = [NSEntityDescription entityForName:#"userinformation"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSError* error;
// Query on managedObjectContext With Generated fetchRequest
NSArray *fetchedRecords = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *info in fetchedRecords)
{
NSLog(#"city name: %#", [info valueForKey:#"city"]);
NSLog(#"first name: %#", [info valueForKey:#"firstName"]);
NSLog(#"Last name: %#", [info valueForKey:#"lastName"]);
NSLog(#"first phonenumber is %#",[info valueForKey:#"number"]);
}
and I have calling this method
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
self.managedObjectContext = appDelegate.managedObjectContext;
self.fetchedRecordsArray = [[appDelegate getAllPhoneBookRecords]mutableCopy];
but it is show only first table data and than it come
NSLog(#"first phonenumber is %#",[info valueForKey:#"number"]);
it is showing Terminating app due to uncaught exception 'NSUnknownKeyException', reason:
'[<userinforamtion 0x84691b0> valueForUndefinedKey:]: the entity userinforamtion is not key value coding- compliant for the key "number
but in phonenumber entity i have taken attribute for number
Am i doing the wrong realtionship between user inforamtion and phonenumber ?
First, entity names are usually CamelCase. The core data model editor even enforces that, so you are already lucky your code does not crash right there.
Second, you should have Xcode create custom classes and use their accessors. That is exactly the way to prevent undefined key errors. You would set and access the attributes like this (also much more readable):
NSLog (#"Last name: %#", info.lastName);
info.lastName = #"Doe";
Third, you should be clear what you are accessing via the relationship. The relationship itself points to an instance of another entity. To get to that entities property you have to go another step.
PhoneNumber *phoneNumber = [info.numbers anyObject];
NSLog(#"Some phone number: %#", phoneNumber.number;
Finally, make sure your attribute and relationship names are correct. E.g. if you have a to-many relationship to phone numbers, the Info relationship to PhoneNumber should appropriately be called numbers. The order of numbers is not determined, so you don't know which is the "first".
Maybe you want to look into the possibility to use the ABAddressBook instead for your purposes.
I am quite new into saving into coreData and using iOS dev.
What I am trying to achieve:
I want to be able to have a user in my db that has a unique identifier / is pulled with idFB and that user can create and retrieve their work out routines.
How far have I gone?
I managed (I think) to create a method that properly retriev the routineName from the Routine entity that is associated with the right User. See the fetch method.
My problem:
I think I am not saving with the right entities relationship association User (usersExercise) <--->> Routine (userID). In order words I think my save method is not right... as I am saving the whole user to userID and it just doesnt feel right? Mainly because when it spits out the Routine.userID it pulls the whole associated user instead of a specific ID? i dont really know what to expect
Could anyone please help me build these method properly? I am very confused with the whole process of coreData saving and making the right relationships.
- (void) save {
Routine *newRoutine = [NSEntityDescription insertNewObjectForEntityForName:#"Routine" inManagedObjectContext:context];
newRoutine.users = [self getCurrentUser];
newRoutine.routineName = #"myRoutine Test Name";
NSError* error;
[context save:&error ];
NSLog(#"Saved now try to fetch");
[self fetch];
}
-(void) fetch {
NSFetchRequest *fetchRequestItems = [[NSFetchRequest alloc] init];
NSEntityDescription *entityItem = [NSEntityDescription entityForName:#"Routine" inManagedObjectContext:context];
[fetchRequestItems setEntity:entityItem];
User* user = [self getCurrentUser];
// if i try [[self getCurrentUser] usersRoutine] it shows an error
[fetchRequestItems setPredicate:[NSPredicate predicateWithFormat:#"users == %#",user]];
//Sort by last edit ordered
NSArray *sortDescriptors = [NSArray arrayWithObjects:nil];
[fetchRequestItems setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSArray* Routines = [context executeFetchRequest:fetchRequestItems error:&error];
NSLog(#"result %#", [(Routine *)Routines[0] users] );
}
-(User *)getCurrentUser {
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
if (_appDelegate.isFB)
{
request.predicate = [NSPredicate predicateWithFormat:#"idFB LIKE %#",_appDelegate.fdID];
NSError *error = nil;
NSArray *matches = [[context executeFetchRequest:request error:&error] mutableCopy];
return (User *)matches[0];
} else
{
NSLog(#"CreateRoutinePOPUP NON FB TO BE TESTED");
request.predicate = [NSPredicate predicateWithFormat:#"email LIKE %#",_appDelegate.currentUser];
NSError *error = nil;
NSArray *matches = [[context executeFetchRequest:request error:&error] mutableCopy];
return (User *)matches[0];
}
This is what the NSLog in fetch is printing:
2013-04-28 22:33:26.555 iGym[7916:c07] result <User: 0xa480580> (entity: User; id: 0xa495a00 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/User/p1> ; data: {
dob = "1986-12-26 00:00:00 +0000";
email = ".com";
firstTime = nil;
gender = male;
height = nil;
idFB =3333;
idUserExternal = 0;
idUserInternal = 0;
isPT = nil;
language = "en_US";
location = "London, United Kingdom";
metricSystem = nil;
name = Joan;
nickname = nil;
password = nil;
surname = Thurft;
usersExercise = "<relationship fault: 0xa4824a0 'usersExercise'>";
usersRoutine = (
"0xa495f00 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p6>",
"0xa4877e0 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p1>",
"0xa4877f0 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p2>",
"0xa487800 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p3>",
"0xa487810 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p4>",
"0xa487820 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p5>"
);
weight = nil;
})
also when i add NSLog(#"get current result %#", [(User *)matches[0] usersRoutine] ); to the getCurrentUser method I get the whole user's data and the relationship says
usersExercise = "<relationship fault: 0xa464730 'usersExercise'>";
Core Data is not exactly like working with a standard database where you assign some foreign key like userID to another table where you want a relationship to the User object and then use that foreign ID to find the relationship like exercise.where('user_id = ?', userID). Instead, you define actual relationships and let Core Data handle everything behind the scenes for setting up any join tables or foreign keys.
Instead of how you have it set up, you'd just have in the User entity two relationships for exercises and routines that are mapped to the Exercise and Routine entities and then you'd have an inverse relationship on the Exercise and Routine called users if it's a has-and-belongs-to-many relationship. So now, you need to replace usersExercise with exercises, usersRoutine with routines and then userID with users for the Exercise and Routine entities.
Even if you don't actually need that inverse relationship, you still need it since Core Data uses it for data integrity purposes and Xcode will give you a warning if you leave it unpopulated.
When you set up those relationships, then you would call the routines or exercises like user.exercises which will return the associated set of exercises for that user. As you noticed, Core Data will return what they call a fault for a relationship that will get fired and the data returned when you actually need the contents of that relationship. Faults are there so that you are only returned exactly what info you need instead of running unnecessary queries on the data set.
Another thing to note is that Core Data doesn't reference unique id's like userID as you are doing. Instead, each object within Core Data has a unique ID found by [objectName objectID] (which is only permanent after it's been saved to the data store). You really shouldn't need to setup a unique ID as an attribute on an entity except for special cases.
Also, you really shouldn't need to use those unique objectID's unless you're passing objects around like in a multi-threaded application for background processing in which case NSManagedObjectID is thread-safe and you can use it to find the object again on a background thread/managed object context.
I'd really recommend reading a good intro to Core Data such as http://www.raywenderlich.com/934/core-data-on-ios-5-tutorial-getting-started
It can be a little strange at first converting to Core Data if you're used to normal database setup/architecture, but once you get used to it, it's actually a lot faster and handles all of the hard work behind the scenes for you.
Update from the comments:
You're misunderstanding the concept of relationships in Core Data. In Core Data, a relationship does not return an associated ID like a typical database join relationship would. Instead, it returns a fault which gets fired when you need the data from that relationship. So it's not returning the entire User object, but a fault to the associated User object which gets fired and queried when you do something like exercise.user.name
Your code is working exactly like it should be when you're saving, you are just under the incorrect assumption that it's not.
You need to use the provided method to add a "many object" in the one to many object. In your case it is called addRoutineObject:
Try this new save method:
- (void) save {
Routine *newRoutine = [NSEntityDescription insertNewObjectForEntityForName:#"Routine" inManagedObjectContext:context];
newRoutine.routineName = #"myRoutine Test Name";
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
NSArray *matches;
NSError *error = nil;
if (_appDelegate.isFB)
{
request.predicate = [NSPredicate predicateWithFormat:#"idFB LIKE %#",_appDelegate.fdID];
matches = [[context executeFetchRequest:request error:&error] mutableCopy];
} else
{
NSLog(#"CreateRoutinePOPUP NON FB TO BE TESTED");
request.predicate = [NSPredicate predicateWithFormat:#"email LIKE %#",_appDelegate.currentUser];
matches = [[context executeFetchRequest:request error:&error] mutableCopy];
}
if (matches.count == 0)
{
NSLog(#"no user matched");
}
else
{
User *aUser = [matches objectAtIndex:0];
[aUser addRoutineObject:newRoutine];
if (![context save:&error])
{
NSLog(#"couldn't save: %#", [error localizedDescription]);
}
}
}
I'm getting this weird error from Core Date and I cant understand why.
The code below is executed when I delete a row of a UITableView.
I pass a string and an object to the method below and it fetches the article in a database table that has that string and has a foreign key to that object. Then I delete that object and reload the table.
- (void)deleteFavorite:(NSString *)link inFolder:(Favorites *)f {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *favsDecriptor = [NSEntityDescription entityForName:#"Favorites" inManagedObjectContext:context];
[request setEntity:favsDecriptor];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(belongsTo == %#) AND (link = %#)", f, link];
[request setPredicate:predicate];
NSError *error = nil;
NSMutableArray *fav = [[NSMutableArray alloc] init];
fav = [[context executeFetchRequest:request error:&error] retain];
if (![context save:&error]) {
NSLog(#"Cannot fetch the story from the fetch request.");
}
NSLog([[fav objectAtIndex:0] title]);
error = nil;
[context deleteObject:[fav objectAtIndex:0]];
if (![context save:&error]) {
NSLog(#"Can't delete the fav! %#", error);
}
}
The app instantly crashes and I get this message in the console.
But when I launch the app afterwards, the row has been deleted.
Detected an attempt to call a symbol in system libraries that is not present on the iPhone:
_Unwind_Resume called from function _PFFaultHandlerLookupRow in image CoreData.
Please help!
Thanks in advance to everyone!
This is probably related to a bug within Core Data itself. I had the same error come up (I asked about it here in SO) and my only fix was to change the keywords in the predicate that still allowed the same results. It took some experimenting to find the right combination. Not ideal, but that's the best answer I can offer based on my experience.
Is it possible that you are holding a reference to the delete object or that the deleted object is an observer and is getting a callback after its been deleted? I had something similar to this recently, though slightly different error message. In my case, I also crashed upon deletion (under some conditions) but when I relaunched the object-to-be-deleted had, in fact, been deleted.
If you haven't already done so, under the Run menu select Stop on Objective-C Exceptions. This helped me track down the root cause of my crash. In my case it was KVO observer getting callback of change of value of a property of deleted NSManagedObject.
I am new to iPhone development trying to figure out how to setup a one to many relationship Core Data. I have two Entities setup One Leagues which has a one to many relationship to an entity Teams. So lot's of teams in a league. Think all the teams that play baseball and are in the MLB.
I am pre-filling the data when the user first enter's their username and password, so I have the leagues populate first, which I have no problem with. Then when I begin adding the teams I do a search looking for league. Code is below.
- (NSMutableSet *)checkItem:(int *)identifier inTable:(NSString *)table inContext:(NSString *)context {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:table inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:context,identifier];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *items = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
if ([items count] >= 1) {
return [items objectAtIndex:0];
} else {
return nil;
}
}
By calling this and then set up the team entry and save ('league' is my relationship):
NSMutableSet *leagueObjectSet = [self checkItem:lid inTable:#"Leagues" inContext:#"id=%i"];
NSManagedObject *teamObject = [NSEntityDescription insertNewObjectForEntityForName:#"Teams" inManagedObjectContext:managedObjectContext];
[teamObject setValue:[NSNumber numberWithInt:tid] forKey:#"id"];
[teamObject setValue:[dict valueForKey:#"name"] forKey:#"teamName"];
[teamObject setValue:leagueObjectSet forKey:#"league"];
[self saveAction];
When I run this I get this error message in the console.
"the entity Teams is not key value coding-compliant for the key "league"."
Am I doing this right? With everything I have read it seems like I am. I come from a MySQL background so be gentle!
From your description, I believe that you have a wrong setup for your one to many relationship. Assuming you have two entities named League and Team, and that you have correctly setup the relationship, when you want to relate a team to a league, your league object must not be a NSMutableSet as shown in your code snippet: it must be a NSManagedObject too. A League object, being on the to many side of the relationship will have a property named teams or similar which will be a NSMutableSet. A Team object, on the one side of the relationship will have instead a property named league or similar which is a League object.
Then, to setup the relationship you can do as you did using key value coding, but for performance reasons it is best to do
teamObject.id = yourId;
teamObject.teamName = #"your team name";
teamObject.league = yourLeagueObject;
This is much faster and suggested by Apple.
I am trying to write a simple table view editor for a Core Data entity. Unfortunately I'm running into problems.
The error occurs when adding the very first entity to the table. The process for bringing up the modal dialog is as follows:
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Group" inManagedObjectContext:context];
insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
NSManagedObject *newManagedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
NameEditController *dialog = [[NameEditController alloc] init];
dialog.managedObject = newManagedObject;
[newManagedObject release];
UINavigationController *navCtrlr = [[UINavigationController alloc] initWithRootViewController:dialog];
navCtrlr.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[[self navigationController] presentModalViewController: navCtrlr animated:YES];
[navCtrlr release];
Inside of NameEditController, I have this after the Done button is pressed:
NSString* name = self.nameLabel.text;
[self.managedObject setValue:name forKey:#"name"];
NSError *error = nil;
if (![managedObject.managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
UIViewController *ctrl = [self parentViewController];
[ctrl dismissModalViewControllerAnimated:YES];
The very first time I create an object (when the list is empty) I get this:
Exception was caught during Core Data change processing: [ valueForUndefinedKey:]: the entity Group is not key value coding-compliant for the key "(null)".
If I fill out the 'name' field before bringing up the dialog, I am able to add the first entity successfully:
[newManagedObject setValue:#"New Group" forKey:#"name"]; // this works
I am using NSFetchedResultsController to manage the table view BTW.
Thanks!
In your first code block, the third line of code appears to be out of context. Not sure if there's something there that could be contributing.
Secondly, the easiest way to get yourself an NSManagedObject into a NSManagedObjectContext from an entity name is to use the [NSEntityDescription insertNewObjectForEntityName:inManagedObjectContext:] selector.
So, I'd do something like:
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityName:#"Group" inManagedObjectContext:context];
You won't need to release newObject anymore, since the [NSEntityDescription insertNewObjectForEntityName:inManagedObjectContext:] selector will return an object with a retain count of 0. Also, make sure that you have NameEditController specifying its managedObject property as retained.
To address your actual issue, it sounds like maybe you have 'name' specified as a required property in your data model? A screenshot showing the details for 'name' in your data model would help.
Yarr... sorry guys, it was actually in my didChangeObject:atIndexPath:forChangeType:newIndexPath: function hastily copied from elsewhere. Apparently an exception thrown here can get obscured inside of the save: method.