iOS Core Data - Why it works? - iphone

I am confused of the core data's logic.
I treated the core data as a database, and core data methods as a SQL query.
When I tried to update some object in a core data with a local memory's object, I found that I can make the feature just with below code:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Book" inManagedObjectContext:_managedObjectContext]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"identifier == %#", theBook.identifier];
[request setPredicate:predicate];
NSError *error;
Book *book = [[_managedObjectContext executeFetchRequest:request error:&error] lastObject];
if (error) {
NSLog(#"Error getting book to update: %#", error);
abort();
}
// NSLog(#"%d", book == theBook);
error = nil;
if (![_managedObjectContext save:&error]) {
NSLog(#"Error saving the book: %#", error);
abort();
}
theBook is the object which I want to update the core data's object with.
And I found that the log message says the two objects are same...
I fetched the request and didn't do anything but it works. Why does it work?

At first Core Data is not a database - it's an object graph, which could be saved to some kind of storage.
If your objects in there have unique identifier, then fetching with the same identifier value gives you the same object. Core Data does not create different instances of managed objects for the same entities (say book with identifier=7), so everywhere you'll deal with the same object, doesn't matter if you fetch it 15 times, you'll always end up with the same one.

Related

how to properly save into coredata one to many relationship

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]);
}
}
}

Will upgrading an application reuse the core data that already exists

I have already done core data migration to access all data that i had in the prior version of my application.Have an entity named coupon and have several attributes for that entity.But in my new version,i have one more attribute ,"username" for the same entity.Now at the launch of app,i have to fetch all those datas that i had in my older version and have to save all with a username.How can i update my data?Any suggestion will be appreciated.In this version i am always fetching data based on the username.
` NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity: [NSEntityDescription entityForName:#"Coupon" inManagedObjectContext: context]];
NSPredicate *newPredicate= [NSPredicate predicateWithFormat:#"username = %#", username];
[request setPredicate:newPredicate];
NSArray *objects = [context executeFetchRequest:request error:&error];
NSLog(#"%#arry",objects);
NSUInteger count = [context countForFetchRequest: request error: &error];
[request release];
if (count ==0) {
}
`
At present,the app crashes here since it does not have such an attribute.So i have to add that attribute before setting this predicate.Anymore details needed for a better solution?
In application:didFinishLaunchingWithOptions: you can check for the migration. If this is the first time the app is started after upgrading with the new data model, you can first modify all records by adding the username attribute.
//first fetch all Cupons
for (Coupon *c in fetchedObjects) {
c.username = whatever;
}
[self.managedObjectContext save:nil];

core data delete not work

I use the following code to populate a UITableView
self.navigationItem.leftBarButtonItem = self.editButtonItem;
test_coredataAppDelegate *appDelegate = (test_coredataAppDelegate *)[[UIApplication sharedApplication] delegate];
self._context = appDelegate.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"patients" inManagedObjectContext:_context];
[fetchRequest setEntity:entity];
NSError *error;
self.PatientsList = [_context executeFetchRequest:fetchRequest error:&error];
self.title = #"patients";
[fetchRequest release];
where PatientsList is NSArray
when I want to delete record I use
[self._context deleteObject:[_PatientsList objectAtIndex:indexPath.row]];
NSError *error;
if (![_context save:&error]) {
// Handle error
NSLog(#"Unresolved error series %#, %#", error, [error userInfo]);
}
[PatientsList removeObjectAtIndex:indexPath.row] ;
[self.tableView reloadData];
but this doesn't have any effect.
How can I solve this issue and delete successfully, especially when my model contain relations?
Here you delete it from db but not from array so you need to delete from array or call database for again load the arry with new db details.
so for first approach delete it from array make your array mutable and use
removeObjectAtIndex method for deleting the object
or in second approach you need to fetch data again from core data.
and then reload table by using
[table reloadData];
Could you post a little more information about the results you're getting from fetch requests before and after you call deleteObject:? You could also check the userInfo property of the NSError object that you pass to some Core Data functions as that often gives very help information in Core Data. Also remember that your modifications aren't "set in stone" until the data model is saved using the save: method of your NSManagedObjectContext instance.
The way Core Data handles the relationships of a deleted object is defined in your Core Data schema (.xcdata file), which you can edit in Xcode. You can set the delete action of relationships to "Deny", "Nullify", "Cascade" or "No Action" here. See the 'Relationship Delete Rules' section of the documentation found here.

Core Data error: _Unwind_Resume called from function _PFFaultHandlerLookupRow in image CoreData

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.

Most performant way to check how many objects are referenced by an to-many relationship in Core Data?

Lets say I have an employees relationship in an Company entity, and it's to-many. And they're really many. Apple in 100 years, with 1.258.500.073 employees.
Could I simply do something like
NSInteger numEmployees = [apple.employees count];
without firing 1.258.500.073 faults? (Well, in 100 years, the iPhone will easily handle so many objects, for sure...but anyways)
-count will not fire a fault. If you already have the parent object in memory then yes this is the best way to get that count.
If you do not have the objects in memory then the more performant way would be:
NSManagedObjectContext *moc = ...;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"child" inManagedObjectContext:moc]];
[request setPredicate:[NSPredicate predicateWithFormat:#"parent == %#", parent]];
NSError *error = nil;
NSInteger count = [moc countForFetchRequest:request error:&error];
NSAssert2(error == nil, #"Error fetching request: %#\n%#", [error localizedDescription], [error userInfo]);
[request release], request = nil;
Which performs a count at the database level.