I am having trouble setting and retrieving a managedObjectId within a loop. First problem, I can't find in the docs what the parts of the MOID mean. So first question, are the following moids unique? The only way that they are different is in the last digit after the entity name, Item. If not, that could be the issue.
0xd000000054200000 <x-coredata://10EC1628-A6D4-487B-BF5C-61EAD9838132/Item/p5384>
0xd000000054240000 <x-coredata://10EC1628-A6D4-487B-BF5C-61EAD9838132/Item/p5385>
Second question, if they unique, when I retrieve the record associated with these ids, I end up retrieving the same record. So maybe there is a problem in the loop below.
Here is my code simplified slightly as there is a sync to server that I have not included.
//NSArray * myItems is an array of items to be saved
for (i=0;i<max;i++)
{
currentItem = myItems[i];
// Create Entity
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:self.managedObjectContext];
// Initialize Record
NSManagedObject *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
// Populate Record
[record setValue:currentName forKey:#"name"];
// Save Record
NSError *error = nil;
if ([self.managedObjectContext save:&error]) {
//Set moID in ivar of saved record
self.moID = [record objectID];
[self syncAndMarkSynced];
}
}//close loop
-(void) syncAndMarkSynced{
//sync to server omitted
Items *object = [self.managedObjectContext objectRegisteredForID:self.moID];
object.synced = #1;
}
First problem, I can't find in the docs what the parts of the MOID mean.
That's because they are not documented. The object ID is unique; the details are not explained because the parts of the URI are not intended to be meaningful on their own.
Second question, if they unique, when I retrieve the record associated with these ids, I end up retrieving the same record.
That's expected. A managed object has a unique ID. When you look up a managed object by ID, you're requesting the same entry from the persistent store. Each entry has a unique ID, so if you use the ID, you get that entry.
So maybe there is a problem in the loop below.
It's not clear to me what the loop is trying to do. Hopefully the information above will help you work it out.
I'm fetching code & desc from web by calling an API. Then loading it into tableView and based on multiple selection I'm saving the selected values into two arrays i.e. selectedCode and selectedCodeDesc. My Entity is:
So I want to [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error){ but don't know how. I know this much:
- (IBAction)confirmPressed:(id)sender {
NSLog(#"Selected Are: %# - %#",selectedDX,selectedDesc);
for (NSString *code in selectedDX) {
if (!_dxToAddEdit) {
self.dxToAddEdit = [MainCode MR_createEntity];
}
[self.dxToAddEdit setCode:code];
[self.dxToAddEdit setCodeDescription:#""]; //what to give here
[self.dxToAddEdit setSuperBill:_forSuperBill];
}
//after this I'm calling the saveToPersistent
So what to give at setCodeDescription?
If I understood correctly and based on your description and example of code you can do the following:
NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];
// Sorry, I renamed selectedCode to selectedCodes and selectedCodeDesc to selectedCodeDescriptions for readability.
// Not sure whether selectedDX is actually selectedCodes.
for (NSInteger i=0; i<selectedCodes.count; ++i) {
NSString *code = selectedCodes[i];
NSString *description = selectedCodeDescriptions[i];
Diagnoses *newDiagnose = [Diagnoses MR_createEntityInContext:defaultContext];
newDiagnose.code = code;
newDiagnose.codeDescription = description;
newDiagnose.superBill = _forSuperBill;
}
[defaultContext MR_saveToPersistentStoreAndWait];
Actually, I would not save the response into two separated arrays. Because of:
Your code becomes difficult to read
Imagine that the model will change and instead of two properties it will contain 4. You will have to create additional arrays.
I would recommend you to parse the response directly into the managed objects. Of course, you may not save them into persistent storage just populate your table view.
I highly recommend you to read these tutorials about Core Data. It will give you insight how to work with Magical Record library. Although, the library simplifies a lot of work it would be better to know what is under the hood ;]
Here is my issue, I am using core data to store around 58 documents. All they have is 4 NSString attributes. I have a helper class that is set up to retrieve documents whenever I need them, however when I passed back the array from my initial getAllDocumentsFromCoreData, all of the attributes seem to be null when accessed in downloadDocumentPDFsAndStoreOnDeviceViaWebService.
The weird thing is when I go to view the array fetched from core data in the getAllDocumentsFromCoreData method, it shows all of the documents/attributes correctly fetched.
What am I doing wrong? I'm relatively new to Core Data, so this could be a rookie mistake.
//USE TO RETRIEVE ALL DOCUMENTS CURRENTLY STORED WITHIN COREDATA
+ (NSArray *) getAllDocumentsFromCoreData
{
CoreData_Helper *helper = [[CoreData_Helper alloc] init];
NSManagedObjectContext *context = [helper managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setReturnsObjectsAsFaults: NO];
NSEntityDescription *entity = [NSEntityDescription entityForName: #"Document" inManagedObjectContext: context];
[fetchRequest setEntity: entity];
NSError *error = nil;
NSArray *fetchedDocuments = [context executeFetchRequest: fetchRequest error: &error];
if (error)
{
NSLog(#"%#", [error localizedDescription]);
}
return fetchedDocuments;
}
+ (void) downloadDocumentPDFsAndStoreOnDeviceViaWebService
{
NSArray *fetchedDocuments = [CoreData_Helper getAllDocumentsFromCoreData];
for (Document *document in fetchedDocuments)
{
NSLog(#"%#", [document fileID]);
}
}
This is happening because:
Managed objects don't have strong references to their managed object context
When a managed object context is deallocated, any managed objects fetched from it become inaccessible, with attribute values set to nil, because they no longer have any connection to the persistent store.
In your case you're allocating a managed object context in getAllDocumentsFromCoreData and performing your fetch. You return the results but the context gets deallocated at the end of the function. By the time you look at the returned array, the context is gone and the objects are useless.
You should create the managed object context somewhere else-- probably (though not necessarily) as a property of the object where these methods exist. It's typical to have relatively long-lived context objects rather than create them locally just before performing a fetch. There are various other techniques, but the key in your case is that you must not let the context be deallocated until you're finished with everything you've fetched from it.
I'm trying to learn Core Data, and am having trouble updating the MOC after adding new objects to an existing object. I can create the original object, a training day, and I can add exercise objects to that training day, but I can't figure out how to save the context so that later in my application I can find all exercises in a training day.
Any ideas??
Here is my code:
// Data from JSON
NSArray *trainingDayData = responseData[#"training_days"];
for (NSDictionary *aTrainingDay in trainingDayData) {
// Find the specific training day and save the MOC, creating the trainingDayObject
NSNumber *idTrainingDay = [NSNumber numberWithInt:[[aTrainingDay objectForKey:kID_KEY] intValue]];
VitTrainingDay *trainingDayObject = [VitTrainingDay trainingDayCreateOrObjectWithID:idTrainingDay];
// Configure the VitTrainingDay object's fields
trainingDayObject.name = aTrainingDay[#"name"];
trainingDayObject.order = aTrainingDay[#"order"];
}
// assign exercises to each trainingDayObject(this is inside a larger for loop)
trainingDayObject.userExercise = [NSSet setWithArray:userExerciseObjects];
// Below are attempt one and two to update the MOC after assigning exercises to the trainingDayObject.
// This works to save the updated MOC, but also adds two blank trainingDayObjects, since it 'insertNewObjectForEntityName', which I don't want.
trainingDayObject = [NSEntityDescription insertNewObjectForEntityForName:#"TrainingDay" inManagedObjectContext:self.context];
//This as far as I can tell is doing nothing. It just points to the conventional MOC save method. I pull it out below.
[self.coreDataManager saveContextForManagedObjectContext:self.context];
Here is the saveContextForManagedObjectContext method I call above:
- (void)saveContextForManagedObjectContext:(NSManagedObjectContext *)moc
{
NSError *error = nil;
if (![moc save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
}
I am a bit confused about your loops but assuming the first one is used to get each Training Day, sets some values and then get the Exercises for that day and set the relationships try the following.
Note that this line of code below is what creates the NSManagedObject, so you need to call it to create each TrainingDay object and each Exercise object.
trainingDayObject = [NSEntityDescription insertNewObjectForEntityForName:#"TrainingDay" inManagedObjectContext:self.context];
Not sure what this line does but presumably it creates a new object or returns one if it already exists
VitTrainingDay *trainingDayObject = [VitTrainingDay trainingDayCreateOrObjectWithID:idTrainingDay];
Unless it is also calling insertNewObjectForEntityForName then it should be replaced with a call that does create the NSManagedObject or searches and returns one with a matching ID. If it is calling insertNewObjectForEntityForName then you should remove the line below because that just creates another trainingDay object in the database without setting any attribute values.
Try something like this
// Data from JSON
NSArray *trainingDayData = responseData[#"training_days"];
for (NSDictionary *aTrainingDay in trainingDayData) {
// Find the specific training day ID
NSNumber *idTrainingDay = [NSNumber numberWithInt:[[aTrainingDay objectForKey:kID_KEY] intValue]];
//Create the Core Data Object
//Assume VitTrainingDay is a NSManagedObject subclass
VitTrainingDay *trainingDayObject = [NSEntityDescription insertNewObjectForEntityForName:#"TrainingDay" inManagedObjectContext:self.context];
// Set the attributes
trainingDayObject.ID = idTrainingDay;
trainingDayObject.name = aTrainingDay[#"name"];
trainingDayObject.order = aTrainingDay[#"order"];
// assign exercises to each trainingDayObject
for (SomeSourceObject *object in SomeExercisesSource) {
ExerciseObject *exercise = [NSEntityDescription insertNewObjectForEntityForName:#"ExerciseObject" inManagedObjectContext:self.context];
// Set the exercises parent object (training day)
exercise.trainingDay = trainingDayObject;
exercise.details = object.details;
}
}
NSError *error = nil;
if (![self.context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
} else {
NSLog(#"Save successful");
}
My guess is you need to search for an existing TrainingDay object with the same ID before creating a new one so if that is what this call [VitTrainingDay trainingDayCreateOrObjectWithID:idTrainingDay]; does then use it instead.
I'm new to Core Data and as such am not sure if I'm making a mistake. I've downloaded some data from a REST API and it successfully saves the JSON response to disk. I'm trying to process the data and save it persistently using Core Data.
NSLog(#"inserted objects: %#", [managedObjectContext insertedObjects]);
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"Unable to save context for class %#", className);
} else {
NSLog(#"saved all records!");
}
}];
I've successfully processed the JSON and added it to an NSManagedObjectContext. In the first line, it shows that I've successfully attempted to insert 2 objects.
inserted objects: {(
<User: 0xa259af0> (entity: User; id: 0xa259b70 <x-coredata:///User/t44BB97D0-C4B4-4BA6-BD25-13CEFDAE665F3> ; data: {
email = "vishnu#vishnuprem.com";
experience = "2013-07-20";
"first_name" = Vishnu;
id = 2;
"job_title" = Developer;
"last_name" = Prem;
location = "";
"phone_number" = "+6590091516";
"profile_pic" = "";
"thumbnail_profile_pic" = "";
"user_id" = 2;
}),
<User: 0xa25e460> (entity: User; id: 0xa25e4c0 <x-coredata:///User/t44BB97D0-C4B4-4BA6-BD25-13CEFDAE665F2> ; data: {
email = "sanchitbareja#gmail.com";
experience = "2013-07-20";
"first_name" = Sanchit;
id = 1;
"job_title" = Developer;
"last_name" = Bareja;
location = "";
"phone_number" = "+15106127328";
"profile_pic" = "";
"thumbnail_profile_pic" = "";
"user_id" = 1;
})
)}
When I attempted [managedObjectContext save:&error], it does so successfully and print out "saved all records" as expected. However, when I go to my application .sqlite file and check for added objects, I realize that it hasn't added any objects to the db.
On app relaunch, I print out a list of objects that are already in the database and it confirms that I've none saved yet.
Does anyone know what's going on and why I'm not able to save the data persistently even though it looks like I've successfully created the 'User' objects that needs to be saved in the Core Data model.
EDIT:
here is where I create the NSPersistentStoreCoordinator
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"RTModel.sqlite"];
NSError *error = nil;
NSLog(#"Test 1");
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSLog(#"Test 2");
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
I have 3 contexts.
masterManagedObjectContext
backgroundManagedObjectContext
newManagedObjectContext
master is parent of both background and new. When I query the contexts like this:
NSError *error = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"User"];
[request setSortDescriptors:[NSArray arrayWithObject:
[NSSortDescriptor sortDescriptorWithKey:#"id" ascending:YES]]];
[request setReturnsObjectsAsFaults:NO];
NSArray *testArray = [[[RTCoreDataController sharedInstance] newManagedObjectContext] executeFetchRequest:request error:&error];
for (User *obj in testArray) {
NSLog(#"obj.id %#", obj.id);
}
NSLog(#"query records: %#",testArray);
master and background both return the correct obj.id in the NSLog as well as gives the output below for #"query records"
(
"<User: 0xa3811d0> (entity: User; id: 0xa381230 <x-coredata:///User/t92BCED2D-CD17-49CC-9EBA-DF8F52F06A002> ; data: {\n email = \"sanchitbareja#gmail.com\";\n experience = \"2013-07-20\";\n \"first_name\" = Sanchit;\n id = 1;\n \"job_title\" = Developer;\n \"last_name\" = Bareja;\n location = \"\";\n \"phone_number\" = \"+15106127328\";\n \"profile_pic\" = \"\";\n \"thumbnail_profile_pic\" = \"\";\n \"user_id\" = 1;\n})",
"<User: 0xa382170> (entity: User; id: 0xa3820b0 <x-coredata:///User/t92BCED2D-CD17-49CC-9EBA-DF8F52F06A003> ; data: {\n email = \"vishnu#vishnuprem.com\";\n experience = \"2013-07-20\";\n \"first_name\" = Vishnu;\n id = 2;\n \"job_title\" = Developer;\n \"last_name\" = Prem;\n location = \"\";\n \"phone_number\" = \"+6590091516\";\n \"profile_pic\" = \"\";\n \"thumbnail_profile_pic\" = \"\";\n \"user_id\" = 2;\n})"
)
however "new" returns (null) for the obj.id in NSLog and returns the following for #"query records":
(
"<User: 0xa2b08a0> (entity: User; id: 0x95aebe0 <x-coredata:///User/tBFCC6C5F-7D2C-4AA0-BA96-B806EE360A762> ; data: <fault>)",
"<User: 0xa2b0910> (entity: User; id: 0xa4b9780 <x-coredata:///User/tBFCC6C5F-7D2C-4AA0-BA96-B806EE360A763> ; data: <fault>)"
)
From your code and the comments it seems that you are not saving the master context. Make sure you call
[managedObjectContext save:&error];
on all child contexts that save the data, and after that on the master context as well.
I just got done banging my head against essentially the same problem. A UITableViewController fetched a subclass of NSManagedObject from the NSManagedObjectContext, checked if an attribute was nil, and if it was downloaded the data, set that attribute, then saved the NSManagedObjectContext. Something like this:
MyManagedObject *mgObject = //get object from NSFetchResultsController
NSManagedObjectContext *mgObContext = mgObject.managedObjectContext;
if (!mgObject.data)
{
mgObject.data = [NSData dataWithContentsOfURL:urlWithData];
[mgObContext performBlock ^{
NSError *saveError = nil;
BOOL saveResult = [mgObContext save:&saveError];
if (saveError || !saveResult)
{
NSLog(#"Save not successful..");
}
}];
}
//do something with myObject.data
The save function was giving a YES boolean return and saveError was remaining nil, but if I quit the app and relaunched, when my Core Data loaded up my NSManagedObject subclasses, the data attribute was nil, and when this UITableViewController came back up, it had to download the data again.
I couldn't really find a solution to this anywhere… reading through the Core Data documentation didn't help. The solution came to me when I considered the difference between the above code and my code that sets the attributes in the NSManagedObject subclass's factory methods, which is basically:
MyManagedObject *mgObject = [NSEntityForDescription insertNewObjectForEntityName:#"MyManagedObject" inManagedContext:mgObContext];
mgObject.attribute1 = some value
mgObject.attribute2 = another value
The only difference is that I'm calling the factory methods from inside a [mgObContext performBlock:].
So the amended code is:
MyManagedObject *mgObject = //get object from NSFetchResultsController
NSManagedObjectContext *mgObContext = mgObject.managedObjectContext;
if (!mgObject.data)
{
[mgObContext performBlock: ^{
mgObject.data = [NSData dataWithContentsOfURL:urlWithData];
NSError *saveError = nil;
BOOL saveResult = [mgObContext save:&saveError];
if (saveError || !saveResult)
{
NSLog(#"Save not successful..");
}
}];
}
//do something with myObject.data
Which, thus far, is working perfectly. So I think anytime you made modifications to NSManagedObjects' attributes, you need to do so on the their NSManagedObjectContext's thread.
Figured I would also add some input to people who may have similar issues.
In my experience, attempting to save objects that don't have sufficient fields filled out don't seem to persist when saving, and no errors seem to be thrown when this is the case. Always double check that your fields are being filled in as expected before the save fires.
Another way to look at these types of issues is to flip the problem on its head. Maybe the object did in fact save, but the method in which you're verifying that they have in fact been saved is wrong. Often you might do this by querying CoreData for the record(s) using certain criteria. Double check that your criteria is correct and that it your query is actually returning what you expect.
If it does not return what you expect, it could be due to your own errors, but it could also be that the array storing your results isn't storing them properly. I have run into cases before where I had to rename an NSArray because something about the array name was causing referencing issues, and thus the array could not point to the results I was expecting. Cheers.
Add this after you save your data :
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
I know that this is not an answer to what the OP asked, but I wanted to share my experience about the same subject in case it will help someone else.
I had some issues with saving data persistently, anything seemed to help me fix it. The structure was very simple, an Entity with one field and one relationship (to-many). I made some changes to the class generated, NSMutableOrderedSet instead of NSOrderedSet.
I was not doing multi thread, or anything like that, just adding elements to the relationship. After saving, and re-launching the application, data just disappeared (elements added to the relationship).
I ended up discovering that there is a property called updated. After adding the new element to the relationship, I checked if this property changed its value. It didn't. So I had to create another field in the Entity, a Boolean, just to be able to force the entity to be saved after adding elements to this relationship.
entity.addObject(..)
entity.forceUpdate = true // without this line, it won't update
managedContext.save(..)
So I hope it helps anyone with the same problem, as I spent some time thinking that I was not saving it correctly..
I'm a beginner with iOS but I have done some example with CoreData to store users info.
First, you need to create your model with your entity (I suppose you have already done). In my example, my entity is called "User".
First, add a property similar to this
NSManagedObjectContext *context;
to your ViewController class.
Second, in your viewDidLoad method, add this two lines:
AppDelegate *appdelegate = [[UIApplication sharedApplication]delegate];
context = [appdelegate managedObjectContext];
And third, store your info:
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
NSManagedObject *newUser = [[NSManagedObject alloc]initWithEntity:entitydesc insertIntoManagedObjectContext:context];
[newUser setValue:(NSString *)[dictionary objectForKey:#"name"] forKey:#"name"];
[newUser setValue:(NSString *)[dictionary objectForKey:#"surname"] forKey:#"surname"];
...
NSError *error;
[context save:&error];
(I take my properties from a NSDictionary called dictionary)
To read your info:
AppDelegate *appdelegate = [[UIApplication sharedApplication]delegate];
context = [appdelegate managedObjectContext];
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entitydesc];
//NSPredicate *predicate = [NSPredicate predicateWithFormat:#"NULL"];
[request setPredicate:nil];
NSError *error;
NSArray *matchingData = [context executeFetchRequest:request error:&error];
//NSArray *matchingData = [context executeFetchRequest:nil error:&error];
// If the user is not logged in previously
if (matchingData.count <=0 ){
//self.displaylabel.text = #"No person find";
} else {
// If the user is already logged in
for (NSManagedObject *obj in matchingData) {
AppDataModel *appDataModel=[AppDataModel getInstance];
appDataModel.appUserInfo = [User alloc];
appDataModel.appUserInfo.name = [obj valueForKey:#"name"];
appDataModel.appUserInfo.surname = [obj valueForKey:#"surname"];
}
}
After hours of debugging, I found that the reason my updates weren't being saved was because in my subclass of NSManagedObject I defined by properties w/ #synthesize instead of #dynamic.
After I change it, it all saved as expected.
Hope that helped someone.
If objects are missing where you dont have an inverse relationship, you need to save both the entities before mapping. Check this example, which I created to demonstrate
how core data objects go missing and how to workaround, while working with Core data, for the case where you dont have an inverse relationship