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

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.

Related

What is the best practice to pass object references between two queues with CoreData?

I am facing a decision to be made for an applications architecture design.
The Application uses CoreData to persist user information, the same information is also stored on a remote server accessible by a REST-Interface. When the Application starts I provide the cached information from CoreData to be displayed, while I fetch updates from the server. The fetched information is persisted automatically as well.
All of these tasks are performed in background queues as to not block the main thread. I am keeping a strong reference to my persistenContainer and my NSManagedObject called User.
#property (nonatomic, retain, readwrite) User *fetchedLoggedInUser;
As I said the User is populated performing a fetch request via
[_coreDataManager.persistentContainer performBackgroundTask:^(NSManagedObjectContext * _Nonnull context) {
(...)
NSArray <User*>*fetchedUsers = [context executeFetchRequest:fetchLocalUserRequest error:&fetchError];
(...)
self.fetchedLoggedInUser = fetchedUsers.firstObject;
//load updates from server
Api.update(){
//update the self.fetchedLoggedInUser properties with data from the server
(...)
//persist the updated data
if (context.hasChanges) {
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
NSError *saveError = nil;
BOOL saveSucceeded = [context save:&saveError];
if (saveSucceeded) {
//notify the App about the updates
//**here is the problem**
}
};
}];
So the obvious thing about this is, that after performing the backgroundTask, my self.fetchedLoggedInUser is not in memory anymore, because of its weak reference to the NSManagedObjectContext provided by the performBackgroundTask() of my PersistentContainer.
Therefore, if I try to access the information from another Model, the values are nil.
What would be the best practice to keep the fetched ManagedObject in Memory and not have to fetch it again, every time I want to access its values?
A) In the Documentation, Apple suggests using the objectID of an ManagedObject, to pass objects between queues
Passing References Between Queues
NSManagedObject instances are not intended to be passed between
queues. Doing so can result in corruption of the data and termination
of the application. When it is necessary to hand off a managed object
reference from one queue to another, it must be done through
NSManagedObjectID instances.
You retrieve the managed object ID of a managed object by calling the
objectID method on the NSManagedObject instance.
The perfectly working code for that situation would be to replace the if(saveSucceeded) Check with this Code:
if (saveSucceeded) {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error = nil;
self.fetchedLoggedInUser = [self.coreDataManager.persistentContainer.viewContext existingObjectWithID:_fetchedLoggedInUser.objectID error:&error];
(...)
//notify the App about the updates
});
}
But I think this may not be the best solution here, as this needs to access the mainContext (in this case the persistentContainer's viewContext) on the mainQueue. This is likely contradictory to what I am trying to do here (performing on the background, to achieve best performance).
My other options (well, these, that I came up with) would be
B) to either store the user information in a Singleton and update it every time the information is fetched from and saved to CoreData. In this scenario I wouldn't need to worry about keeping the NSManagedObject context alive. I could perform any updates on a private background context provided by my persistentContainer's performBackgroundTask and whenever I'd need to persist new / edited user information I could refetch the NSManagedObject from the database, set the properties, save my context and then update the Singleton. I don't know if this is elegant though.
C) edit the getter Method of my self.fetchedLoggedInUser to contain a fetch request and fetch the needed information (this is probably the worst, because of the overhead when accessing the database) and I am not even sure if this would work at all.
I hope that one of these solutions is actually best practice, but I'd like to hear your suggestions why/how or why/how not to handle the passing of the information.
TL:DR; Whats the best practice to keep user information available throughout the whole app, when loading and storing new information is mostly done from backgroundQueues?
PS: I don't want to fetch the information every time I need to access it in one of my ViewControllers, I want to store the data on a central knot, so that it is accessible from every ViewController with ease. Currently the self.fetchedLoggedInUser is a property of a singleton used throughout the application. I find that this saves a lot of redundant code, using the Singleton makes loading and storing the information clearer and reduces the access count to the database. If this is considered bad practice I'd be happy to discuss about that with you.
Use a NSFetchedResultsController - they are very efficient and you can use them even for one object. A FetchedResultsController does a fetch once and then monitors core data for changes. When it changes you have a callback that it has changed. It also works perfectly for ANY core-data setup. So long as the changes are propagated to the main context (either with newBackgroundContext or performBackgroundTask or child contexts or whatever) the fetchedResultsController will update. So you are free to change your core-data stack without changes your monitoring code.
In general I don't like keeping pointers to ManagedObjects. If the entry is deleted from database then the managedObject will crash when you try to access it. A fetchedResultsController is always safe to read fetchedObjects as it tracks deletions for you.
Obviously attach the NSFetchedResultsController to the viewContext and only read it from the main thread.
I came up with a very elegant solution in my opinion.
From the beginning I was using a Singleton called sharedCoreDataManager, I added a property backgroundContext that is initialized like so
self.backgroundContext = _persistentContainer.newBackgroundContext;
_backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_backgroundContext.retainsRegisteredObjects = YES;
and is retained by sharedCoreDataManager. I am using this context to perform any tasks. Through calling _backgroundContext.retainsRegisteredObjects my NSManagedObject is retained by the backgroundContext, which is itself (like I said) retained by my Singleton sharedCoreDataManager.
I think this is an elegant solution as I can access the ManagedObject threadsafe from the background anytime. I also won't need any extra class that holds the user information on top. And on top of that I can easily edit the user information at anytime and then call save() on my backgroundContext if needed.
Maybe I am going to add it as a child to my viewContext in the future, I'll evaluate the performance and eventually update this answer.
You are still welcome to propose a better solution or discuss this topic.

Orphaned objects in iOS CoreData

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.

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.

Is it safe to pass NSManagedObjectID-s between multiple NSPersistentStoreCoordinator-s?

I have multiple NSPersistentStoreCoordinator instances that have just one NSPersistentStore each, all pointing to the same sqlite file. And in my testing passing NSManagedObjectID objects between them, and then calling
- (NSManagedObject *)existingObjectWithID:(NSManagedObjectID
*)objectID error:(NSError **)error
on NSManagedObjectContext seems to work fine, but I have yet to come across any documentation from apple that confirms that this is supposed to work fine.
So, my question is, if anyone knows if this is supposed to work (a link to some apple documentation confirming it would be awesome!), of if anybody knows that this is not usually supposed to work, and that I should not be doing this, that would be awesome too!
It's always safe to call that method. Whether it returns an object is another story.
When using an object ID from a different context, you'll only get a non-nil result if the object is already accessible to the context you call this method on. That implies that the object already exists in the persistent store, meaning it has already been saved on its original managed object context.
Or in other words, you'll only get a non-nil result if you've already saved the object with the ID, and if you're using the object's permanent (post-save) ID.

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.

Resources