NSIncrementalStore called with NSSaveChangesRequest.deletedObjects == nil - ios

I am in the process of writing an NSIncrementalStore that is backed by a REST service. It works nicely for POSTing and GETing objects, but when DELETEing them I encounter the following problem:
test calls [context delete: object];
context.deletedObjects contains object (as one would expect)
test calls [context save: &error];
iOS calls NSIncrementalStores executeRequest:withContext:error: with a NSSaveChangesRequest; when the call arrives both context.deletedObjects == nil and saveChangesRequest.deletedObjects == nil, so my code cannot find out which objects to DELETE from the server.
How can it happen that deletedObjects is set to nil between the call to the context's save and its invocation its NSIncrementalStore?
UPDATE possible cause: object.objectID is still a temporaryID following save. The NSIncrementalStore is currently obtaining object IDs from newObjectIDForEntity:referenceObject: and the description from Apple's documentation somehow does not seem to apply yet:
New objects inserted into a managed object context are assigned a
temporary ID which is replaced with a permanent one once the object
gets saved to a persistent store.

The trouble was that I did not yet store enough information on the server to recreate (permanent) object IDs during fetches.
This solves the problem in NSIncrementalStore:
When executeRequest:withContext:error: receives an NSSaveChangesRequest it extracts (in my case) reference strings from saved objects with referenceObjectForObjectID: and stores these (along with objects' other attributes) on the server.
When executeRequest:withContext:error: receives an NSFetchRequest it obtains reference strings (along with objects' other attributes) from the server and recreates objects with [context objectWithID: [self newObjectIDForEntity: entity referenceObject: referenceString]].

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.

Fetch data from context that is not yet committed. CoreData

I have created multiple instances of an NSManagedObject entity(for example Car: NSManagedObject) in the default NSManagedObjectContext using MagicalRecord.
I didn't save the context. Is there a way to execute a fetch request and obtain the data that is already in persistent state and the data not yet committed that was added in default context ?
Yes, it is fetched. Please check Apple docs at https://developer.apple.com/reference/coredata/nsmanagedobjectcontext:
An object that meets the criteria specified by request (it is an instance of the entity specified by the request, and it matches the request’s predicate if there is one) and that has been inserted into a context but which is not yet saved to a persistent store, is retrieved if the fetch request is executed on that context.
not as far as I know... you can fetch it all and then look at the objects objectID to decide which one was already saved.
id all = [ctx fetch..];
id savedOnly = [NSMutableArray array];
for(id o in all) {
if([[o objectID] isTemporary] == NO) {
[savedOnly addObject:o];
}
}
OR change your code to use to contexts -- that may be better :D
OR maybe use a predicate like:
savedOnly = [ctx fetchWithPredicate:#"... self.objectID.isTemporary=NO"];
MIGHT work... don't know
If we are talking about the same managed object context - yes, you should be able to get also those objects, which are not committed. This is the default behavior. The managed object context contains also the uncommitted objects, i.e. the objects which are not saved yet in the persistent store.

context detectConflictsForObject

In iOS Core Data, there is a method on NSManagedObjectContext detectConflictsForObject:
This method's documentation says:
If on the next invocation of save: object has been modified in its persistent store, the save fails. This allows optimistic locking for unchanged objects. Conflict detection is always performed on changed or deleted objects.
I have a [context save] that throws exception regardless whether I use detectConflictsForObject or not.
I thought this method will help me to determine whether a call to [context save] will cause a crash or not.
The specific situation I have is like this.
I have contexts A is parent context of B. B is parent context of C.
Some NSManagedObject was deleted from B and after a while [C save] is about to get called. This causes "could not fulfill a fault" exception which I thought I could detect early using the method detectConflictsForObject and thus avoid crashing.
This problem is described in this document "Troubleshooting Core Data". I think that you try to retrieve an attribute or relationship from the object previously deleted in other context. To check object's existing you could use existingObjectWithID:error: method of NSManagedObjectContext. If the object cannot be fetched, or does not exist, or cannot be faulted, it returns nil. For example:
- (void)doSomethingWithAttributesOfObject:(NSManagedObject *)object {
if ([self.managedObjectContext existingObjectWithID:object.objectID error:nil])
; // you can do something with object
else
; // object was deleted
}

About the benefit of objectWithID:

The doc says:
If the object is not registered in the context, it may be fetched or
returned as a fault. This method always returns an object. The data in
the persistent store represented by objectID is assumed to exist—if it
does not, the returned object throws an exception when you access any
property (that is, when the fault is fired). The benefit of this
behavior is that it allows you to create and use faults, then create
the underlying data later or in a separate context.
I'm thinking about the last sentence:
The benefit of this behavior is that it allows you to create and use faults, then create the underlying data later or in a separate context.
Does it mean I can use objectWithID: with an arbitrary ID to get a fault handle of an non-existing object first then later create the object with ID? But how can I assign an arbitrary ID to the new object?
In general, Yes you can get a handle to a non existing item an later create that item.
But, since you don't know what ID will be assigned to the item these is not very useful in that case.
You could use obtainPermanentIDsForObjects:error: to obtain the object final ID, but, this is a trip to the store, and will have a performance penalty.
You can use objectWithID: to "warm up" the coordinator cache. in this manner you may fetch objects in the background, and use this method in another context then access these items without hitting the store (much better performance).
Since every NSManagedObjectID must initially come from a fulfilled NSManagedObject and there is no way to create one from scratch, the only possible way to "create the underlying data later" is meaningless, as follows:
NSManagedObjectID *objID = object.objectID;
[moc deleteObject:object];
…
object = [moc objectWithID:objID]; // Deleted so non-existing
[moc insertObject:object]; // Kinda of resurrecting the deleted object, but not really since the data are gone only ID is left. So it is creating a new object with the old ID. But what's the point?
// Fill data into object
…
[moc save:NULL];
If you use -objectWithID:, it will return a fault if the object is not already registered in the managed object context (ie. only if the object hasn't already been fetched and hasn't been faulted in). In the case that it does return a fault, you do not need to do anything to "create the object". Simply accessing the attributes of the object will automatically fire the fault and let you access its data. There is no additional work needed on your part to create additional objects.

iOS - Core data - NSManagedObjectContext - not sure if it is saved

Overview
I have an iOS project in which I am using Core data
I am inserting an object, then I want to save it.
I am not sure if save works.
Save seems to be working when app goes into background
When using Simulator, If I click on Stop button on Xcode, save doesn't seem to be working.
Question
Is the save actually happening ?
Am I facing a problem because I created a view based app (the core data checkbox was not available) ?
Steps Followed
I am using the simulator to test it.
Insert an object (code is in the next section)
Save the inserted object (code is in the next section)
I press the Stop button on Xcode to stop running the app
Output noticed
setBeforeSave.count = 1
setAfterSave.count = 0
Before saving, The NSManagedObjectContext method insertedObjects returns 1 object
Before saving, The NSManagedObjectContext method insertedObjects returns 0 objects
When Xcode Stop button is pressed, and when the app is relaunched, the previous data is not available (is it because I clicked on stop on xcode)
managedObjectContext is NOT nil
The NSManagedObjectContext method save: returns YES.
Code to Insert Object
Test *test = [NSEntityDescription insertNewObjectForEntityForName:#"Test" inManagedObjectContext:self.database.managedObjectContext];
Code to Save:
//database is a property of the type UIManagedDocument
NSSet *setBeforeSave = [self.database.managedObjectContext insertedObjects];
NSLog(#"setBeforeSave.count = %i", setBeforeSave.count);
NSError *error = nil;
if(![self.database.managedObjectContext save:&error])
NSLog(#"error = %#", error);
NSSet *setAfterSave = [self.database.managedObjectContext insertedObjects];
NSLog(#"setAfterSave.count = %i", setAfterSave.count);
According to the UIManagedDocument documentation, you should not call save on either of the internal managed contexts. Instead, if you want data saved, you should do one of two things.
Use the undoManager, as it will mark the context dirty, and ready to be saved.
Call [document updateChangeCount:UIDocumentChangeDone];
Thus, in your case, you should replace that save call with:
[self.database updateChangeCount:UIDocumentChangeDone];
And your data will get saved.
EDIT
To provide additional detail. A UIManagedDocument has two MOCs., in a parent/child relationship. The child is the one you get when calling document.managedObjectContext. Now, when a NSManagedObjectContext has a parent, the normal way to propagate changes to the parent is to call save:. However, the UIManagedDocuememt does other stuff, and its documentation specifically says NOT to call save on either the parent or child context.
Well, how does stuff get saved, then? Well, you tell the UIManagedDocument that it is "dirty" and needs to be saved. The two ways you can do that are by either using the undoManager, or calling updateChangeCount:.
When doing either of those, the internals of UIManagedDocument will make sure that the parent context is notified of the change. At some point in the future, the parent will effect the change to the actual backing store (i.e., file(s) on disk).
Furthermore, when a context is "saved" it may or may not keep references to the objects that were changed. You can set a property which tells it to retain objects that have been saved, or to release them.
Hopefully, that addresses your problems.
to summarize, though, see the original answer.
BTW, you can actually see a log of what the SQL store is doing underneath by adding "-com.apple.CoreData.SQLDebug 1" to your command line arguments. You do that in the "Edit Scheme" dialog.

Resources