After I updated the Core data with the right value, i would like to add these value to the map view.
This the code that a I'd wrote for doing this it's this:
-(void)updateMapWithPredicate:(NSPredicate *)predicate
{
int numberOfElementiOnMap = 0;
dispatch_async(dispatch_get_main_queue(), ^{
[self.ActiviIndicator startAnimating];
[self.mapView removeAnnotations:self.mapView.annotations];
});
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
if (predicate) {
request.predicate = [NSCompoundPredicate andPredicateWithSubpredicates:#[[NSPredicate predicateWithFormat:#"(latitude != nil) AND (longitude != nil)"]
,predicate]];
} else {
request.predicate = [NSPredicate predicateWithFormat:#"(latitude != nil) AND (longitude != nil)"];
}
//Get all different coordinate from core data.
[request setPropertiesToFetch:#[#"latitude", #"longitude"]];
[request setReturnsDistinctResults:YES];
[request setResultType:NSDictionaryResultType];
// Execute the fetch
NSError *error = nil;
NSArray *photos = [self.context executeFetchRequest:request error:&error];
for (NSDictionary *coordinateDic in photos) {
NSPredicate *predicateToSend;
if (predicate) {
/* Get all pictures that corresponding to the coordinate inside the coordinateDic
*/
predicateToSend = [NSCompoundPredicate andPredicateWithSubpredicates:#[[NSPredicate predicateWithFormat:#"(latitude == %#) AND (longitude == %#)",
[coordinateDic valueForKey:#"latitude"], [coordinateDic valueForKey:#"longitude"]],predicate]];
} else {
predicateToSend = [NSPredicate predicateWithFormat:#"(latitude == %#) AND (longitude == %#)",[coordinateDic valueForKey:#"latitude"], [coordinateDic valueForKey:#"longitude"]];
}
dispatch_async(dispatch_get_main_queue(), ^{
//get all picture with the same lautide and longitude that are specified on predicate
NSArray *photos2 = [Photo pictureFromContext:self.context withPredicate:predicateToSend];
//if the pictures are more then one, we add only one picture, and we write the number of pictures as a subtitle of the mkannotationview
if ([photos2 count] > 1) {
Photo *photo = [photos2 lastObject];
photo.photoDescription = [NSString stringWithFormat:#"album with %lu picures",(unsigned long)[photos2 count]];
if (photo) {
// [self.mapView addAnnotation:photo];
}
} else { //add the only pictures
if ([photos2 firstObject]) {
[self.mapView addAnnotation:[photos2 firstObject]];
}
}
});
}
}
On this code i basically retrieve the value that in need from core date, and then add it to the map.
The objects that I'm adding to the map are obviously conform to the <MKAnnotation> protocol. I got the error on the [self.mapView addAnnotation:[photos2 firstObject]];line. The strange fact is that some object that i get from core date are correctly added to the map. I tried to debug the code by enabling the NSZombie but i got the same error. I also check the coordinate and everything it's all right. The only difference that i found while I'm debugging the code it's this:
The object that are added correctly to the maps looks like this in the debugger:
Instead when i get the error, the object that I'm adding to the map looks like this on the simulator:
and when i print the content of: 0 i get this:
Printing description of *([0]->[0]):
(Photo_Photo_) [0] = {}
which it's look like a pointer to an empty entity.
do you have any idea of how can i solve this?
Thanks
After submitting the bug report Apple told me the solution for my problem. The problem was that i was updating the coordinate in a non KVO compliant way. I store the properties like you, with latitude and longitude. Try adding this on your Photo class.
- (void)willChangeValueForKey:(NSString *)key
{
if ([key isEqualToString:#"latitude"] || [key isEqualToString:#"longitude"]) { [self willChangeValueForKey:#"coordinate"]; }
[super willChangeValueForKey:key];
}
- (void)didChangeValueForKey:(NSString *)key
{
if ([key isEqualToString:#"latitude"] || [key isEqualToString:#"longitude"]) { [self didChangeValueForKey:#"coordinate"]; }
[super didChangeValueForKey:key];
}
Hope this helps.
Related
I have a Chat-App with a Data-Modell like this.
User <--> Conversation <-->> Message
My Problem now: Sometimes, if I get old messages from a Backup, I have Messages twice in my DataModel. I'd like to have a NSSet-Like Class which recognizes, if a Message-Object has exactly the same values on it's properties. I've read, that I must not override the methods -hash and -isEqual:, so I don't know how to do it. Any Idea? Here is some code...
+(void)addMessages:(NSSet<JSQMessage *> *)messages toConversation:(Conversation *)conversation
{
DataManager * dataManager = [DataManager dataManager];
NSMutableSet * storeSet = [NSMutableSet setWithCapacity:messages.count];
for (JSQMessage * jsqMessage in messages) {
Message * message = [NSEntityDescription insertNewObjectForEntityForName:CDEntityNameMessage inManagedObjectContext:[dataManager managedObjectContext]];
message.senderId = jsqMessage.senderId;
message.senderDisplayName = jsqMessage.senderDisplayName;
message.text = jsqMessage.text;
message.date = jsqMessage.date;
[storeSet addObject:message];
}
[conversation addMessages:storeSet];
NSError *error;
if (![[dataManager managedObjectContext] save:&error]) {
NSLog(#"Something went wrong: %#", [error localizedDescription]);
} else {
//Saved successfull
}
}
And the Conversation -addMessages: Method is the one automatically generated from Xcode/CoreData
- (void)addMessages:(NSSet<Message *> *)values;
One way of doing it would be to add unique constraints on your entity for one or more attributes. But, this feature was added from iOS 9. Here's the link to the WWDC video explaining it:
https://developer.apple.com/videos/play/wwdc2015/220/
As a final option, you can always override hash and equal, if that suits your logic and requirements.
You hash method could look something like this:
- (NSUInteger)hash
{
NSInteger hashResult = 0;
for (NSObject *ob in self)
{
hashResult ^= [ob hash];
}
}
This is not the best implementation of a hash function. Check out this answer: https://stackoverflow.com/a/5915445/2696922
For the isEqual method, it could look something like:
- (BOOL)isEqual:(id)object
{
if (self == object)
{
return YES;
}
if (object == nil || ![object isKindOfClass:[JSQMessage class]])
{
return NO;
}
JSQMessage *jsqMessage = (JSQMessage*)object;
//You can have more parameters here based on your business logic
if (self.message != jsqMessage.message && self.date != jsqMessage.date)
{
return NO;
}
}
What I do now is checking manually, if there is a Object with same Attributes in my MOC. If there is one, I skip the creation. I know, it is a bit inefficient but with my expected number of messages, this should be no problem.
NSFetchRequest * fr = [NSFetchRequest fetchRequestWithEntityName:CDEntityNameMessage];
[fr setPredicate:[NSPredicate predicateWithFormat:#"text == %# AND date == %# AND conversation.user.objectId == %#", message.text, message.date, chatpartner.objectId]];
NSArray * results = [[self managedObjectContext] executeFetchRequest:fr error:nil];
if (results && results.count > 0) {
continue;
}
I have an entity named Geometry which is related to a Plot entity. In our piece of code, given the Plot and some downloaded data stored in a NSDictionary, we must get the Geometry and set some NSString properties but, after doing that, I find that the relationship between entities is lost.
NSError * saveError = nil;
NSFetchRequest * request = [[NSFetchRequest alloc] initWithEntityName:[Geometry entityName]];
request.predicate = [NSPredicate predicateWithFormat:#"plot == %#", plot];
NSError * error = nil;
NSArray * results = [context executeFetchRequest:request error:&error];
Geometry * __block geometry = nil;
if ([results count] > 0) {
[results enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop) {
Geometry * geometryObject = obj;
if ([geometryObject.gid isEqualToString:[NSString stringWithFormat:#"%#", [data valueForKey:#"gid"]]]) {
geometry = geometryObject;
stop = YES;
}
}];
}
if (geometry != nil) {
[geometry setPolygon:[NSString stringWithFormat:#"%#", [data valueForKey:#"polygon"]]];
}
if (![context save:&saveError]) {
NSLog(#"%#", saveError);
}
The first time I run this code results have one object, but the next time I run this there is no results.
Assume everything outside the scope of this piece of code is working right. Any hint or clue about why this happen? How can I solve this?
EDIT: The problem has been solved outside the scope of the code posted and outside the scope of this question. I should have properly reviewed the code further.
There is nothing in your code that breaks the relationship. The error must be elsewhere.
You have a Plot object, so you can get the geometries with plot.geometries without a fetch request, and filter them without a loop:
Geometry *geometry = [plot.geometries
filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:#"gid = %#", gidString]]
.firstObject
where geometries is the name of the inverse relationship for plot.
You can now set the polygon property and save. Check your setPolygon method if you are not removing the relationship.
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.
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.
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.