Core data save not persisting - ios

I am trying to remove an item from my UICollectionView. The item is also saved using CoreData. Removing the item seems to work until I reload the view or restart the app. Both of which call getCards below. When that happens I the item is back and even in the Core Data database it seems the object has not been removed.
Code:
-(void)removeCard:(int)position{
UserModel *selectedUser = [self getSelectedUserFromDB];
CardModel *cardToRemove;
for(CardModel *cardmodel in selectedUser.cards){
if(cardmodel.position.intValue == position){
cardToRemove = cardmodel;
break;
}
}
int positionOldCard = cardToRemove.position.intValue;
[selectedUser removeCardsObject:cardToRemove];
NSMutableArray *cards = [selectedUser.cards.array mutableCopy];
NSLog(#"CARDSCOUNT: %d", cards.count);
//This changes the position of the cards to accomodate the removing of cards above
for(CardModel *cardmodel in cards){
if(cardmodel.position.intValue > positionOldCard){
[cardmodel setPosition:[NSNumber numberWithInt:(cardmodel.position.intValue - 1)]];
[selectedUser replaceObjectInCardsAtIndex:cardmodel.position.intValue withObject:cardmodel];
}
}
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Removing the card failed %#", error);
}
NSLog(#"Saved cards: %d", [self getCards].count);
}
-(NSMutableArray *)getCards{
UserModel *selectedUser = [self getSelectedUserFromDB];
NSMutableArray *cards = [[NSMutableArray alloc] init];
for(CardModel *cardModel in selectedUser.cards){
[cards addObject:[self modelToCard:cardModel]];
}
NSLog(#"Loaded cards: %d", cards.count);
return cards;
}
Output:
2015-03-17 11:13:56.259 BeNext[3593:349310] Loaded cards: 4
2015-03-17 11:13:56.260 BeNext[3593:349310] Saved cards: 4
2015-03-17 11:13:56.271 BeNext[3593:349310] Loaded cards: 4
//RELOADING VIEW
2015-03-17 11:14:02.351 BeNext[3593:349310] Loaded cards: 5

If you want to delete the card you need to call [context deleteObject:card model] before you call save - you are manipulating the relationship but this is not the same as actually deleting the card.

Related

My NSManagedObjectID is still temporary after saving. But why?

I saved my managedObjects. But my NSManagedObjectID is still temporary after saving. Why?
dispatch_async(privateQueue, ^{
__block NSMutableArray *ids = [NSMutableArray array];
[[[LPAppDelegate instance] privateContext] performBlockAndWait:^{
if ([responseObject isKindOfClass:[NSDictionary class]]) {
id arr = ((NSDictionary*)responseObject)[#"results"];
for (int i=0; i < ((NSArray *)arr).count; i++) {
LPFilm *film = [LPFilm MR_createEntityInContext:privateContext];
[ids addObject:film.objectID];
}
}
NSError *error = nil;
[privateContext save:&error];
if (error) {
NSLog(#"___fetch service error: %#", [error localizedDescription]);
}
}];
for (NSManagedObjectID *objID in ids) {
if (objID.isTemporaryID) {
NSLog(#"__tempID %#", objID);
}
}
});
When you save changes, new NSManagedObject instances get a new object ID. Previously existing instances of NSManagedObjectID are not converted in-place, they're replaced with new instances. But you have to ask the managed object for its new ID.
In your case, you're saving up an array of object IDs before saving. Those objects will never change, even if you save changes. But, if you go back to your managed objects and ask them for their object IDs again, you'll get different results, which will not be temporary.

Magical Records Update already Fetched Data

This is database design with to-many relations.This is my method
-(void)fetchChannelData:(id)responseObject{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *channels = [responseObject objectForKey:#"channels"] ;
for (NSDictionary *channelStack in channels) {
NSLog(#"Tenant id %#",[[ NSUserDefaults standardUserDefaults ] valueForKey:#"test"]);
ChannelsData *channels = [ChannelsData MR_importFromObject:channelStack inContext:localContext];
[channels setCategory:[[channelStack valueForKey:#"category"] firstObject]];
[channels setTenantID:[[ NSUserDefaults standardUserDefaults ] valueForKey:#"test"]];
NSArray *channelLogo = [channelStack objectForKey:#"channelImages"] ;
for (NSDictionary *channelLogoStack in channelLogo) {
ChannelImages *images = [ChannelImages MR_importFromObject:channelLogoStack inContext:localContext];
[channels addImagesObject:images];
[images setChannelID:channels.channelID];
}
}
} completion:^(BOOL contextDidSave, NSError *error) {
if (contextDidSave) {
[self fetchListing];
}
}];
}
Where I am importing my data. I have dropdown where I selected different API points for specific data. All fetching different data.Now issue is that when I launch my app again service is again called and instead data is updated it insert it again.I already Used relatedByAttribute on each entity letting their "ID" be the primary key.]1]1

Why can't I add each item into this array?

I'm using Parse and have a class of a few jobs with a rating (number 1 out of 5). I want to query for the class and stick each rating into an array, then calculate the average. However, when I try to add the objects into the array, it seems to only add the last item returned by the query, and I can't figure out why.
- (void)viewDidLoad {
[super viewDidLoad];
self.jobName = [self.job objectForKey:#"jobTitle"]; //jobName string
self.title = [NSString stringWithFormat:#"%#", self.jobName];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
PFQuery *getCompletedJobsQuery = [PFQuery queryWithClassName:#"completedJobs"];
[getCompletedJobsQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
self.completedJobs = objects;
for (PFObject *completedJob in objects) {
self.ratingsArray = [[NSMutableArray alloc] init];
NSLog(#"RATING: %#", [completedJob objectForKey:#"customerRating"]);
[self.ratingsArray addObject:[completedJob objectForKey:#"customerRating"]];
}
NSLog(#"ratings array ... %#", self.ratingsArray);
[self.tableView reloadData];
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
When it runs, it logs each rating, but when I try to print out the array or check its length, it only shows the last object and doesn't include the other objects:
2015-01-05 13:05:00.274 Ribbit[15538:251863] RATING: 3
2015-01-05 13:05:00.274 Ribbit[15538:251863] RATING: 5
2015-01-05 13:05:00.274 Ribbit[15538:251863] RATING: 4
2015-01-05 13:05:00.274 Ribbit[15538:251863] RATING: 5
2015-01-05 13:05:00.275 Ribbit[15538:251863] ratings array ... (
5
)
So, what's the proper way to retrieve things from Parse and put them into an array?
Edit: As oltman and Jack have indicated, I am re-creating the array each time the loop runs, so moving the array creation outside of the loop solves the problem. That is also my queue to take a break from coding for a few hours and sit in my cube of shame. Thanks!
You're creating a new array (self.ratingsArray = [[NSMutableArray alloc] init];) with each loop iteration. Move this line out of the loop (to before the loop) and you should come out of it with more than just the last object in the array.
You are recreating your array every iteration of the loop:
for (PFObject *completedJob in objects) {
self.ratingsArray = [[NSMutableArray alloc] init]; // HERE
NSLog(#"RATING: %#", [completedJob objectForKey:#"customerRating"]);
[self.ratingsArray addObject:[completedJob objectForKey:#"customerRating"]];
}
Move that outside of the loop:
self.ratingsArray = [[NSMutableArray alloc] init]; // HERE
for (PFObject *completedJob in objects) {
NSLog(#"RATING: %#", [completedJob objectForKey:#"customerRating"]);
[self.ratingsArray addObject:[completedJob objectForKey:#"customerRating"]];
}

Core Data sync with JSON API

My Data Model is named "Person" and it has 3 attributes "id", "firstName", and "lastName"
When importing the JSON data using AFNetworking I want to be able to check whether the entity already exists or not within Core Data using the "id" as the identifier. If it isn't there I would like to create it, and if it is there I would like to merge the item or update it.
right now I have a method called duplicateCheck which looks like:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id==%#", _person.id];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSError *error = nil;
[fetch setEntity:[NSEntityDescription entityForName:#"Person" inManagedObjectContext:self.managedObjectContext]];
[fetch setPredicate:predicate];
NSArray *items = [self.managedObjectContext executeFetchRequest:fetch error:&error];
for (NSManagedObject *object in items) {
// Not sure how to check from here and insert or update
// then save and call it during the API request?
}
I have a predicate set up but am not sure where to go from here. Is looping over each item the right way to go or am I going about this the wrong way?
Usually one would expect an identifier to be unique. therefor if the predicate return 0 objects, you know that this object is new. If 1 is returned you know that this object already exists and maybe you need to update it.
NSArray *items = [self.managedObjectContext executeFetchRequest:fetch error:&error];
if(items){
if([items count] == 0){
//the object is not present yet. create it.
} else if([items count] == 1) {
NSManageObject *obj = items[0];
//there is exactly 1 object. change it properties if needed
} else {
//what to do if several objects have the same identifier???
}
} else {
//handle error from the error object
}
So I commented with a link to a tutorial I wrote on this topic, but to narrow it down, this method may help guide you.
NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext];
//
// Iterate over all registered classes to sync
//
for (NSString *className in self.registeredClassesToSync) {
if (![self initialSyncComplete]) { // import all downloaded data to Core Data for initial sync
//
// If this is the initial sync then the logic is pretty simple, you will fetch the JSON data from disk
// for the class of the current iteration and create new NSManagedObjects for each record
//
NSDictionary *JSONDictionary = [self JSONDictionaryForClassWithName:className];
NSArray *records = [JSONDictionary objectForKey:#"results"];
for (NSDictionary *record in records) {
[self newManagedObjectWithClassName:className forRecord:record];
}
} else {
//
// Otherwise you need to do some more logic to determine if the record is new or has been updated.
// First get the downloaded records from the JSON response, verify there is at least one object in
// the data, and then fetch all records stored in Core Data whose objectId matches those from the JSON response.
//
NSArray *downloadedRecords = [self JSONDataRecordsForClass:className sortedByKey:#"objectId"];
if ([downloadedRecords lastObject]) {
//
// Now you have a set of objects from the remote service and all of the matching objects
// (based on objectId) from your Core Data store. Iterate over all of the downloaded records
// from the remote service.
//
NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:#"objectId" usingArrayOfIds:[downloadedRecords valueForKey:#"objectId"] inArrayOfIds:YES];
int currentIndex = 0;
//
// If the number of records in your Core Data store is less than the currentIndex, you know that
// you have a potential match between the downloaded records and stored records because you sorted
// both lists by objectId, this means that an update has come in from the remote service
//
for (NSDictionary *record in downloadedRecords) {
NSManagedObject *storedManagedObject = nil;
if ([storedRecords count] > currentIndex) {
//
// Do a quick spot check to validate the objectIds in fact do match, if they do update the stored
// object with the values received from the remote service
//
storedManagedObject = [storedRecords objectAtIndex:currentIndex];
}
if ([[storedManagedObject valueForKey:#"objectId"] isEqualToString:[record valueForKey:#"objectId"]]) {
//
// Otherwise you have a new object coming in from your remote service so create a new
// NSManagedObject to represent this remote object locally
//
[self updateManagedObject:[storedRecords objectAtIndex:currentIndex] withRecord:record];
} else {
[self newManagedObjectWithClassName:className forRecord:record];
}
currentIndex++;
}
}
}
//
// Once all NSManagedObjects are created in your context you can save the context to persist the objects
// to your persistent store. In this case though you used an NSManagedObjectContext who has a parent context
// so all changes will be pushed to the parent context
//
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"Unable to save context for class %#", className);
}
}];
//
// You are now done with the downloaded JSON responses so you can delete them to clean up after yourself,
// then call your -executeSyncCompletedOperations to save off your master context and set the
// syncInProgress flag to NO
//
[self deleteJSONDataRecordsForClassWithName:className];
[self executeSyncCompletedOperations];
}
}

How does this code use only updates into core data?

This is a SyncEngine from an RW tutorial. I need help understanding how only UPDATED records from the web are fetched and processed into Core Data.
- (void)processJSONDataRecordsIntoCoreData {
NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext];
// Iterate over all registered classes --- CHECK!
for (NSString *className in self.registeredClassesToSync) {
if (![self initialSyncComplete]) {
NSDictionary *JSONDictionary = [self JSONDictionaryForClassWithName:className];
NSArray *records = [JSONDictionary objectForKey:#"results"];
for (NSDictionary *record in records) {
[self newManagedObjectWithClassName:className forRecord:record];
}
} else {
NSArray *downloadedRecords = [self JSONDataRecordsForClass:className sortedByKey:#"objectId"];
if ([downloadedRecords lastObject]) {
NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:#"objectId" usingArrayOfIds:[downloadedRecords valueForKey:#"objectId"] inArrayOfIds:YES];
int currentIndex = 0;
//if dl count is < current index, there is an updated object dl from the web
for (NSDictionary *record in downloadedRecords) {
NSManagedObject *storedManagedObject = nil;
//Quick check to see if they indeed match, if they do, update the stored object with remote service objects
if ([storedRecords count] > currentIndex) {
storedManagedObject = [storedRecords objectAtIndex:currentIndex];
}
//Othwerwise its a new object and you need to create a new NSManagedObject to represent it in CDdb
if ([[storedManagedObject valueForKey:#"objectId"] isEqualToString:[record valueForKey:#"objectId"]]) {
[self updateManagedObject:[storedRecords objectAtIndex:currentIndex] withRecord:record];
} else {
[self newManagedObjectWithClassName:className forRecord:record];
}
currentIndex++;
}
}
}
// After all NSMO are created in your context, save it!
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"Unable to save context for class %#", className);
}
}];
// Cleanup time
[self deleteJSONDataRecordsForClassWithName:className];
[self executeSyncCompletedOperations];
}
[self downloadDataForRegisteredObjects:NO];
}
From what I understand, on the first or initial sync, it fetches JSONDictionaryForClassWithName which reads the downloaded data from disk and creates a newManagedObjectWithClassName.
My confusion is in the update else block. downloadedRecords is populated from JSONDataRecordsForClass which simply calls JSONDictionaryForClassWithName. Then it checks to see if there is at least 1 object in that array. If there is it does this:
NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:#"objectId" usingArrayOfIds:[downloadedRecords valueForKey:#"objectId"] inArrayOfIds:YES];
This fetches all managedObjectsForClass:sortedByKey which is below:
- (NSArray *)managedObjectsForClass:(NSString *)className sortedByKey:(NSString *)key usingArrayOfIds:(NSArray *)idArray inArrayOfIds:(BOOL)inIds {
__block NSArray *results = nil;
NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:className];
NSPredicate *predicate;
if (inIds) {
predicate = [NSPredicate predicateWithFormat:#"objectId IN %#", idArray];
} else {
predicate = [NSPredicate predicateWithFormat:#"NOT (objectId IN %#)", idArray];
}
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:
[NSSortDescriptor sortDescriptorWithKey:#"objectId" ascending:YES]]];
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
}];
return results;
}
The next bit which compares the [storedRecords count] > currentIndex is confusing. Can someone please explain this? I think my confusion lies in what the managedObjectsForClass method does with the usingArraysOfIds & inArrayOfIds.
I would expect that at some point it gets the the updatedAt field from the downloaded records and compares it to the updatedAt field of the CoreData fetched records.
This function is processing the stored JSON. The actual remote fetching and updateAt checking happens in downloadDataForRegisteredObjects and mostRecentUpdatedAtDateForEntityWithName.
[storedRecords count] > currentIndex is a bit crazy. Although in defense of the original programmer, writing any decent syncengine will quickly make you go googoo. Basically he needs to work out which records are existing and which ones are new and update the local data store accordingly, that's all.
I had another look and this code is actually horribly broken. It will only works if either you have the same records both locally and remotely. Or if the new objects have an objectId that sort-wise comes after the last object the local store has. Which is not the case with Parse objectId's.
If you are testing with just one device this works because new objects will be inserted locally before being pushed to the server. Therefor you will always have the same amount of records. If additional records get inserted any other way, this code will do weird things.

Resources