context detectConflictsForObject - ios

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
}

Related

NSIncrementalStore called with NSSaveChangesRequest.deletedObjects == nil

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]].

When Is obtainPermanentIDsForObjects:error: ever needed? from 2 different classes?

My understanding on the background of using this (2 different versions which varies on return values) method is: Managed-Objects created in memory by a MOC are first assigned temporary object IDs so that they may be uniquely identified without involving the persistent store. When a MOC is saved, however, the persistent store coordinator needs permanent object IDs for these Managed-objects. (correct me if I got wrong, please!)
I never used MOC's
- (BOOL)obtainPermanentIDsForObjects:(NSArray *)objects error:(NSError **)error
, but still call MOC's save: method without problems. However the code I used this week by 3rd party, uses this MOC's obtainPermanentIDsForObjects each time right after inserting a new Managed-object, is this really necessary?? Maybe needed for multithreading environment? But why Apple "CoreData PG" never mentioned this method? Has persistent-store-coordinator done it automatically for me just before saving MOC?
During research I saw another version of this method in NSIncrementalStore Class...
- (NSArray *)obtainPermanentIDsForObjects:(NSArray *)array error:(NSError **)error
, it says "This method is called (overridden in subclass then called, I guess) before executeRequest:withContext:error: with a save request, to assign permanent IDs to newly-inserted objects."
What should I do? Do I need to use both versions (NSIncrementalStore subclass's before save, and MOC's version right after new creation)? Which method is must called OR neither?
Thanks!
When an NSManagedObject is first inserted into an NSManagedObjectContext it has a temporary NSManagedObjectID. When the context is saved, the NSManagedObjectContext will do the equivalent of obtainPermanentIDsForObjects:error on NSManagedObjectContext, which in turn calls NSPersistentStoreCoordinator, which finds the store responsible for this object and calls obtainPermanentIDsForObjects:error on the store itself. This turns the NSManagedObject's temporary ID into a permanent one.
NSManagedObjectIDs can be passed between NSManagedObjectContexts (if they share the same stores), but only if the NSManagedObjectID is a permanent ID. You can call obtainPermanentIDsForObjects:error to turn a temporary ID into a permanent ID independent of a save operation, but the store that the object belongs to may not expect that.

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.

Pointers to NSManagedObject after object delete / context save?

Can someone please explain what happens to the pointers to the NSManagedObjects after the object is deleted and the context is saved? How should I set them up so that they get set to nil automatically?
Well, it's quite simple.
[managedObjectContext deleteObject:managedObject];
[managedObjectContext save:error];
managedObject = nil;
If you are afraid of memory leaks when deleting lots of objects, just use fast enumeration. This is pretty much guaranteed to clean up behind itself:
for (NSManagedObject *obj in fetchedObjects) {
[managedObjectContext deleteObject:obj];
}
[managedObjectContext save:error];
After you delete an object, the isDeleted property will be true. After saving the context, the isDeleted will be false if you still have a reference to the managed object.
You can safely make weak references to managed objects. The weak reference will nil out automatically for you under ARC when Core Data is done with them.
Here are the three relevant paragraphs from the Core Data Programming Guide:
Core Data “owns” the life-cycle of managed objects. With faulting and
undo, you cannot make the same assumptions about the life-cycle of a
managed object as you would of a standard Cocoa object—managed objects
can be instantiated, destroyed, and resurrected by the framework as it
requires.
When a managed object is created, it is initialized with the default
values given for its entity in the managed object model. In many cases
the default values set in the model may be sufficient. Sometimes,
however, you may wish to perform additional initialization—perhaps
using dynamic values (such as the current date and time) that cannot
be represented in the model.
You should typically not override dealloc to clear transient
properties and other variables. Instead, you should override
didTurnIntoFault. didTurnIntoFault is invoked automatically by Core
Data when an object is turned into a fault and immediately prior to
actual deallocation. You might turn a managed object into a fault
specifically to reduce memory overhead (see “Reducing Memory
Overhead”), so it is important to ensure that you properly perform
clean-up operations in didTurnIntoFault.
I check for (managedObject.managedContext == nil).
If it is nil then it was deleted.
Although this is not guaranteed by Apple Documentation, it seems to be working fine with me.
If you use it in different contexts or it is not saved, it will not work.
See How can I tell whether an `NSManagedObject` has been deleted? for details.

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