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.
Related
I'm new to core data and had a query.
If I call executeFetchRequest:error: to retrieve an entity from the context, and store this entity in a variable called A, and I repeat the process and store it the next time in a variable called B, will A and B refer to the same instance of the NSManagedObject i.e. will a change made to object A also be made to object B?
In addition, assuming I proceed to delete the entity from the managed object context, what would happen to these references?
Take a look at the Core Data Programming Guide section on Faulting and Uniquing. To quote:
Uniquing Ensures a Single Managed Object per Record per Context
Core Data ensures that—in a given managed object context—an entry in a persistent store is associated with only one managed object. The technique is known as uniquing. Without uniquing, you might end up with a context maintaining more than one object to represent a given record.
So, provided you execute the fetches on the same context, the returned results will point to the same instances.
When you delete an object, it is flagged for deletion until the next save operation, at which point it is deleted from the store. If you retain a reference to it thereafter, CoreData will throw an error if you try to access it. From the same document, in the section on creating and deleting objects:
You can find out if a managed object has been marked for deletion by sending it an isDeleted message. If the return value is YES, this means that the object will be deleted during the next save operation, or put another way, that the object is marked deleted for the current (pending) transaction.
the NSManagedObject subclasses are created and handled by the NSManagedContext by itself. It means you might possibly get the same instance of the NSManagedObject when you fetch them more time (but you don't know for sure). If you want to compare 2 objects, you should use some of your "isEqualTo:" implementations.
When you delete the object in that context, the contents of your properties will be removed. That means they will be set to nil if they are weak or they will point to deallocated memory if they are string. So it might happen that you app crashes when you try to access them via your properties(I've experienced that :)).
Every time you fetch an object you are obtaining different instances. To check if represents the same object you should compare objectID properties (to check if they are pointing to the same record in the persistent store).
If you delete one from context, you can check if the other is in context using existingObjectWithId:error
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]].
Say I have a CoreData entity type called Player and it has a to-one relationship (purpose) with an entity type called PlayerPurpose. For completeness, say we have an inverse relationship in PlayerPurpose called parentPlayer. Consider the following swift code:
// Assume we already have a player object in a NSManagedObjectContext called context:
player.purpose = NSEntityDescription.insertNewObjectForEntityForName("PlayerPurpose",
inManagedObjectContext: context) as PlayerPurpose;
// Later in the code, we set the value to nil (or we could have replaced
// it with another call to insertNewObjectForEntityForName)
player.purpose = nil;
// What happens to the previous playerPurpose object within the Managed Object Context?
My question: what happens to the original playerPurpose object within the Managed Object Context when the only reference it has in the data is set to nil (or replaced with another object)?
This is not really related to relationship deletion rules because I'm not explicitly deleting any object -- I'm removing it from any meaningful relationships, making it an orphan.
From an ARC perspective (if the PlayerPurpose was just a normal, non-managed object), the original PlayerPurpose instance now has no references, so it can be cleared from memory -- but what happens in the Managed Object Context? Does CoreData recognize this as an orphaned object and delete it via the context?
If not, then I assume I have to be careful to delete any managed object created via a context if I'm going to get rid of all references to it. Assuming that's the case, is there a good pattern go use for making sure that orphaned objects get cleared from the NSManagedObjectContext and that they are no longer stored in the persistent store?
Thanks!
Core Data does not automatically delete objects in this scenario, because "orphaned" is a concept that your code has but not one that Core Data recognizes. There's no reason for it to delete a PlayerPurpose object just because one of its relationships is nil.
The most reliable way to ensure that PlayerPurpose instances are deleted would be to
Create custom NSManagedObject subclasses for your entities (if you don't have them already).
Override the setter method for purpose on the Player subclass. If the new value is nil, delete the old one.
You can also handle this by just making sure to call deleteObject: at the appropriate times. Or you could run a clean-up step where you fetch every PlayerPurpose with a nil value for parentPlayer and delete them.
I have a Core Data stack based on the NSInMemoryStoreType store. And I've noticed that deleting objects doesn't really remove them or make them nil, bur rather simply turns them into faults.
For example, (MyManagedObjectEntityClass as well as the <> identifier are placeholders):
MyManagedObjectEntityClass *o = [NSEntityDescription insertNewObjectForEntityForName:#"<MyManagedObjectEntityClass Entity Name>" inManagedObjectContext:self.localContext];
NSLog(#"\n%#", o);
[self.localContext deleteObject:o];
NSLog(#"\n%#", o);
Will log that the object is still there only that it's data is a fault.
And adding [self.localContext save:nil]; after the delete doesn't change this either.
I was hoping I could at some point test the o variable for nil, in which case I'd reload the object - but it seems I can't.
Just in case, yes, I know I could instead test o for -isFault. But thing is, extrapolate this test to an NSSet and I can't just rely on [[set anyObject] isFault] to conclude that all objects in that set have been removed (Ideally the set's count would be 0, but all objects are still there as faults).
So I'm wondering if it's possible at all or what alternate approach could I take to be able to test that objects have been deleted in a way transparent to the fact that they are managed objects.
This is not actually a Core Data issue. C (and by extension Objective-C) doesn't work like that.
The deleteObject: method takes one argument, a pointer to an object. It can change the object (like setting its isDeleted flag), or it can do other things related to the object (like deleting it from its managed object context). It cannot change the value of the pointer itself. So whatever it does or should do, C says that once it's done, the pointer that you pass in still points to the same location in memory. As a result it's actually impossible for that method to force that pointer to be nil in this language. If you want it to be nil, you have to change that yourself. (As an aside, it would have been possible to implement the method to take a pointer to pointer argument, which could modify your pointer. This would have no effect on other references such as those in arrays, though, so it would be kind of pointless).
This is why the isDeleted method is public, so that if you have a pointer to this object in some other location, you can check whether it has been deleted before attempting to use it.
If that's not convenient enough (and it often isn't), Core Data also provides NSManagedObjectContextObjectsDidChangeNotification and NSManagedObjectContextDidSaveNotification. You can use these anywhere in your app to get notified of changes to the context and respond in whatever way is appropriate (updating an array, for example). These notifications both try to help you out by providing lists of inserted, updated, and deleted objects. Use those when possible to check whether you actually need to update your references.
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.