I have a core data object RootObject.
This object has an array/relationship of other objects, ChildrenObject[].
When executing NSFetchRequest to get all my RootObjects I get an array with all of them.
The way CoreData works is that it only loads properties on demand, meaning only when I access my ChildrenObject property that property would load from the file.
Something like:
RootObject.childrenObjects.firstObject.someChildrenObjectProperty
CoreData will load someChildrenObjectProperty in memory only on first access of that property.
Is there a way to force CoreData to load it earlier, somewhere during the NSFetchRequest execution?
One option is to iterate through all of the fetched RootObject items and just access the properties but I am wondering is there some better way to do this on a CoreData level (some setup flag, fetch flag).
See NSFetchRequest.returnsObjectsAsFaults and NSFetchRequest.setRelationshipKeyPathsForPrefetching
Related
Some background:
I am trying to store a large amount of data in local DB and I want to do it as efficiently as possible.
Scenario:
There are many entities which are inter-related, such as address is associated with a contact like this:
address <<-> contact
To manage relationships, I have written a method in each subclass of NSManagedObject, below is some code snippet:
// class Contact
- (void)manageRelationships
{
#autoreleasepool {
LocalDBManager *localDBManager = [[LocalDBManager alloc] init];
// managing relationships
// map associated addresses
NSPredicate *addressIdPredicate = [NSPredicate predicateWithFormat:#"%K == %#",ADDRESSID,self.addressid];
// below method returns an object as fault by firing a fetch request against context
NSSet *retrievedAddresses = [localDBManager retrieveManagedObjectsForEntity:ADDR_ENTITY withPredicate:addressIdPredicate asFault:YES withPropertyValues:NO error:nil];
self.addresses = retrievedAddresses;
// managing few more relationships
}
}
Point to consider:
Since there can be multiple relationships for an object, I know that memory consumption will increase when I will be mapping relationships.
Question:
I want to turn back an object into fault, without loosing any changes made, once the relationships are mapped.
From apple documentation and some googling I came to know that I can use refreshObject:mergeChanges: method. So I am planning to add below line at end of code block in manageRelationships method:
[[self managedObjectContext] refreshObject:self mergeChanges:YES];
I am a bit confused and want to know that -
Does it mean that whatever changes were made to the object will be
stored in persistent store and then the object will turn to a fault?
If yes, then can I consider it equivalent to save method of
NSManagedObjectContext
Please suggest.
First of all you do not need to manage relationships by yourself. Let CoreData handle that.
Core Data automatically resolves (fires) the fault when you access data in the fault. This lazy loading of
the related objects is much better for memory use, and much faster for fetching objects related to rarely used
(or very large) objects.
CoreData performance
You might do this by simple instantiating one-to-many relationship between contact-address.
Regarding to refreshObject:mergeChanges: you are wrong. It is not an equivalent to save: method. If you set merge changes to YES it only means that:
If flag is YES, then object’s property values are reloaded from the values from the store or the last cached state then any changes that were made (in the local context) are re-applied over those (now newly updated) values.
Cocoa touch Doc
So if you did some changes in managed object A and then did [context refreshObject:A mergeChanges:YES] than object A would still remain in an unsaved state.
Prerequisites:
I have 2 methods in the network API:
return list of entities (just basic info: name, id, etc.)
return detailed info about entity
The requirement is to save only objects created by processing the second request (save to sqlite) and leave non-full objects without saving.
Also, the 'main' context should contain only full objects from 1st request, and any other 'temporary' context should contain all the others objects.
I've tried to create two instances of NSPersistentStoreCoordinator and use them for different types of contexts, but it seems that for one NSManagedObjectModel can exist only one coordinator (the pointer points to the same adress).
If I understand you correctly, then I think your best option is to only create a managed object once you're sure you want it to persist in Core Data storage. You may need another layer of non-managed objects to contain data for the "non-full" entities
(This would be something like Data Transfer Objects from Java EE programming.)
You can not save indiscriminately from within the same MOC. Saving the MOC always saves everything in it.
So, you can use a separate MOC that is never saved, and then just "move" those objects to the main MOC when they are ready to be saved.
The solution that exactly solves my problem is to create two 'forks' of core data stack:
one with default config and mainContext
the second (new instance of the same NSManagedObjectModel, new
NSPersistentStore (inMemory) and new NSPersistenStoreCoordinator )
I have an app set up much like the iCloudCoreDataRecipes sample (ie, using Core Data in conjunction with iCloud). In the app delegate, I observe the
NSPersistentStoreDidImportUbiquitousContentChangesNotification
When a notification arrives, I call
[context mergeChangesFromContextDidSaveNotification:note];
I have some additional processing I'd like to do when this notification is received but am having trouble using the objects identified by the NSManagedObjectID's present in the NSDeletedObjectsKey set.
NSSet *deletedObjects = [info objectForKey:NSDeletedObjectsKey];
for (NSManagedObjectID *oid in deletedObjects) {
NSManagedObject *obj = [context objectWithID:oid];
}
If I access any property on the obj, it is nil.
I then tried running the above code prior to calling mergeChangesFromContextDidSaveNotification:
When I did that, I was able, most of the time, to access the properties on the object. In some cases, I'd get an exception for unable to fulfill fault; the record was already deleted from the Core Data store.
I realized that accessing the deleted object's properties would work if the object had been loaded into the context some time prior to the notification arriving (ie, if the object was viewed/accessed within the app).
My problem is that I'd like to do some clean-up related to the deleted objects; my NSManagedObject's have a property that I'd like to read and then use to perform some work outside of Core Data related to that value.
What am I missing? Is it possible to do this?
You should probably look at
- (void)prepareForDeletion;
and override that in your NSManagedObject subclass.
Is it possible to find out which properties were saved on a managed object after the save occurs? For example, I have someone listening for managed object context saves, (NSManagedObjectContextDidSaveNotification) and I want to know which properties on the objects were saved.
The NSManagedObjectContextDidSaveNotification does contain all three bits of information you would need to sync with a server. Check the [notification userInfo] and you will find three sets inside: NSInsertedObjectsKey, NSUpdatedObjectsKey, and NSDeletedObjectsKey
If you want to know what properties on an entity have changed that would require that you track them yourself using KVO. I would recommend against this as the odds of that level of effort being worth it over just pushing the entire object up to a server are slim.
Update #2
On further poking around:
From the NSManagedObjectContextWillSaveNotification you could loop through each set and reference the changedValues method. You could keep a reference to that dictionary until after you receive the NSManagedObjectContextDidSaveNotification and then process the changes. Still sounds very heavy to me.
Update
What is your end goal?!?!
If you are trying to figure out what to push to a server then being at the attribute level is too low. You should be syncing at the entity level.
If you are just trying to keep some internal consistency inside of your application then you are thinking way, way too low level. This is a solved problem. Core Data solved it.
Why don't you get them when they are about to be saved. Subscribe to NSManagedObjectContextWillSaveNotification and check insertedObjects, updatedObjects and deletedObjects of the NSManagedObjectContext.
Update:
Even easier, get the user info of the NSManagedObjectContextDidSaveNotification
From the documentation:
Typically, on thread A you register for the managed object context
save notification, NSManagedObjectContextDidSaveNotification. When you
receive the notification, its user info dictionary contains arrays
with the managed objects that were inserted, deleted, and updated on
thread B.
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/coredata/Articles/cdConcurrency.html#//apple_ref/doc/uid/TP40003385-SW1
Here's the solution I settled with. I have one singleton class that is notified when a context saves. The NSManagedObjectContextWillSave notification tells me which things have changed so I store them in a dictionary with the key being the context that saved. Then when I get the NSManagedObjectContextDidSave notification, I check the dictionary for the associated context. Finally, I remove that entry from the dictionary. Does that seem reasonable?
Bit of Background info, I'm using An NSOutlineView with Core Data.
This Is What I'm trying to do:
Retrieve All The objects from the Outline Views Tree Controller.
Retrieve the 'name' Property (Core Data) from each of these objects as a String.
Finally store the Strings of the 'name' Property from all the Objects in an NSArray.
For extra help, here is a Picture of my Core Data Model, http://snapplr.com/xqxv
Is this possible?
You don't need to go to the treeController to get your objects, you can query your ManagedObjectContext directly.
You essentially create and execute a fetch request, which returns an NSArray. You set the predicate for the fetch in this process as well, so if you are using it to try and filter your data this is useful as well.
Here is the example from the Apple Documentation on Fetching Managed Objects.