Extract specific update from NSPersistentStoreCoordinator - ios

I'm currently using iCloud and CoreData to sync data across my app, so every time notification fires, I update my local array of data. The problem I am running into is that my data set is getting large and I don't want to update the entire set of data every time there is a new notification.
Basically, I have an Entity called Photo, and every time the user makes an update to one Photo object on device A, it gets synced with iCloud, which then gets pushed to device B. The device receives the notification through:
persistentStoreDidImportUbiquitousContentChanges:
which looks like this:
notification.userInfo.description:
{
deleted = "{(\n)}";
inserted = "{(\n 0x17045c80 56E70CB19352/Photo/p8431>\n)}";
updated = "{(\n)}";
}
I'd like to grab that specific insertion, update, or deletion and apply it to my local array instead of iterating through the entire set of fetchedObjects.
I tried casting the insertion object to a Photo object, but that didn't work. Any thoughts on how to extract that info?
Thanks!

The objects in that notification are instances of NSManagedObjectID. You can use those with your NSManagedObjectContext to retrieve the managed objects. Use existingObjectWithID:error: (safe, potentially slow) or objectWithID: (fast, potentially less safe).
You probably don't need to do that, though. You can take that notification and pass it to mergeChangesFromContextDidSaveNotification: to merge any changes it contains. If you need to do manual merging, you can, but it's usually not needed.

Related

Why does storing a reference to an NSManagedObject prevent it from updating?

