Bulk update data in iOS 7 CoreData - ios

Is there way to run updating of all objects for some entity by one SQL-query?
Not to fetch and run-looping.
For example like to run
UPDATE someEntity SET filed1 = value1 WHERE field2 = value2

Core Data Batch Updates were introduced on iOS 8:
NSBatchUpdateRequest *batchRequest = [NSBatchUpdateRequest batchUpdateRequestWithEntityName: [RSSItem entityName]];
batchRequest.propertiesToUpdate = #{NSStringFromSelector(#selector(read)): #YES};
batchRequest.resultType = NSStatusOnlyResultType; // NSStatusOnlyResultType is the default
batchRequest.affectedStores = #[...]; // Optional, stores this request should be sent to
batchRequest.predicate = [NSPredicate predicateWithFormat:#"..."]; // Optional, same type of predicate you use on NSFetchRequest
NSError *requestError;
NSBatchUpdateResult *result = (NSBatchUpdateResult *)[self.managedObjectContext executeRequest:batchRequest error:&requestError];
if (result) {
// Batch update succeeded
} else {
NSLog(#"Error: %#", [requestError localizedDescription]);
}
However, it does not change the context: it changes the persistent store directly. This means that no validation is done and that you need to update your UI after.
This answer was based on this post by Matthew Morey.

Mercelo's answer will not work for iOS-7 for this you can
NSFetchRequest *fetchAllRSSItems = [NSFetchRequest fetchRequestWithEntityName:[RSSItem entityName]];
NSError *fetchError;
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchAllRSSItems error:&fetchError];
if (results == nil) {
NSLog(#"Error: %#", [fetchError localizedDescription]);
} else {
for (RSSItem *rssItem in results) {
rssItem.read = [NSNumber numberWithBool:YES];
}
[self saveManagedObjectContext];
}

Related

Update a Core Data record in iOS application

I've just read some tutorials and decide to add Core Data storage to my project. Then I implement "create" and "read" methods. It works OK.
But then I encountered a problem with "update" method.
- (void)updateForecastPlace:(NSString *)placeString
{
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:ENTITY_NAME inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
WFForecast *forecastToUpdate;
for (WFForecast *forecast in fetchedObjects)
{
if ([[forecastToUpdate timestamp] compare:[forecast timestamp]] == NSOrderedAscending)
{
forecastToUpdate = forecast;
}
}
[forecastToUpdate setPlace:placeString];
error = nil;
if ([context save:&error])
{
NSLog(#"Forecast information was updated!");
}
else
{
NSLog(#"The forecast information was not updated: %#", [error userInfo]);
}
}
I'm fetching objects from context. (It's OK)
Then choose one to update.
Setup new value to its property( [forecastToUpdate setPlace:placeString];)
Then save the context. ( [context save:&error] )
It seems like it works (it's rise no errors and send success massage to console log). But when I read this object it appears to be non-updated.
I read a lot of stuff on this problem but didn't figure out how to fix it.
Any suggestions, please?
UPDATE: I check the value of my updated object property place
[forecastToUpdate setPlace:placeString];
NSLog(#"---arg value %#", placeString);
NSLog(#"---updated value %#", [forecastToUpdate place]);
and got the output like:
---arg value Sydney, Australia
---updated value (null)
Any idea what caused such mistake?
Unfortunately the problem was in my inattentiveness :(
I forgot to assign fetched object with my objectToUpdate pointer before compare values and do other stuff.
WFForecast *lastestForecast = fetchedObjects[0]; // <- missed this row
for (WFForecast *forecast in fetchedObjects)
{
NSLog(#"%#", [forecast place]);
if ([[lastestForecast timestamp] compare:[forecast timestamp]] == NSOrderedAscending)
{
lastestForecast = forecast;
}
}

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.

How do I use the properties returned from a NSFetchRequest?

While working on an iOS app, I am having issue resolving the properties returned from a NSFetchRequest. This is not about setting the resultType or the propertiesToFetch. It is about using the NSArray of NSDictionary instances.
Here is the actual code below, the crash is near the bottom. Thank you!
BTW, the point of this code is to eventually produce a list of section headers based on hair color (that is not under hats) and then produce a list of people, without hats who have that hair color for the cells. I am not sure this is the right approach to do that, but regardless, the question stands. Thanks Again!
//
// CDHairbrained.m
//
#import "CDHairbrained.h"
#import "CDHair.h"
#import "CDPerson.h"
#implementation CDHairbrained
void defaultErrorBlock(NSError*error) {
NSLog(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
} else {
NSLog(#" %#", [error userInfo]);
}
UIAlertView* av = [[UIAlertView alloc] initWithTitle:#"Booo..." message:#"MangagedObjectContext Error" delegate:nil cancelButtonTitle:#"cry" otherButtonTitles: nil];
[av show];
}
-(void) initializeObjectContext:(NSManagedObjectContext*)moc {
//NSArray<CDHairs>
for (CDHair *hair in [self fetchAllOfEntityName:#"Hair" InManagedObjectContext:moc]) {
[moc deleteObject:hair];
}
for (CDPerson *person in [self fetchAllOfEntityName:#"Person" InManagedObjectContext:moc]) {
[moc deleteObject:person];
}
//NSDictionary{color}
NSDictionary* hairdata = #{#"red": [NSEntityDescription insertNewObjectForEntityForName:#"Hair" inManagedObjectContext:moc],
#"blond":[NSEntityDescription insertNewObjectForEntityForName:#"Hair" inManagedObjectContext:moc],
#"brown":[NSEntityDescription insertNewObjectForEntityForName:#"Hair" inManagedObjectContext:moc],
#"black":[NSEntityDescription insertNewObjectForEntityForName:#"Hair" inManagedObjectContext:moc]
};
for (NSString* color in hairdata.allKeys) {
CDHair* hair = hairdata[color];
hair.color = color;
}
//NSArray<NSDictionary{name,hair,hat}>
NSArray* peopleData = #[
#{#"name":#"Stan",#"hair":hairdata[#"red"], #"hat":#"no"},
#{#"name":#"Lucy",#"hair":hairdata[#"red"], #"hat":#"no"},
#{#"name":#"Fred",#"hair":hairdata[#"black"], #"hat":#"no"},
#{#"name":#"Sherlock",#"hair":hairdata[#"black"], #"hat":#"yes"},
#{#"name":#"Barney",#"hair":hairdata[#"blond"], #"hat":#"yes"},
#{#"name":#"Dennis",#"hair":hairdata[#"blond"], #"hat":#"yes"}
];
for (NSDictionary* personData in peopleData) {
CDPerson* person =[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:moc];
person.name = personData[#"name"];
person.hair = personData[#"hair"];
person.hat = personData[#"hat"];
}
NSError*error;
[moc save:&error];
if(error) defaultErrorBlock(error);
}
-(NSArray*) fetchAllOfEntityName:(NSString*)entityName InManagedObjectContext:(NSManagedObjectContext*) moc {
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:entityName];
NSError* error;
NSArray* fetchResults = [moc executeFetchRequest:request error:&error];
if (fetchResults) {
return fetchResults;
}
defaultErrorBlock(error);
return nil;
}
-(NSArray*) fetchDistinctProperties:(NSArray*) propertyDescriptors
forEntityName:(NSString*) entityName
Predicate:(NSPredicate*) predicate
SortedBy:(NSArray*) sortDescriptors
InManagedObjectContext:(NSManagedObjectContext*)moc
FailureBlock:(void(^)(NSError*)) failureBlock
{
// The darnedest thing: you can't query disctict against in memory changes.
// CoreData is more trouble than it is worth.
if (moc.hasChanges) {
[NSException raise:#"FetchDistinct not return in memory changes." format:#"%# has unsaved changes.",moc];
}
NSFetchRequest* fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];
fetchRequest.returnsDistinctResults = YES;
fetchRequest.propertiesToFetch = propertyDescriptors;
fetchRequest.resultType =NSDictionaryResultType;
fetchRequest.predicate=predicate;
fetchRequest.sortDescriptors = sortDescriptors;
NSError* error;
NSArray* fetchResults = [moc executeFetchRequest:fetchRequest error:&error];
if (fetchResults) {
NSLog(#"Fetched %3lu properties of %#", (unsigned long)fetchResults.count, entityName );
return fetchResults;
}
if (failureBlock)
failureBlock(error);
else
defaultErrorBlock(error);
return nil;
}
-(void) doIt:(NSManagedObjectContext*)moc {
[self initializeObjectContext:moc];
// Get a list of distinct Hair that is not underhats, to be section headers.
// And Get a list of People, with that Hair and without hats to be section cells.
//
// Expecting visibleHair to contain red, black. Not blond (none visible) Not brown, no people w/ brown hair.
// Get a distinct list of hair properties from all people without hats.
// Presume result is NSArray*<NSDictionary*{"hair":CDHair*}>
NSArray* visibleHair = [self fetchDistinctProperties:#[#"hair"]
forEntityName:#"Person"
Predicate:[NSPredicate predicateWithFormat:#"hat=='no'"]
SortedBy:nil
InManagedObjectContext:moc
FailureBlock:nil
];
// Quick Sanity Check for the debugger
NSDictionary* foundProperties = [visibleHair firstObject];
CDHair* aFoundHair = foundProperties[#"hair"];
NSLog(#"%u",aFoundHair.isFault); // <--- is nil
NSLog(#"aFoundHair: %#",aFoundHair);
NSLog(#"aFoundHair: %#",aFoundHair.color); // <------ CRASH!
// 2013-11-06 12:43:19.513 CDTest[2865:70b] -[_NSObjectID_48_0 color]: unrecognized selector sent to instance 0x8ba8670
NSLog(#"aFoundHair: %#",aFoundHair);
// Get a list of people with a particular Hair Color, who don't have hats.
NSSet* peopleWithAFoundHair = aFoundHair.people; // of CDPerson
NSArray* peopleWithAFoundHairSorted=[peopleWithAFoundHair sortedArrayUsingDescriptors:
[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES]
]; // of CDPerson
NSArray*peopleWithAFoundVisibleHairSorted = [peopleWithAFoundHairSorted filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings)
{
CDPerson*p=evaluatedObject;
return [p.hat compare:#"no"]==NSOrderedSame;
}]
]; // of CDPerson
CDPerson* aPerson=[peopleWithAFoundVisibleHairSorted firstObject];
NSLog(#"%#",aPerson);
}
#end
The NSDictionaryResultType returns an array of dictionaries with property names and values, not an array of dictionaries with entity names and values.
Thus, not:
[
{ "person" : PersonObject },
{ "person" : OtherPersonObject }
]
but rather
[
{ "name" : "John", "age" : 30 },
{ "name" : "Jane", "age" : 20 }
]
To do what you want, you need to just fetch the CDPerson entity with NSManagedObjectResultsType.
Person *person = fetchedObjects[0];
NSLog (#"%#", person.name);
Note that "Person.name" (with a capital "P") is probably wrong, as it looks like a class method rather than an instance method.
Your fetchDistinctProperties: method needs an array of NSPropertyDescriptions but you are passing an array of NSString.

How to delete a selected number of rows from CoreData using NSPredicate?

I want to delete a selected list of items from a CoreData table: A number of Persons with certain namesToDelete:
NSError* error = nil;
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Person"
inManagedObjectContext:managedObjectContext]];
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"NOT (name IN %#)", namesToDelete];
[request setPredicate:predicate];
NSArray* deleteArray = [managedObjectContext executeFetchRequest:request error:&error];
if (error == nil)
{
for (NSManagedObject* object in deleteArray)
{
[managedObjectContext deleteObject:object];
}
[managedObjectContext save:&error];
//### Error handling.
}
else
{
//### Error handling.
}
This works, but is this the easiest/shortest way to do this in CoreData?
About the best trimming is something like:
NSError* error = nil;
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:#"Person"];
[request setPredicate:[NSPredicate predicateWithFormat:#"NOT (name IN %#)", namesToDelete]];
NSArray* deleteArray = [managedObjectContext executeFetchRequest:request error:&error];
if (deleteArray != nil)
{
for (NSManagedObject* object in deleteArray)
{
[managedObjectContext deleteObject:object];
}
[managedObjectContext save:&error];
//### Error handling.
}
else
{
//### Error handling.
}
Note also that you check if the array is returned as the success criteria, not tat the error in nil. Likewise for the save: you should check the returned BOOL.
Prior to iOS 9 we delete the object one by one but on iOS 9.0+ we can delete them in a batch.
You can use NSBatchDeleteRequest available on iOS 9.0+, macOS 10.11+, tvOS 9.0+, watchOS 2.0+
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"NOT (name IN %#)", namesToDelete]];
NSFetchRequest *fetchRequest = [Person fetchRequest];
[fetchRequest setPredicate:predicate];
// Create batch delete request
NSBatchDeleteRequest *deleteReq = [[NSBatchDeleteRequest alloc] initWithFetchRequest:fetchRequest];
deleteReq.resultType = NSBatchDeleteResultTypeCount;
NSError *error = nil;
NSBatchDeleteResult *deletedResult = [appDelegate.persistentContainer.viewContext executeRequest:deleteReq error:&error];
if (error) {
NSLog(#"Unable to delete the data");
}
else {
NSLog(#"%# deleted", deleteReq.result);
}
Swift code (from the above link)
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Employee")
fetch.predicate = NSPredicate(format: "terminationDate < %#", NSDate())
let request = NSBatchDeleteRequest(fetchRequest: fetch)
do {
let result = try moc.execute(request)
} catch {
fatalError("Failed to execute request: \(error)")
}
NOTE:
I found below comment about execute of moc
Method to pass a request to the store without affecting the contents of the managed object context.
Which means any unsaved data in moc won't be affected. i.e. if you've created/updated entity that falls in the delete request criteria and don't called save on moc then that object won't be deleted.
Yes, that's it. There is no SQL-like delete capability.
This is how i use in Swift.
func deleteRecentWithId(recentID : String)
{
let fetchRequest: NSFetchRequest<Recent_Message> = Recent_Message.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "recentUserOrGroupid = %#", recentID)
let request = NSBatchDeleteRequest(fetchRequest: fetchRequest as! NSFetchRequest<NSFetchRequestResult>)
do {
try context.execute(request)
try context.save()
} catch {
print ("There was an error")
}
}
Just change your table name and predicate condition. Enjoy!!!

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