How to update all records as quickly as possible? - ios

I need to update all the records with the same value. And I don't want to do it with iteration over objects since that takes about 2 seconds for over 2000 objects. Basically I have all users locally and I receive the users id's from the server. I now want to set property 'requested' to false except for the users return from the server-
How it is done now (which is totally wrong!):
Iterating thru all users (about 2000 of them) and correcting their requested value.
NSFetchRequest * allUsers = [[NSFetchRequest alloc] init];
[allUsers setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:context]];
NSArray *users = [context executeFetchRequest:allUsers error:&error];
NSArray *userIDs = [JSON objectForKey:#"users_ids"];
for (User *user in users) {
if ([userIDs containsObject:user.userID]) {
user.requested = [NSNumber numberWithBool:YES];
} else {
user.requested = [NSNumber numberWithBool:NO];
}
user.requested = [NSNumber numberWithBool:NO];
}
[context save:nil];
My idea how to do it:
Set requested=false for every object with a sql-like one statement. Going thru id's returned by the server (20 on average) and setting requested=true for those.
// UPDATE users SET requested = false WHERE 1=1
NSArray *userIDs = [JSON objectForKey:#"users_ids"];
for (int i = 0; i < [userIDs count]; i++) {
User *user = [User userWithServerID:[userIDs[i] integerValue] usingManagedObjectContext:context];
user.requested = [NSNumber numberWithBool:YES];
}
[context save:nil];
So how could I update all the records with one "query" ? Or does anyone have any better idea how to implement this (more performance efficient way of doing the iteration perhaps) ?
Putting this in the background thread is a solution (perhaps), but I would still like to optimise it first before putting it in the background thread.
Thank you!

You would still have to fetch all the objects but you can do it all in one line.
[[fetchedResultsController fetchedObjects] makeObjectsPerformSelector:#selector(setYourValue:) withObject:[NSNumber numberWithBool:YES]];
Or just cycle the array
NSArray *objects = [context executeFetchRequest:allRequest error:&error];
[objects setValue:[NSNumber numberWithBool:YES] forKey:#"user_ids"];
//now save your changes back.
[context save:&error];

Related

Core Data executeFetchRequest: not returning saved data

I have this method getLevelInformation that sends a fetch request to my Core Data store to acquire information about this int called _bestMoveCount based on two parameters, level and type of level (level being any int from 0-99, and type of level from 1-4). If the predicate hasn't been located, a new object is created for specific level and best move (and type of level if it hasn't been created already). Otherwise, I retrieve the best movement NSNumber and eventually convert it to my NSInteger instance var _bestMoveCount.
My problem: it appears that _levelInformation (by the time it checks to see if there is anything contained in the specific fetch) is always empty when I replay one of my levels (which it shouldn't, because I save the newly created data). When I go back to play the level again, and put a breakpoint at the if statement, I take a look at _levelInformation after the executeFetchRequest: method. Here's what I see:
<Score: 0x1cd68a40> (entity: Score; id: 0x1cd68470 <x-coredata:///Score/t510F3599-89E9-4A57-B9BC-34DE0131E2142> ; data: {
bestMovement = 0;
bestTime = 0;
level = 0;
overallScore = 0;
typeOfLevel = 0;
})
At this point, if I clicked on level 2, bestMovement should be something like 18, level should be 1, typeOfLevel should be 2 (I'm playing my second type). Any thoughts as to why this isn't saving my data?
- (void)getLevelInformation {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
_managedObjectContext = [(AppDelegate*)[[UIApplication sharedApplication]
delegate] managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Score" inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:entityDescription];
NSNumber* levelNumber = [NSNumber numberWithInteger:_currentLevel];
NSNumber* typeNumber = [NSNumber numberWithInteger:_level];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K == %# AND %K == %#",
#"level", levelNumber, #"typeOfLevel", typeNumber];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
_levelInformation = [[_managedObjectContext
executeFetchRequest:fetchRequest error:&error] lastObject];
// the only way this fails is if the level the player is on has never been accessed
// OR RATHER, if the player never actually finished the level (thus never getting
// a best move count, best time, and overall score.
if (!_levelInformation) {
_levelInformation = [NSEntityDescription insertNewObjectForEntityForName:#"Score"
inManagedObjectContext:_managedObjectContext];
[_levelInformation setValue:levelNumber forKey:#"level"];
if (![_levelInformation valueForKey:#"typeOfLevel"]) {
[_levelInformation setValue:typeNumber forKey:#"typeOfLevel"];
}
[_levelInformation setValue:[NSNumber numberWithInteger:0] forKey:#"bestMovement"];
if (![_levelInformation.managedObjectContext save:&error]) {
NSLog(#"Unable to save managed object context.");
NSLog(#"%#, %#", error, error.localizedDescription);
}
}
_levelInformation.bestMovement = [_levelInformation valueForKey:#"bestMovement"];
_bestMoveCount = [_levelInformation.bestMovement integerValue];
}
Forgot to mention. After the level is completed I save the new data again.
- (void)saveTheBestMove {
[_levelInformation setValue:[NSNumber numberWithInteger:_bestMoveCount] forKey:#"bestMovement"];
NSError *error;
[_managedObjectContext save:&error];
}

Handle duplicates when inserting objects to core data entity

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.

IOS Core Data - Find or Create duplicates inserts

I'm missing something in my logic when trying to sync web service data with local store and I need your help. This is what I've got:
I have one NSArray of NSDictionaries describing each event object (downloaded from web), which I sort by event id. Then I fetch local store using IN predicate and also sort it by event id. Then I try to iterate and match the ids and if they match, i update record and if they don't match i create new NSManagedObject. It works fine if the newly downloaded event object has a greater eventID than last eventID in local store, but if the eventID from web service is smaller than the one in local store then it INSERTS ALL OBJECTS, no matter if they exist or not and that exactly is my problem.
So in other words, if a new record is at the beginning of sorted array it will add every object, but if it is at the end of sorted array it will update all except the new one. I need it to create the new one and update old ones.
Here's some code:
The function with the logic where I believe I'm missing something:
- (void)findOrCreateObject:(NSArray*)eventArray
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
//get sorted stored records
NSArray *fetchedRecords = [self.fetchedResultsController fetchedObjects];
//sort dictionaries
NSSortDescriptor *aSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"id" ascending:YES];
NSArray *downloadedRecords = [self.events sortedArrayUsingDescriptors:[NSArray arrayWithObject:aSortDescriptor]];
NSLog(#"DOWNLOADED EVENTS = %#", downloadedRecords);
NSLog(#"FETCHED EVENTS = %#", fetchedRecords);
//if store is not empty we need to walk through data and add/update records, otherwise/ELSE we need to import initial data
if (fetchedRecords.count != 0) {
//stores has records already
NSLog(#"FIND OR CREATE PROCESS");
if ([downloadedRecords count] > 0) {
NSArray *storedRecords = [self fetchEvents:eventArray withContext:context];
NSUInteger currentIndex = 0;
for (NSDictionary* event in downloadedRecords) {
Event* eventObject = nil;
if ([storedRecords count] > currentIndex) {
eventObject = [storedRecords objectAtIndex:currentIndex];
}
NSLog(#"STRING VALUE OF KEY = %#", [[eventObject valueForKey:#"eventID"]stringValue]);
if ([[event valueForKey:#"id"] isEqualToString:[[eventObject valueForKey:#"eventID"] stringValue]]) {
//Update Record
NSLog(#"Updating Record!!!");
[self updateManagedObject:eventObject withRecord:event inContext:context];
}
else
{
//New Record
NSLog(#"Inserting Record!!!");
eventObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
eventObject.eventID = [self makeNumberFromString:[event valueForKey:#"id"]];
eventObject.title = [event valueForKey:#"title"];
eventObject.venue = [event valueForKey:#"venue"];
}
currentIndex++;
}
}
}
else
{
//import initial data
NSLog(#"IMPORTING INITIAL DATA");
for (NSDictionary* event in downloadedRecords) {
Event *eventObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
eventObject.eventID = [self makeNumberFromString:[event valueForKey:#"id"]];
eventObject.title = [event valueForKey:#"title"];
eventObject.venue = [event valueForKey:#"venue"];
}
}
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
The FETCHEVENTS method:
-(NSArray*)fetchEvents:(NSArray*)eIDs withContext:(NSManagedObjectContext*)context
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(eventID IN %#)", eIDs];
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:#[ [[NSSortDescriptor alloc] initWithKey: #"eventID" ascending:YES] ]];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"No rows returned");
}
return fetchedObjects;
}
The Update Object method:
- (void)updateManagedObject:(NSManagedObject*)object withRecord:(NSDictionary*)record inContext:(NSManagedObjectContext*)context
{
[object setValue:[self makeNumberFromString:[record valueForKey:#"id"]] forKey:#"eventID"];
[object setValue:[record valueForKey:#"title"] forKey:#"title"];
[object setValue:[record valueForKey:#"venue"] forKey:#"venue"];
}
I'm calling findOrCreate method once I download the web service data and parse it.
Let me know if you have any other questions.
Try this,
- (void)findOrCreateObject:(NSArray*)eventArray {
//if store is not empty we need to walk through data and add/update records, otherwise/ELSE we need to import initial data
if (fetchedRecords.count != 0) {
//stores has records already
NSLog(#"FIND OR CREATE PROCESS");
if ([downloadedRecords count] > 0) {
NSArray *storedRecords = [self fetchEvents:eventArray withContext:context];
for (NSDictionary* event in downloadedRecords) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"eventID = %#",[event valueForKey:#"id"]];
NSArray *matchedArray = [storedRecords filteredArrayUsing
Predicate:predicate];
Event* eventObject = nil;
if ([matchedArray count] > 0) {
//Update Record
NSLog(#"Updating Record!!!");
eventObject = [matchedArray objectAtIndex:0];
[self updateManagedObject:eventObject withRecord:event inContext:context];
}
else
{
//New Record
NSLog(#"Inserting Record!!!");
eventObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
eventObject.eventID = [self makeNumberFromString:[event valueForKey:#"id"]];
eventObject.title = [event valueForKey:#"title"];
eventObject.venue = [event valueForKey:#"venue"];
}
}
}
} else {
.....
}
}
I think, every time you insert a new event object, you should update storedObjects such that it should now contain the inserted object.
Or more simply, you should put the initialisation line of storedObjects inside your for loop. (This would make sure that as you enumerate from the beginning of downloadedObjects every eventObject will have the same index on it as on storedObjects. But, with regards to your code this will only be true if all elements of storedObjects will always be found in downloadedObjects which, I assume is the case.)
One thing though, isn't fetchedRecords supposed to be the same as storedObjects, if they are you should just reassign storedObjects as [self.fetchedResultsController fetchedObjects], as it would reflect the changes in your context without executing another fetch request which would solve the inefficiency of the suggestion above.

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

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