This question is poorly phased but this can be better explained in code.
We have a Core Data Stack with private and main contexts as defined by Marcus Zarra here: http://martiancraft.com/blog/2015/03/core-data-stack/
We call a separate class to do a fetch request (main context) and return an array of NSManagedObjects:
NSArray *ourManagedObjects = [[Client sharedClient].coreDataManager fetchArrayForClass:[OurObject class] sortKey:#"name" ascending:YES];
We then do some processing and store a reference:
self.ourObjects = processedManagedObjects
Our view contains a UITableView and this data is used to populate it and that works just fine.
We change the data on our CMS, pull to refresh on the UITableView to trigger a sync (private context) and then call this same function to retrieve the updated data. However, the fetch request returns the exact same data as before even though when I check the sqlite db directly it contains the new data. To get the new values to display I have to reload the app.
I have discovered that if I don't assign the processedManagedObjects to self, the fetch request does indeed return the correct data, so it looks like holding a reference to the NSManagedObject stops it from getting new data from the main context. However I have no idea why that would be.
To clarify, we're pretty sure there's nothing wrong with our Core Data Stack, even when these managed objects are not being updated, other are being updated just fine, it's only this one where we store a local reference.
It sounds like what's going on is:
Managed objects don't automatically update themselves to reflect the latest data in the persistent store when changes are made via a different managed object context.
As a result, if you keep a reference to the objects, they keep whatever data they already had.
On the other hand if you don't keep a reference but instead re-fetch them, you get the new data because there was no managed object hanging around with its old data.
You have a few options:
You could keep the reference and have your context refresh the managed objects, using either the refresh(_, mergeChanges:) method or refreshAllObjects().
If it makes sense for your app, use an NSFetchedResultsController and use its delegate methods to be notified of changes.
Don't keep the reference.
The first is probably best-- refreshAllObjects() is probably what you want. Other options might be better based on other details of your app.
Try setting the shouldRefreshRefetchedObjects property of the fetch request to true. According to the documentation:
By default when you fetch objects, they maintain their current property values, even if the values in the persistent store have changed. Invoking this method with the parameter true means that when the fetch is executed, the property values of fetched objects are updated with the current values in the persistent store.

Creating a copy of a PFObject

I am in a situation where I allow the user to download a PFObject and modify it locally, and they can then either cancel the changes or hit Done, which will dismiss the editing interface but NOT upload the changes to Parse yet. They need to hit Save on the previous screen to write all changes to the database at once.
The problem is once the PFObject is modified, you cannot revert it to its prior state without refetching from the database. But I cannot always refetch the data from the database every time they hit Cancel because the prior state may not be uploaded to Parse yet (and that's a bad UX making them wait to discard changes that are only stored locally).
For example, imagine the user taps to edit the PFObject, they make changes then hit Done, then tap on it again and further edit the object, then hit Cancel. In this case, the object needs to be reverted to its prior state, but that state has not been uploaded to Parse yet. So I cannot refetch the data from the database to revert changes otherwise it would overwrite the changes they made the first time.
To solve this problem, I would simply fetch the PFObject and store a copy of it. I'd call that the transient object. I would have another property that stores the real object. The user would modify the transient object, and when they hit Cancel I would simply set that to nil, if they instead hit Done I would set the real object equal to the transient object, and once they finally hit Save I would save the real object to the database. That way I can be sure changes aren't being made to the real object until the user commits the changes. The problem is, PFObject does not adopt the NSCopying protocol (not sure why), therefore I cannot create a copy of the PFObject. Any change I make to it affects the real object.
How can this be resolved, without modifying the app's design that allows control over when the data is committed and later saved? Is there a way to extend PFObject and adopt NSCopying, has it been done before?
I did consider storing the attributes of the object in a dictionary and allow the user to edit that instead, then upon commit set each of those attributes on the PFObject. The problem with this solution arises with complex structures. In this app, I allow the user to modify multiple arrays that contain multiple PFObjects. It's just infeasible to try to recreate and later merge changes with complex structures like this beyond a single simple PFObject.
I ran into this same problem. I did not make any changes directly to the PFObject, but rather, saved the updates in an NSDictionary. When the user clicks the done button, I then update the PFObject and saveInBackground. I don't think there is a "discard local changes" option for PFObject. If you don't do this, the only option is to throw out the existing PFObject and fetch again.
Regarding the NSDictionary comment, perhaps NSArray would be better. The implementation really depends on your specific program, but I'll give a quick example. The NSArray we'll call instructionArray. Imagine there are 3 sections in a tableView. Also assume that the data source for each section is an NSArray of PFObjects. Now say you want to set the age property of each PFObject in Section 2 to 35.
Add an NSArray object (corresponding to an instruction to carry out) to the instructionArray. This instruction to carry out could have the form
Section to update
Property to update
Value to update to
So the object you'll add is #[#(2),#"age",#(35)];
Given that the user is probably carrying out a finite amount of instructions, it might not be that performance heavy to loop through the instructionArray in cellForRowAtIndexPath so when a cell uses its corresponding PFObject to figure out what to display, it can loop through the instructions after and change what is displayed as if the PFObject was updated.
When the save button is touched, loop through the instructions and actually edit the PFObjects themselves.
If you need the instructions to handle specific objects rather than sections, then you just have to update the structure of the instructionArray. Maybe you could include an identifier to indicate what type of instruction it is.

Parse.com - Find deleted objects

I have an iOS app that synchronises to Parse.com.
It can find anything that was added to Parse and add it to Core Data using PFQuery. It can also check for any data that has been updated and update accordingly.
However, I'm not sure how to find objects that have been deleted on Parse.com.
Does anyone know of a query that will list the ObjectIDs that have been deleted and the date of their deletion? I can then remove them from the Core Data on the app.
I needed this function, too, but figured that marking rows as deleted will bloat the data and add a condition to every query. So I created a Deletion class. It records only the class name and ID of any deleted row, so it stays pretty small:
function recordDeletion(klass, identifier) {
var Deletion = Parse.Object.extend("Deletion");
var deletion = new Deletion();
deletion.set("klass", klass);
deletion.set("identifier", identifier);
return deletion.save();
}
// for every class that you want deletions recorded, add one of these...
Parse.Cloud.beforeDelete("MyClass", function(request, response) {
recordDeletion("MyClass", request.object.id).then(function() {response.success();});
});
My iOS clients record the date when they last fetched data, then get everything newly created/updated from MyClass (+ others) and Deletion. With that, the can delete the Deletions locally.
Over a longer period, the clients remove all of the locally cached data and get a fresh copy of everything (except Deletions). This allows me to have a scheduled job on the server that will empty the Deletion table (on a cycle that's much longer than the client's cycle).
There is no provided API for this.
As per the comment from #Fogmeister you can tag objects as deleted and update like that. Alternatively you can maintain a specific list of deleted ids (potentially using Parse.Cloud.beforeDelete) and then make a specific request to get only the deletions.
In either case you will need to explicitly manage the scheme you choose and also decide how and when to clean up the deleted objects / deletion records.

Remove all references to ManagedObjects belonging to a ManagedObjectContext

I'm looking to integrate iCloud with a Core-Data-managed SQLite database (only on iOS 7 and later). I've been reading Apple's guide on using Core Data with iCloud (https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/UsingCoreDataWithiCloudPG.pdf).
To quote from the guide, "Core Data posts an NSPersistentStoreCoordinatorStoresWillChangeNotification notification. In your notification handler, you reset your managed object context and drop any references to existing managed objects."
Calling -reset on the MOC to reset it isn't the problem, the problem is the part where they say all references to managed objects need to be dropped. I understand why this needs to be done (because the persistent store is changing), what I don't know is how to do it.
All my Core Data work is handled by a singleton and I had originally thought of posting a notification, and listening classes could set all their managed objects to nil. First, this doesn't sound like a particularly good way of doing it. Secondly, I have a FetchedResultsController managing a tableView, the FetchedResultsController manages it's own managed objects, therefore, as far as I know, I can't set them to nil.
I'd be really grateful for any advice on what to do here.
Thanks in advance.
The way I handle situations like this is to post two notifications in my app: just before resetting, and just after resetting.
For example, I might post MYMainContextWillResetNotification, then reset the context, then post MYMainContextDidResetNotification.
Any controller receiving the will-reset notification should release its managed objects, but also store any information it will need to recover after the reset. Usually this will be one or more NSManagedObjectID objects. In some cases, you may not need to store anything, simply performing a fetch after the reset instead.
A typical method might look like this:
- (void)mainContextWillReset:(NSNotification *)notif
{
self->noteID = note.objectID;
}
This code supposes there is a controller for a single note object. When the reset is about to take place, the note's object identifier is stored in an instance variable.
The did-reset notification method retrieves the note.
- (void)mainContextDidReset:(NSNotification *)notif
{
note = [context existingObjectWithID:noteID error:NULL];
[self refreshViews];
}
This code uses existingObjectWithID:error:, but you could equally do a fetch.
With an NSFetchedResultsController, you would need to call performFetch: in the did-reset method, to refresh the objects.

NSFetchedResultsChangeInsert gets called twice for 1 insert with different object IDs

I'm losing my mind around this question.
So I have a Core Data setup in my iOS app done this way:
http://www.cocoanetics.com/2012/07/multi-context-coredata/
I then insert an object by creating a temporary MOC (as explained in the blog post) and perform saves on all 3 contexts in performBlock: methods.
In a view controller I have an NSFetchedResultsController and it gets notified that I did indeed insert a new object. The problem is that the NSFetchedResultsChangeInsert is fired twice and each time the object that is passed trough has a different objectID (it also is a different object instance in memory). What happens is that I then have 2 rows inserted in my table view but un the SQL database there is only one new. It then of course crashes when I scroll to the bottom of the table view.
If I also perform some updates on the object I get NSFetchedResultsChangeUpdate called only once and with the objectID that was passed in the second NSFetchedResultsChangeInsert call.
The first ID looks like this:
<x-coredata:///ReceivedMessage/t605BB9A7-A04E-4B89-B568-65B12E8C259A2>
The second (and all consequent ones) like this:
<x-coredata://02A917C5-850F-4C67-B8E4-1C5790CF3919/ReceivedMessage/p28>
What could this be? Am I missing out something obvious?
PS: I also checked if the notification comes from the same context, thread, etc. It does.
The two IDs you are seeing may very well represent one object. The difference between them is just that the first one is a temporary object ID, assigned to the object on creation, and the second one is the permanent object ID, assigned to the object when it gets stored to the managed object store (see NSManagedObjectID's isTemporaryID).
To work around this issue you could call NSManagedObjectContext's obtainPermanentIDsForObjects:error: just before you save the temporary MOC. This way the inserted object will have just one ID during the save propagation and the NSFetchedResultsControllerDelegate methods should get called just once.

Resources