Handle duplicates when inserting objects to core data entity - ios

In my application, I'm downloading data from web service with pagination. Output is a json array of dictionaries.
Now, I am saving the output json array in core data. So, my problem is, every time calls the saveInCoreData: method with the result array, it creates duplicate objects in the data base. How can i check for an object and update or replace the object if its already exists?
myId is a uniq key.
// save in coredata
+ (void) saveInCoreData:(NSArray *)arr{
// get manageObjectContext
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
if(arr != nil && arr.count > 0) {
for(int i=0; i < arr.count; i++){
SomeEntity *anObj = [NSEntityDescription
insertNewObjectForEntityForName:#"SomeEntity"
inManagedObjectContext:context];
anObj.description = [[arr objectAtIndex:i] objectForKey:#"description"];
anObj.count = [NSNumber numberWithInteger:[[[arr objectAtIndex:i] objectForKey:#"count"] integerValue]];
// Relationship
OtherEntity *anOtherObject = [NSEntityDescription
insertNewObjectForEntityForName:#"OtherEntity"
inManagedObjectContext:context];
creatorDetails.companyName = [[[arrTopics objectAtIndex:i] objectForKey:#"creator"] objectForKey:#"companyName"];
}
}

The most efficient way to avoid duplicates is to fetch all the objects you already have, and avoid processing them when iterating over the results.
Get the topicIds from the results:
NSArray *topicIds = [results valueForKeyPath:#"topicId"];
Fetch existing topics with these topicIds:
NSFetchRequest *request = ...;
request.predicate = [NSPredicate predicateWithFormat:#"%K IN %#",
#"topicId", topicIds];
NSArray *existingTopics = [context executeFetchRequest:request error:NULL];
Get the existing topicIds:
NSArray *existingTopicIds = [existingTopics valueForKeyPath:#"topicId"];
Process the results:
for (NSDictionary *topic in results) {
if ([existingTopicIds containsObject:topic[#"topicId"]]) {
// Update the existing topic if you want, or just skip.
continue;
}
...
}
Attempting to fetch each existing topic individually, within the processing loop, will be very inefficient in terms of time. The tradeoff is more memory usage, but as you are only getting 20 objects at a time, this should be a complete non-issue.

Related

IOS/Objective-C/Core data: Find index of an NSNumber in an array of NSManaged Objects

I perform a fetch from core data of nsmanaged objects. One of the attributes of the managed objects is an NSNumber, corresponding to the id of the objects (not Apple's objectid, my own sequential NSNumber id scheme). For a given object which has an id#, I would like to get the index of that object in the array. However, I can't figure out a way to get a list of the id#s as valueforkey does not work for nsnumbers and objectforkey throws an error with an nsmutablearray. Can anyone suggest how to do this?
Here is my non-working code. The problem is that I can't get a valid list of ids out of the fetched objects.
//FETCH ITEMS
- (id) getItems{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Items"];
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"lasttouched" ascending:NO];
[fetchRequest setSortDescriptors:#[sort]];
NSError *error = nil;
self.managedObjectContext = [IDModel sharedInstance].managedObjectContext;
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest
NSMutableArray *mutableresults = [results mutableCopy];
[mutableresults removeObjectIdenticalTo:[NSNull null]];
NSArray*someids =[[mutableresults valueForKey:#"myid"] mutableCopy];
//THIS IS EMPTY WHEN LOGGED
_someids = someids;//save to ivar
return mutableresults; //an array of activity objects with all their properties
}
// In method to get index of the object
NSInteger myIndex=[_someids indexOfObject:#33];
NSLog(#"anIndex%ld",(long)anIndex);
You need to remap the objects into a new array. For straight forward something like:
NSMutableArray *arrayOfIdentifiers = [[NSMutableArray alloc] init];
for(WhateverMyManagedObjectIs *item in results) { if(item.myid) [arrayOfIdentifiers addObject:item.myid]; }
This will result in array of ids as NSNumber objects only. Since NSNumber is an object the indexOfObject may not work. The reason is that it will compare pointers instead of values. So you will need to use compare method like:
NSInteger index = -1;
for(NSInteger i=0; i<_someids.count; i++) {
if([_someids[i] compare:#33] == NSOrderedSame) {
index = i;
break; // Break after first one is found
}
}
So in the end it is pretty much the same if you skip mapping your objects and simply compare it on the same object array:
NSInteger index = -1;
for(NSInteger i=0; i<results.count; i++) {
if([[results[i] myid] compare:#33] == NSOrderedSame) {
index = i;
break; // Break after first one is found
}
}

Edit CoreData object then save context

I have two entities, one called InProject that has several attributes and one relationship. the relationship is with another entity called Ins.
I am editing one of the Ins that is related to InProject. I used InProject attribute ID which then returns a NSDictionary value that has several key-values one of which is for an array of Ins. I then find the Ins I need to edit in a for loop I edit them, but then I become unstuck because I am not sure how to save the contect of InProject with the *updated Ins
I need to figure out how to save InProject after I have overwritten the Ins attributes I need to update.
This is what my code looks like after battling this problem:
- (void)editSelectedins:(NSString *)projIDString UpdatedNSD:(NSMutableDictionary *)updatedNSD DPC:(int)dpc{
// get context
NSManagedObjectContext *context = [self managedObjectContext];
if (context == nil) {
NSLog(#"Nil");
}
else {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"InsProject" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSMutableArray *InsProjectDictionaryArray = [[NSMutableArray alloc] init];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (InsProject *insProj in fetchedObjects) {
NSMutableDictionary *tempInsProjectDictionaryArray = [[ NSMutableDictionary alloc] init];
[tempInsProjectDictionaryArray setObject:insProj.companyName forKey:#"CompanyName"];
[tempInsProjectDictionaryArray setObject:insProj.projNo forKey:#"ProjNo"];
[tempInsProjectDictionaryArray setObject:insProj.desc forKey:#"Desc"];
[tempInsProjectDictionaryArray setObject:insProj.guid forKey:#"GUID"];
[tempInsProjectDictionaryArray setObject:insProj.projID forKey:#"ProjID"];
[tempInsProjectDictionaryArray setObject:insProj.ins forKey:#"ins"];
[InsProjectDictionaryArray addObject:tempInsProjectDictionaryArray];
}
// now that you have the InsProjects, choose the one you are curently working on in insView using the projectID
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ProjID==%#",projIDString];
[fetchRequest setPredicate:predicate];
// new array with one value that was created using the NSPredicate ProjID
NSArray *tempInsProjectArray = [InsProjectDictionaryArray filteredArrayUsingPredicate:predicate];
// get ins array out of the NSDictionary to edit
NSSet *inssForInsProject = tempInsProjectArray[0][#"ins"];
NSMutableArray *tempAllinss = [[NSMutableArray alloc] init]; // this will contain everything, that means all repeated values are included
for (Items* currItem in [inssForInsProject allObjects]) {
NSArray *keys = [[[currItem entity] attributesByName] allKeys];
NSDictionary *dict = [currItem dictionaryWithValuesForKeys:keys];
[tempAllinss addObject:dict];
}
NSArray *myArray = [tempAllinss copy];
// get the correct items from myArray anything whos dpc matches the dpc parameter of this method
NSMutableArray *editedinsArray = [[NSMutableArray alloc] init];
for (int i = 0; i < [myArray count]; i++) {
NSMutableDictionary *tempinssDictionary = [myArray objectAtIndex:i];
// if you get a match put it into the new editedinsArray to be edited
if ([[tempinssDictionary objectForKey:#"dpc"] integerValue] == dpc) {
[editedinsArray addObject:tempinssDictionary];
}
}
// by now you should have three things
// 1, access to your ins coredata object //this s wrong I actually have access to insProject
// 2, the values you need to be edited saved into a NSArray (editedinsArray, which will be used to check against and keep old values correct)
// 3, UpdatedNSD which will be used to update any values that need to be updated.
// go through your values and update the ins object
int i = 0;
for (ins *temp in editedinsArray) {
NSDictionary *currentEditedins = [editedinsArray objectAtIndex:i];
i++;
// these values should stay the same so use currentEditedins which contains old vals
NSString *stringToNumberDpc = [currentEditedins valueForKey:#"dpc"];
int tempDpcNum = [stringToNumberDpc integerValue];
NSNumber *dpcNumber = [NSNumber numberWithInt:tempDpcNum];
temp.dpc = dpcNumber;
NSString *totDQtyString = [currentEditedins valueForKey:#"totDQty"];
if ((NSNull *)totDQtyString == [NSNull null]) {
temp.totDQty = #"";
} else {
temp.totDQty = totDQtyString;
}
NSString *totShipString = [currentEditedins valueForKey:#"totShip"];
if ((NSNull *)totShipString == [NSNull null]) {
temp.totShip = #"";
} else {
temp.totShip = totShipString;
}
// values to be updated so use updatedNSD wthich was passed in as method param with the new vals
temp.newInsComp = [updatedNSD valueForKey:#"newInsComp"];
temp.newDryComp = [updatedNSD valueForKey:#"newDryComp"];
temp.updatedRow = [updatedNSD valueForKey:#"updatedRow"];
}
#warning --- I have no idea what to do here... i.e. how do I update the tempInsProjectArray.ins values I have just updated in the above for loop then save context which I hope would update insProj and the ins entities involved.
//save
[context save:&error];
}
}
As you can see at the bottom of the code with #warning I explain where I am having the issue. if I log temp inside the for loop I see the updated values perfectly the issue I am having is how do I then update the current tempInsProjectArray.ins values that I have just edited? then save them of course.
Your code is in great need of simplification. Some ground rules:
Use names with smallInitial and camelCase for variables. So not InsProjectDictionaryArray but insProjectDictionaryArray.
The same applies to dictionary keys indicating attribute names of managed objects. So projNo, not ProjNo.
Avoid cryptic abbreviations. Use plain and readable English Not projNo but projectNumber. What is an Ins? What is "dcp"?
Don't use the plural form for entity names. An suitable name for an item is Item, not Items
Don't use the mutable versions of dictionary and array when immutable ones would do.
Avoid duplicating your data, such as in [array copy].
Avoid dictionaries when you have an object graph. The object graph is what core data creates. It renders dictionaries with values and keys unnecessary.
Don't use IDs. The object graph renders those unnecessary as well in most cases. If you use IDs, do not use strings but numbers, such as long ints, or the object version NSNumber.
When fetching data from the Core Data persistent store, don't fetch all the data and the filter the result. Fetch only the data you need.
What you want to accomplish can surely be done in a few lines of code. I will try to summarize what you want to do as far as I understand it.
Your data model looks something like this:
Project <----->> Item
Where the items are in a to-many relationship called ins. I will rename this items. I will also assume that you will refactor your IDs to be of type NSNumber.
All the code up to myArray could be substituted with this:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:"Project"];
request.predicate = [NSPredicate predicateWithFormat:#"projectID = %#", projectID];
request.fetchLimit = 1;
NSArray *fetchedObjects = [self.managedObjectContext
executeFetchRequest:request error:nil];
Project *project = fetchedObjects[0];
You now have all items available simply with project.items. I understand that there could be more than one item with a mysterious attribute dcp of type int (i.e. NSNumber for managed objects), that is equal to the dcp parameter passed.
NSSet *matchingItems = [project.items filteredSetUsingPredicate:
[NSPredicate predicateWithFormat:#"dcp = %#", #(dcp)]];
Now it becomes a bit murky. Why do you have type ins in your for loop if the ins are actually of type Item? You then cast them into a dictionary... This should generate a compiler error. Or you have another class called ins instead of Ins??
Anyway, if you stay with the Items you can just update the values with what you pass in your dictionary:
for (Item *item in matchingItems) {
item.newInsComp = [updatedNSD valueForKey:#"newInsComp"];
item.newDryComp = [updatedNSD valueForKey:#"newDryComp"];
item.updatedRow = [updatedNSD valueForKey:#"updatedRow"];
}
[self.managedObjectContext save:nil];
Done!
BTW you could make it even shorter by setting the entity name of the fetch request to "Item" and setting the following predicate:
[NSPredicate predicateWithFormat:#"project.projectID = %# && dcp = %#",
projectID, #(dcp)];
If you know your InProject, then updating your Ins related to that project is a matter of editing property values on your managed objects.
Why not use the predicate to get an NSManagedObject of the InProject, then pull the relationship off of that and edit the values?
NSManagedObjectContext *context = [self managedObjectContext];
if (!context) {
return;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"InsProject" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Set the predicate on the Core Data fetch request instead
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"ProjID==%#",projIDString];
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
// We now have an array that has objects matching the projectIdString
// Might want to do some additional checks if you're only expecting zero or one objects
InsProject *aProject = [fetchedObjects lastObject];
// If we have no project, no point going any further
if ( !aProject ) return;
// On this NSManagedObject is an NSSet property with all related Ins objects
for ( Ins *anIns in aProject.ins ) {
// If our Ins item matches the passed dpc...
if ( [ins.dpc integerValue] == dpc ) {
// ...we have a match, edit properties
ins.dpc = #(dpc);
ins.newInsComp = [updatedNSD valueForKey:#"newInsComp"];
ins.newDryComp = [updatedNSD valueForKey:#"newDryComp"];
ins.updatedRow = [updatedNSD valueForKey:#"updatedRow"];
}
}
// These are managed objects, so saving the context saves all the changes
NSError *saveError;
[context save:&saveError];
if ( saveError ) {
NSLog(#"Save error: %#", [error localizedDescription]);
}

Cannot add Managed Object ID to NSMutableArray in GCD with Child Managed Object Child Context

I'm using GCD as well as Core Data. I'm aware Core Data is not thread safe, so I create child Managed Object Context (tempContext) with the mainContext as the parent. MainContext has a PCS to save data. What I'm doing is:
Pull posts from Facebook
Save each post to Core Data by creating a new Managed Object called Post, updating it and saving it
while I process each post, if it needs additional handling, I add the post.obectID to an NSMutableArray of Managed Object IDS. This NSMutableArray is used by another process to finish updating the posts. I am using object IDs because the separate process will not be in the same tempContext, thus I will fetch the Post from Core Data using the object ID.
The process gets data, and I see it is stored within store.data (I use the product called Base to view the physical database file's content). But, I cannot seem to store the post's Object ID. As you see in code, I am using NSLog to print out the post's object ID, and I see them, thus I know the object IDs are there. Code also shows I am doing [tempContext save] and I am doing [mainContext save:], thus I should now have permanent, not temporary object IDs for posts. Again, the data is within Core data's physical file, but the mutable array count is still = 0.
What am I doing wrong to save the object IDs to NSMutableArray? In code you will see commented out where I've tried running the [mutableArray addObject:p.objectID] even on the main Queue, but it does not matter.
Here is my code:
- (void)processPosts {
NSLog(#"-- Begin processPosts --");
dispatch_async(_processPostsQ, ^{
for (NSDictionary * info in _posts)
{
NSManagedObjectContext * tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
tempContext.parentContext = mainMOContext;
// If Post exists, rewrite over top of it. otherwise create a new one
NSFetchRequest * fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription * entity = [[mainMOModel entitiesByName] objectForKey:#"Post"];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"fb_post_id = %#",
[info objectForKey:#"post_id"]];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey:#"fb_post_id" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sd]];
NSError * error = nil;
NSArray * fetchResults = [tempContext executeFetchRequest:fetchRequest error:&error];
Post * p = [NSEntityDescription insertNewObjectForEntityForName:#"Post" inManagedObjectContext:tempContext];
if (fetchResults.count > 0)
{
p = [fetchResults objectAtIndex:0];
}
NSDictionary * appData = [[NSDictionary alloc]init];
appData = [info objectForKey:#"app_data"];
p.fb_actor_id = [info objectForKey:#"actor_id"];
p.fb_app_data = [self archivedData:[info objectForKey:#"app_data"]];
p.fb_attachment = [self archivedData:[info objectForKey:#"attachment"]];
p.fb_created_time = [info objectForKey:#"created_time"];
p.timestamp = [info objectForKey:#"updated_time"];
p.fb_attribution = NULL_TO_NIL([info objectForKey:#"attribution"]);
p.fb_message = NULL_TO_NIL([info objectForKey:#"message"]);
p.fb_type = NULL_TO_NIL([info objectForKey:#"type"]);
p.fb_post_id = [info objectForKey:#"post_id"];
p.fb_likes_info = [self archivedData:[info objectForKey:#"like_info"]];
p.fb_comments_info = [self archivedData:[info objectForKey:#"comment_info"]];
p.fb_parent_post_id = NULL_TO_NIL([info objectForKey:#"parent_post_id"]);
p.fb_permalink = NULL_TO_NIL([info objectForKey:#"permalink"]);
p.fb_photo_data = nil;
p.fb_place = NULL_TO_NIL([info objectForKey:#"places"]);
p.fb_source_id = [info objectForKey:#"source_id"];
p.social_network = #"facebook";
p.fkPostToFriend = [[FriendStore sharedStore]findFriendWithFBUID:[info objectForKey:#"source_id"] withMOContext:tempContext];
[tempContext save:&error];
dispatch_async(dispatch_get_main_queue(), ^{
NSError * error;
[mainMOContext save:&error];
});
if (appData.count > 0)
{
if ([[appData objectForKey:#"photo_ids"] count] > 0)
{
NSLog(#" -- photos need to be loaded for postID: %#",p.objectID);
//dispatch_async(dispatch_get_main_queue(), ^{
[_postsNeedingPhotos addObject:p.objectID];
//});
}
}
}
NSLog(#"ProcessPosts: num of posts needing photos: %d",_postsNeedingPhotos.count);
dispatch_async(_getPhotosQ, ^{
[self loadPhotoData];
});
NSLog(#"-- End processPosts --");
});
}
Thanks in advance for your help!
I'm an idiot. I forgot to initialize _postsNeedingPhotos with:
_postsNeedingPhotos = [[NSMutableArray alloc] init];
Now everything runs fine... :)

fetch coredata relationship

I would like to know how to fetch the items from my coredata relation ship. I guess it should be a dictionary or arrays or something that gets returned so that you can have your one to many thing.
I am quite lost at this I know how to write/read single objects but this relationship stuff is abit confusing.
I think I have sucsessfully written a relationship to coredata however now I would like to be able to read it to see if I have it right.. I have started writing the method for this but have no idea what to actually do to get all of the information out.
this is the code i have so far.. for both read and write
- (void)writeFin:(NSArray *)recivedProjectData ItemsData:(NSArray *)itemsData {
// WRITE TO CORE DATA
NSManagedObjectContext *context = [self managedObjectContext];
for (NSDictionary *dict in recivedProjectData) {
project = [NSEntityDescription insertNewObjectForEntityForName:#"Project" inManagedObjectContext:context];
project.proNumber = [dict valueForKey:#"ProNumber"];
project.projectDescription = [dict valueForKey:#"Description"];
// project.items = [dict valueForKey:#""]; // this is the relationship for project
}
for (NSDictionary *dict in itemsData) {
items = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:context];
items.Number = [dict valueForKey:#"Number"];
items.Description = [dict valueForKey:#"Description"];
items.comment = [dict valueForKey:#"Comment"];
items.project = project; // this is the relationship for items
[project addItemsObject:items];
}
NSError *error = nil;
if (![__managedObjectContext save:&error]) {
NSLog(#"There was an error! %#", error);
}
else {
NSLog(#"created");
}
}
- (NSMutableArray *)readFin {
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Project" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSMutableArray *projectDictionaryArray = [[NSMutableArray alloc] init];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (ProjectList *projectList in fetchedObjects) {
NSMutableDictionary *tempProjectDictionaryArray = [[ NSMutableDictionary alloc] init];
[tempProjectDictionaryArray setObject:project.proNumber forKey:#"ProNumber"];
[tempProjectDictionaryArray setObject:project.description forKey:#"Description"];
[tempProjectDictionaryArray setObject:project.projectID forKey:#"ProjectID"];
[projectDictionaryArray addObject:tempProjectDictionaryArray];
}
return projectDictionaryArray;
}
So just o reiterate, I would like to know A, is my write method look okay? B, how do you fetch/read the relationship objects from core data.
any help would be greatly appreciated.
A relationship in Core Data isn't an object, its a property which happens to correspond to another object in your model rather than just being a dead end. You're already most of the way there as far as checking whether your relationships are ok as far as I can see -- what you need to do is add one more line in your projectList
[tempProjectDictionaryArray setObject: project.items forKey:#"items"];
the object that you will have added will be an NSSet. You can then check that things are as they should be with a loop like this after you've finished setting things up
NSSet itemsForProject = projectDictionaryArray[someIndex][#"items"]
for (Item* currItem in [itemsForProject allObjects]) {
//access some property of the current item to make sure you have the right ones -- \
description for example
NSLog(#"%#", item.description);
}

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