Duplicate NSManagedObjects in Core Data using MagicalRecord and MMRecord - afnetworking

Update 21/7/2014: I have found a workaround for the problem described here. I have created a separate NSManagedObjectContext called downloadContext. The downloaded serialized objects are put in that separate context. I then iterate through the response array and check if the object already exists in the MR_defaultContext (using the primary key).
If it does I update the properties from the downloaded object with the same primary key. It is not possible to simply copy the whole object and thus overwriting all properties of the existing object (because the updated object and the old one are in separate contexts). Instead I copy all properties of the downloaded object into an NSDictionary (dictionaryWithValuesForKeys) and then use setValuesForKeysWithDictionary to overwrite the properties of the old object. The same has to be done with the child objects.
If the object is new, I create a new object and copy the properties in the same way. I will need to check if this solution is efficient enough or if it will occupy too much memory in case there are many objects to download (first sync). The alternative is to create my own serialiser that decides on the basis of primary keys if to update an existing object or to create a new one.
For this issue, please note that I have seen the discussion on the relatedByAttribute key setting. It doesn't help in this case since this might only avoid duplicates of child relationship objects ( I haven't checked if there are duplicates). This is about the parent object duplicates.
In my project, I use AFNetworking, MMRecord and MagicalRecord together as dependencies. The situation is as follows:
I create a response serializer from the AFMMRecordResponseSerializer class which I simply set to my subclass object of the AFHTTPSessionManager. With this session manager I send a GET-request to the server. The response object already contains serialized NSManagedObjects (they are actually a subclass of MMRecord). I have set the MMRecordEntityPrimaryAttributeKey to each of the NSManagedObject's intended primary key. I think that the problem lies with MagicalRecords though, because it simply ignores MMrecord's primary attribute key setting.
After the initial GET-request with the persistent store still empty, there is no modification/creation date predicate. In case there are already persisted objects, the GET-request contains a predicate retrieving only objects after the last mod date from the server. So the updated object is persisted but the older object is not overwritten. When I display the titles of the objects in a table view I can see that the older version and updated version of an object are both displayed. Important: the table view is populated only from core data, not directly from the responseObject (containing the serialized objects).
Below is the most important code.
Thanks for any hints in the right direction
Dominik
-(void) fetchNotesFromServer
{
NSLog(#"fetchNotesFromServer called");
self.sessionManager.responseSerializer = [self getSerializerForEntityName:#"NAS_Notes" AndEndPointPathComponent:#"DBFetchNotes.php?"];
[self.sessionManager GET:[self getFetchNotesExtension] parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject)
{
if (responseObject )
{
NSLog(#"fetch all notes response: %#", responseObject);
self.latestNotes = (NSMutableArray *)responseObject;
if ([self.latestNotes count] > 0)
{
[self saveDownloadedNotesInContext:[NSManagedObjectContext MR_defaultContext]];
}
}
} failure:^(NSURLSessionDataTask *task, NSError *error)
{
NSLog(#"NSerror %#", error);
}];}
-(void) saveDownloadedNotesInContext:(NSManagedObjectContext *)context{
[context MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
if (success) {
[self.delegate didFinishNotesSync:self];
}
else if (error) {
NSLog(#"error saving to persistent store %#", error);
}
}];
}

Related

Setting CKReference is not saved

In CloudKit dashboard, record type User has a field called Boards of type Reference (List), and record type Board has a field called User of type Reference. This is a sample of code indicating how I am attempting to link a Board to a User:
CKReference *reference = [[CKReference alloc] initWithRecord:userRecord action:CKReferenceActionDeleteSelf];
[boardRecord setObject:reference forKey:#"User"];
If I NSLog(#"%#", [boardRecord objectForKey:#"User"]); before and after setting the object, I get a result of (null) before, and after it is a CKReference object with the same recordName as the User. But in CloudKit dashboard, the Board does not update its User field, and the User does not have a reference added to its Boards reference list, so when I subsequently query the User for associated Boards I do not receive any results. I cannot understand why the reference is correctly set according to what is logged, but does not actually get saved.
Here is the code used to save to iCloud:
+ (void)saveEntity:(CloudKitEntity *)entity completion:(void (^)(NSError * _Nonnull))completion {
[[self privateDatabase] saveRecord:entity.record completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) {
if (completion) {
completion(error);
}
}];
}
Logging the error revealed an error that "Server Record Changed", which I think means the server version of the record is newer than the local version. But I don't know what to do about this since I have just cleaned/rebuilt, deleted the app and rebuilt/reinstalled and received the same error.
The solution was to fetch both of the records again using their recordIDs, then set create the CKReference and set it using the re-fetched objects, and then save the re-fetched object. Why it needs to be this complicated I have no idea.

Parse.com caching with localDataStore enabled

I have caching problems in the iOS-Parse-SDK and I don't know what is wrong. I'm using the local datastore and it runs smoothly. But sometimes when I call a PFCloud function I don't get the current but an old version of an object or at least some fields of it are old (in my case it is a custom object with the field "status"). The strange thing is if I call the same function in the dev console on the Parse site I get the updated object. Is there anything I can do to prevent caching within the app or at least make sure I get the current version of an object?
Example PFCloud call:
[PFCloud callFunctionInBackground:#"importFriends"
withParameters:#{#"phoneNumbers": numbers}
block:^(NSDictionary *result, NSError *error) {
if (!error) {
DDLogDebug(#"Request Send Numbers SUCCESS: %#", result); //result objects are out of date
if (successBlock) successBlock(result);
}else{
DDLogDebug(#"Request Send Numbers FAIL: %#", error);
if (failedBlock) failedBlock(error);
}
}];
It seem like when you use the localdatastore and custom classes for any object you can't overwrite it's setters otherwise parse will never change it again. Even though the setter did exactly what a normal setter would do just a little bit more math afterwards.

iOS Core Data data insert

I have an app which asynchronously downloads a JSON file and then it should insert those objects within Core Data for persistent storage. Regarding the insert, is it a good idea to do it from the main thread? What if there are thousands of objects? Should I do the inserts on a different thread? Could you provide me with some snippets regarding this matter? Regarding the fetching of the objects after I've saved them, should I also use a different thread?
My code for inserting into Core Data is:
- (void) insertObjects:(NSArray*)objects ofEntity:(NSString *)entityName
{
NSString *key;
NSManagedObject *managedObject;
NSError *error;
for(NSDictionary *dict in objects){
managedObject = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:_managedObjectContext];
for(key in dict){
[managedObject setValue:dict[key] forKey:key];
}
}
[_managedObjectContext save:&error];
}
PS: The objects are of the same entity. The project runs on iOS 7.0 or higher.
Since I can't comment yet..
What iOS Versions do you plan to support? If 5 and higher, this might help Concurrency stack
Summary of the link:
you create a context of private concurreny type to access your physical data
based on this you create a context of main concurreny type
on top of this you use private concurrency type stores again.
Don't forget so save in every store, otherwise, the data seems to be saved while the app is running, but after restart it is lost.
And yes, you want to do it an extra thread, since it would otherwise block the UI if there are to many items.

Magical Record appears to save, but changes are lost if app is terminated

I'm using Magical Record 2.1 to handle the data persistence in my app. If I create a new entity, set some it's attributes and save, it works fine. However, later, if I fetch that entity, update it's attributes and save, subsequent fetches have the new data until I terminate the app and restart. During the new app session the old data reappears.
This is how I create a new entity:
self.localContext = [NSManagedObjectContext MR_defaultContext];
self.theNewListing = [Listing MR_createInContext:self.localContext];
I'm using MRDefaultContext having read this blog post: http://saulmora.com/2013/09/15/why-contextforcurrentthread-doesn-t-work-in-magicalrecord/
In this case my main attribute is a dictionary, and I set it like this:
NSMutableDictionary *tempDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:#"A description", #"slFieldDescription", etc, etc, nil];
self.theNewListing.dataDictionary = tempDictionary;
This is how I save it:
[self.presentingViewController dismissViewControllerAnimated:YES completion:^(void) {
[self.localContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error){
if(!success) {
NSLog(#"%#", error);
}
else {
[self.thePresentingVC refreshCollectionViews:nil];
}
}];
}];
I display my data in a collection view, and at this point everything looks fine. If I terminate and restart the data is still there.
If I fetch the entity again and update the attributes like this:
NSMutableDictionary *newTempDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:#"A new description", #"slFieldDescription", etc, etc, nil];
self.theNewListing.dataDictionary = newTempDictionary;
Then save using the same save code as above, and then update my collection view using the code below, all looks good.
self.listingsArray = [[NSMutableArray alloc] initWithArray:[Listing MR_findAllSortedBy:#"dateListed" ascending:NO]];
[self.mainCollectionView reloadData];
That is, until I quit the app and restart.
If you're wondering, I'm using FTASync, and this only supports MR 2.1, which is why I haven't upgraded to the latest version.
Thanks!
Not sure about MR, nor why you would need it. If that framework can give you the main context, just call the native Core Data save.
[context save:nil];
Cracked it!!
I noticed that my other attributes were saving, just not this one (this one holds all the data that is presented in the UI) and this led me on another line of investigation.
So, it seems that I needed to be working with immutable dictionaries to store this data as explained here:
Core Data saving problem: can't update transformable attribute (NSArray)

iOS 6 Core Data Relation Object insertObject issue

In the below example code I am passing amanagedContext object via a property of the view controller this selector lives in. In this case this property is currentPetCoreDataObject.
Does someone have a simple project, not necessarily iOS project, that has a one to many relationship in it using the Core Data framework? A simple command-line app would do. All the examples with relationships that I can find are one to one.
I don't know how to use the generated selector in the xCode Generated entity class and couldn't find any examples:
- (void)insertObject:(Feeding *)value inPetFeedRelationAtIndex:(NSUInteger)idx;
The following code appears to work but when checking the count, it doesn't appear to be saving. What am I missing?
I am new to Core Data and have not yet successfully used a relation.
if (!self.nsMutableOrderedSetFeed)
{
NSLog(#"current feed count:%d", self.currentPetCoreDataObject.PetFeedRelation.count);
[self.addedFeedObject setBrand:self.txtBrand.text];
[self.addedFeedObject setFood:self.txtType.text];
[self.addedFeedObject setParentPetRelation:self.currentPetCoreDataObject];
[self.addedFeedObject addPetFeedRelationObject:self.addedFeedObject];
//[self.currentPetCoreDataObject insertObject:self.addedFeedObject inPetFeedRelationAtIndex:[self.currentPetCoreDataObject.PetFeedRelation i];
[self.currentPetCoreDataObject setPetFeedRelation:[self.nsMutableOrderedSetFeed initWithObject:self.addedFeedObject]];
}
NSError *error;
if (![self.managedObjectContext save:&error])
NSLog(#"Failed to add new Pet profile with error: %#", [error domain]);
[self dismissViewControllerAnimated:NO completion:nil];

Resources