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
Related
I am in a situation where i need to update transformable attribute in my entity in core data, until now i've tried every possible answer from google and stack overflow but did't achieve anything.
This is the method where i am saving object in core data, and my object which i am saving is an NSMutablDictionary type object.
-(void)didSaveToCoreData :(NSMutableDictionary *)newDict
{
#try {
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = appDelegate.managedObjectContext ;
DataModelSupport *entity = [NSEntityDescription insertNewObjectForEntityForName:#"CPIEntity" inManagedObjectContext:context];
if (newDict != nil) {
[entity.fixed_Model removeAllObjects];
entity.fixed_Model = newDict;
}
NSError *error ;
[context save:&error];
if(error)
{
NSLog(#"Error in Saving Data");
}
else
{
[self didFetchFromCoreDataModel];
NSLog(#"Successfully saved");
}
}
#catch (NSException *exception) {
[self spareMeFromTheCrash:exception];
}
#finally {
}
}
in this method i am saving a dictionary object of 19 key/value, at the first time and i am fetching it correctly in didFetchFromCoreDataModel method, but when i refresh the data and get dictionary of 18 key/value i save that dictionary in core data using the same method didSaveToCoreData and fetch it in the same way from didFetchFromCoreDataModel but it still show 19 key/value
DataModelSupport is a subclass of NSManagedObject.
In DataModelSupport.h:
#property (nonatomic,weak) NSMutableDictionary *fixed_Model;
In DataModelSupport.m:
#dynamic fixed_Model;
This is it for the DataModelSupport class.
Now here in this method i am fetching the same object form core data
-(void)didFetchFromCoreDataModel
{
#try {
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = appDelegate.managedObjectContext ;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CPIEntity" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setReturnsDistinctResults:YES];
[request setReturnsObjectsAsFaults:NO];
[request setResultType:NSDictionaryResultType];
[request setEntity:entity];
NSError *error ;
NSArray *arr = [context executeFetchRequest:request error:&error];
updatedfinalArr = [arr valueForKey:#"fixed_Model"];
if(error)
{
NSLog(#"Error");
}
else
{
}
}
#catch (NSException *exception) {
[self spareMeFromTheCrash:exception];
}
#finally {
}
}
And this is how my core data looks like:-
Any help is appreciated.
EDIT
I've implemented some changes in my code now in didSaveToCoreData method i am using this line of code to fetch the Entity by name
NSEntityDescription *descriptor = [NSEntityDescription entityForName:#"CPIEntity" inManagedObjectContext:context];
by this i am not creating new entity every time i call didSaveToCoreData method.
and this is how i am saving NSMutlableDictionary object
DataModelSupport *entity = [[DataModelSupport alloc]initWithEntity:descriptor insertIntoManagedObjectContext:context];
[entity.fixed_Model removeAllObjects]
entity.fixed_Model = newDict;
but still i am not getting correct result.
now when i refresh the data and save it using the above procedure explained in EDIT section, and fetch it, i get the updated data but it increase the number of objects, like on first attempt when i fetch i got 1 object in array, and on second attempt i got 2 objects and it goes like this, so when ever new data is added its not updating it but instead it add it in the entity s fixed_Model attribute and increase the number of object.
Lastly now i am using this line of code to get the last and update object from array in didFetchFromCoreDataModel method
NSDictionary *ddd = [[arr valueForKey:#"fixed_Model"]lastObject];
updatedfinalArr = [NSMutableArray arrayWithObject:ddd];
Your save method creates a new CPIEntity object each time. So, unless you delete the old object elsewhere in your code, I suspect your fetch is returning several objects, the first of which has the dictionary with 19 key/value pairs in the fixed_Model attribute, and the second/subsequent objects contain the 18 key/value pairs.
When you save, you should try to fetch the existing object first, and if you get zero results then create a new object. Then set the fixed_Model attribute of the new/existing object to your new dictionary.
EDIT
You are still inserting a new object each time (DataModelSupport *entity = [[DataModelSupport alloc]initWithEntity:descriptor insertIntoManagedObjectContext:context];). See below for an example of "fetch or create":
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = appDelegate.managedObjectContext ;
NSEntityDescription *descriptor = [NSEntityDescription entityForName:#"CPIEntity" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
request.entity = descriptor;
NSError *error;
NSArray *results = [context executeFetchRequest:request error:&error];
if (results == nil) {
// This implies an error has occurred.
NSLog(#"Error from Core Data: %#", error);
} else {
if (results.count == 0) {
// No objects saved, create a new one...
DataModelSupport *entity = [[DataModelSupport alloc]initWithEntity:descriptor insertIntoManagedObjectContext:context];
entity.fixed_Model = newDict;
} else {
// At least one object saved. There should be only one
// so use the first...
DataModelSupport *entity = [results firstObject];
entity.fixed_Model = newDict;
}
}
I've assumed for simplicity that newDict is not nil; amend as appropriate to handle that case.
Can you narrow down the problem?
Ie. can you compare the two Dictionaries..the original one with 19 values and the new one with 18 values?
Is there a particular entry which is not being 'removed'? That might point to a challenge with 'delete' (or the lack there of).
Alternatively, if you completely replace the content, what result do you get on fetch?
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 have a problem checking whether a particular attribute of an Entity exists in the Core Data Database (through predicates) before creating a new object; if the object exists, I'd rather return it than create a new object.
I have a simple App which has a table view with a plus button in the Navigation Bar; the user clicks that and is presented with a View Controller with 4 text fields. They fill in that information, press save and it gets saved to Core Data and displayed in the TableView (through the use of NSFetchedResultsControllers).
The data model is as follows:
Transaction Entity with isReceived BOOL attribute
Person Entity with name string attribute
Occasion Entity with title string attribute
Item Entity with amount string attribute
The transaction has a relationship to the Person (whoBy), Occasion (Occasion) and Item entities.
In the view controller with the save method, I have the code below which will insert new objects into the Transaction, Person, Occasion Entities, etc. Each Transaction is of course unique, but with each transaction, the user can select an existing PERSON and/or Occasion and if that person then does not exist, it will be created (likewise with the occasion).
I'm slightly confused as to the format of the code here.
EDIT: I have tried a combination of code and can just not get this working. In the code below, I'm referencing person.name in the predicate, but I also tried creation a local NSString variable to hold the self.nameTextField.text code but that did nothing. I tried creating a NSString property to reference it that way and that not work. I tried using the words MATCHES, LIKE, CONTAINS, == and every combination in-between.
- (IBAction)save:(id)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
Transaction *transaction= [NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:context];
Person *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
Occasion *occasion = [NSEntityDescription insertNewObjectForEntityForName:#"Occasion" inManagedObjectContext:context];
Item *amount = [NSEntityDescription insertNewObjectForEntityForName:#"item" inManagedObjectContext:context];
NSFetchRequest *personFind = [NSFetchRequest fetchRequestWithEntityName:#"Person"];
personFind.predicate = [NSPredicate predicateWithFormat:#"name == %#", person.name];
// I have tried every combination of the predicate like MATCHES, LIKE.
// I created a local NSString variable and an NSString property
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
personFind.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *matches = [context executeFetchRequest:personFind error:&error];
if (!matches || ([matches count] > 1))
{
// Handle Error
}
else if ([matches count] == 0)
{
person.name = self.nameTextField.text;
transaction.whoBy = person;
occasion.title = self.occasionTextField.text;
transaction.occasion = occasion;
}
else
{
person = [matches lastObject];
transaction.whoBy = person;
occasion.title = self.occasionTextField.text
transaction.occasion = occasion;
}
if (![context save:&error])
{
NSLog(#"Can't save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
Logically, what I want to achieve is:
When the user is adding a Transaction, check if it's for a new person or an existing one — if it's an existing one, choose it from a list of Persons (and when the user selects a person, get its NSManagedObjectID). If it's a new one, create it on the spot.
The same for the Occasion.
Set all the other fields of the Transaction object (amount, etc.).
My question is:
What predicate do I use to get this working?
When I put a break point in this method, a NEW NAME (one that doesn't exist before) correctly calls the else if ([matches count] == 0) method and if I create an entry with an existing name, it calls the
else
{
person = [matches lastObject];
transaction.whoBy = person;
occasion.title = self.occasionTextField.text
transaction.occasion = occasion;
}
Even with the this statement, it is still creating a new person object for the same name.
I will correctly implement the occasion after getting the person working, but I'm just lost on how to get this working.
Any help would be massively appreciated!
"Is this correct?":
No. You are creating a new Person and Occasion objects whether you are using an existing person/occasion or not.
First check for existence and only if the object not already exist, insert a new one.
Alternatively, if the person/occasion exist, delete the inserted object.
"How do I retrieve the managedObjectID for person/event?":
Person* person = /*Get an existing person object*/
NSManagedObjectID* personId = person.objectID /*This is the person object ID, will work for any NSManagedObject subclass*/
To find a person that start with a string str use this predicate in a fetch request:
/*UNTESTED*/
[NSPredicate predicateWithFormat:#"(name BEGINSWITH[cd] %#)", str];
Edit:
To be more precise, you practice find or create using something like this:
(this is very limited, and only good for a single object performance-wise)
(NOT TESTED)
- (NSManagedObject*) findOrCreateObjectByValue:(id)value
propertyName:(NSString*)propertyName
entityName:(NSString*)entityName
additionalInfo:(NSDictionary*)additionalInfo
context:(NSManagedObjectContext*)context
error:(NSError* __autoreleasing*)error
{
NSManagedObject* res = nil;
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:entityName];
[r setPredicate:[NSPredicate predicateWithFormat:#"%K == %#",propertyName,value]];
NSArray* matched = [context executeFetchRequest:r
error:error];
if (matched) {
if ([matched count] < 2) {
res = [matched lastObject];
if (!res) { //No existing objects found, create one
res = [NSEntityDescription insertNewObjectForEntityForName:entityName
inManagedObjectContext:context];
[res setValue:value
forKey:propertyName];
[res setValuesForKeysWithDictionary:additionalInfo];
}
} else {
if (error) {
*error = [NSError errorWithDomain:#"some_domain"
code:9999
userInfo:#{#"description" : #"duplicates found"}];
}
}
}
return res;
}
So now, your save: method should look something like:
(I assume here that the person name and occasion title are held by a UITextField on the view controller [txtPersonName and txtOccasionTitle respectively] )
- (void) save:(id)sender
{
//create a clean context so that changes could be discarded automatically on failure
NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[context setParentContext:[self managedObjectContext]];
//A Transaction is always created in save event, so add it to the context
Transaction* transaction = [NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:context];
__block NSError* error = nil;
Person* p = (Person*)[self findOrCreateObjectByValue:self.txtPersonName.text
propertyName:#"name"
entityName:#"Person"
additionalInfo:nil
context:context
error:&error];
if (!p) {
NSLog(#"Error: %#, person name: %#",error,self.txtPersonName.text);
return;
}
Occasion* o = (Occasion*)[self findOrCreateObjectByValue:self.txtOccasionTitle.text
propertyName:#"title"
entityName:#"Occasion"
additionalInfo:nil
context:context
error:&error];
if (!o) {
NSLog(#"Error: %#, occasion title: %#",error,self.txtOccasionTitle.text);
return;
}
transaction.whoBy = p;
transaction.occasion = o;
//Not sure what you are using this property for
transaction.item = [NSEntityDescription insertNewObjectForEntityForName:#"Item"
inManagedObjectContext:context];
NSManagedObjectContext* ctx = context;
if ([context obtainPermanentIDsForObjects:[context.insertedObjects allObjects]
error:&error])
{
//save your changes to the store
__block BOOL saveSuccess = YES;
while (ctx && saveSuccess) {
[ctx performBlockAndWait:^{
saveSuccess = [ctx save:&error];
}];
ctx = [ctx parentContext];
}
if (!saveSuccess) {
NSLog(#"Could not save transaction, error: %#",error);
}
} else {
NSLog(#"Could not obtain IDs for inserted objects, error: %#",error);
}
//Do what you have to do next
}
This is just for making things a bit clearer on what you should do to avoid duplications, and reuse existing objects.
I have a JSON request which returns different parameters, name for example. I would like to cache a variable for the name parameter, which I can view in the app later.
For example: name from JSON request = david. the variable is called firstname. firstname should equal "david". firstname should be stored so I can view it in all parts of my apps.
For something simple as a string, the quick and dirty solution is to store it in NSUserDefaults.
Storing
[[NSUserDefaults standardDefaults] setObject:firstname forKey:#"kUserFirstName"];
[[NSUserDefaults standardDefaults] synchronize];
Retrieving
NSString *string = [[NSUserDefaults standardDefaults] objectforKey:#"kUserFirstName"];
If it gets more complicated than that, you have to consider a more structured persistence store. A valid option is CoreData.
There exist a few frameworks that might help you in storing JSON resources in CoreData, the most interesting being RestKit.
First off, you might consider checking out RestKit as it successfully accomplishes a whole slew of server interaction and CoreData persistence in iOS.
I'm a little short on time (on a lunch break here) so I'll just lazily post an example from an app I have.
- (void)loadFiltersFromJSON {
NSString *path = [[NSBundle mainBundle] pathForResource:#"FreeFilterBank" ofType:#"json"];
NSData *filterData = [NSData dataWithContentsOfFile:path];
NSError *err;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:filterData options:kNilOptions error:&err];
if (err) { //TODO this can be removed prior to shipping the app
NSLog(#"%#", err);
}
NSArray *definedFilters = [json objectForKey:#"Filter"];
NSManagedObjectContext* moc = [self managedObjectContext];
for (NSDictionary *filter in definedFilters) {
NSString *name = [filter valueForKey:#"name"];
BOOL exists = [self alreadyExists:name inManagedObjectContext:moc];
if (!exists) {
NSString *imageNamed = [filter valueForKey:#"imageName"];
NSString *filterDesignator = [filter valueForKey:#"filterDesignator"];
NSString *paid = [filter valueForKey:#"paidOrFree"];
[self createFilterWithName:name imageNamed:imageNamed filterDesignator:filterDesignator paidOrFree:paid];
}
}
}
- (BOOL)alreadyExists:(NSString*)filterNamed inManagedObjectContext:(NSManagedObjectContext*)moc {
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"name == %#", filterNamed];
NSEntityDescription* description = [NSEntityDescription entityForName:#"Filter" inManagedObjectContext:moc];
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:description];
[request setPredicate:predicate];
NSError* error;
NSArray* fetchedResult = [moc executeFetchRequest:request error:&error];
if (error) {
NSLog(#"%#",error.localizedDescription);
}
if (fetchedResult.count == 0) {
return NO;
}
else {
return YES;
}
}
- (void)createFilterWithName:(NSString*)name imageNamed:(NSString*)imageName filterDesignator:(NSString*)designator paidOrFree:(NSString *)paid {
NSManagedObjectContext* moc = [self managedObjectContext];
Filter* newFilter = [NSEntityDescription insertNewObjectForEntityForName:#"Filter" inManagedObjectContext:moc];
newFilter.name = name;
newFilter.imageName = imageName;
newFilter.filterDesignator = designator;
newFilter.paidOrFree = paid;
NSError* error;
[moc save:&error];
if (error) {
NSLog(#"%#",error.localizedDescription);
}
}
TL;DR This loads data from a JSON stored in the bundle, checks the SQLite data store to see if we already have something with the same name, and creates a new persistent instance of this object if we don't.
Take this example for what you will, there are many many more invocations for serialized data pulled from the web and persistent data within iOS beyond this one example.
The easiest way is to use NSUserDefaults and set the key to #"firstname" and the value would be #"david". That being said, you might consider using a better persistence model like CoreData. You can also use an Sqlite database or have the key/value saved in a plist. There are a number of ways to do this.
For reference ,see this:
Save string to the NSUserDefaults?
I have copied all the local contacts and stored them in core data,now when i use time profiler instrument,it shows me a large amount of time is being spent on deleting local contacts from my app.Can anyone suggest me any optimization techniques to improve my code and app performance.
Here is my code for deletion of contacts from core data:
+(void)deleteContacts
{
[[LoadingIndicator currentIndicator]displayActivity:#"Deleting Old contacts"];
//fetch the data from core data and delete them because you are going to sync again
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSManagedObjectContext *managedObjectContext=[appDelegate managedObjectContext];
fetchRequest.entity = [NSEntityDescription entityForName:#"PersonEvent" inManagedObjectContext:managedObjectContext];
NSInteger *contactsIdentifier=1;
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"sourceflag == %d", contactsIdentifier];
NSArray * persons = [managedObjectContext executeFetchRequest:fetchRequest error:nil];
//error handling goes here
if (persons.count>0) {
for (NSManagedObject * person in persons) {
[managedObjectContext deleteObject:person];
}
}
NSError *saveError = nil;
[managedObjectContext save:&saveError];
[[LoadingIndicator currentIndicator]displayCompleted:#"Done"];
[[LoadingIndicator currentIndicator]hide];
NSLog(#"Finished deleting local contacts..");
}
The time profiler shows me 94.3% time is spent on
[managedObjectContext save:&saveError];
Any help would be appreciated
Thanks
Maybe PersonEvent entity has relationships that are configured with Cascade deletion rule? And maybe even objects on the other side of those relationships have similar deletion rules? Core Data will fire all those faults individually.
In this case you should prefetch those relationships when fetching for the objects to delete:
fetchRequest.relationshipKeyPathsForPrefetching
= #[#"relationshipName",
#"anotherRelationship",
#"anotherRelationship.subrelationship"];
If no objects are deleted in a cascade together with PersonEvent, and you just delete a lot of PersonEvent, you may try saving in batches. This will probably not reduce the total time needed to save all the deletions, but it won't lock context, persistent store coordinator, and the store file itself for one huge save.
const NSUInteger kBatchSize = 200;
NSUInteger currentCount = 0;
for (NSManagedObject *person in persons) {
currentCount++;
[context deleteObject:person];
if (currentCount == kBatchSize) {
currentCount = 0;
NSError *error;
BOOL saved = [context save:&error];
}
}
if (currentCount != 0) {
NSError *error;
BOOL saved = [context save:&error];
}
Firstly i want to say "Good Question". And appreciate the things on which you have concerns.
Answer :-
In core data managedObjectContext remains have the all changes in that same object it not update it to the actual database.
So when [managedObjectContext save:&saveError] called it starts updating it to core data database. So You can do optimisation by below way follow it & check the time profile performance.
NSError *saveError = nil;
if (persons.count>0) {
for (NSManagedObject * person in persons) {
[managedObjectContext deleteObject:person];
[managedObjectContext save:&saveError];
}
}
[[LoadingIndicator currentIndicator]displayCompleted:#"Done"];
[[LoadingIndicator currentIndicator]hide];
NSLog(#"Finished deleting local contacts..");
Hope it helps you ...!