I got a log object retrieved by NSFetchedResultsController, it has an optional attribute that's an slide object. And it's currently faulted. So if I log log.slide, it will return a faulted object. How do I check if log actually have a slide object attached to it? I want to be able to use if(log.slide){...}
I tried to set the NSFetchRequest to [request setReturnsObjectsAsFaults:NO]; before hand it to NSFetchedResultsController. But it's still returning faulted objects. Thanks!
A fault is automatically fulfilled when you access it, so you can simply use your example if (log.slide) {...} and it will work like you expect it to, fulfilling the fault if necessary.
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.
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?
I have an app which connects to the internet and stores data in an SQL database. I tested with iOS4, it works completely as it should. When I upgrade to the new version though, I get an NSInternalInconsistencyException, with this as the reason:
'_obtainOpenChannel -- NSSQLCore 0x951a640: no database channel is available'
From what I can gather, my database is being accessed by something when it shouldn't be, though I can't understand where or why.
Can anyone help me locate and properly diagnose my problem?
I found something for this one:
I got the error (among some other ones, seemingly randomly appearing) while I was accessing a managed object's relationships in a different thread than the one the managed context was created in. There have been some changes with respect to concurrent access to managed objects in iOS5 (see here http://developer.apple.com/library/ios/#releasenotes/DataManagement/RN-CoreData/_index.html#//apple_ref/doc/uid/TP40010637) - and although the doc states the default behaviour should be as pre-iOS5 it apparently is not true, my code did work without problems in iOS4.2.
For now, my workaround was to do all the relationship-access thingies in the main thread, store the data in an array, and access the data I need in the other thread via that array. No more errors at least. This is not the 'nice' solution I suppose, as I should (and will) change the way I concurrently access managed objects, but I'm not going to change that in a hurry now.
This default concurrency type for NSManagedObjectContext is NSConfinementConcurrencyType, which means it can only be used by a single thread. From the documentation:
You promise that context will not be used by any thread other than the
one on which you created it.
You can instead create a managed object context that is backed by a private queue for multithreaded use:
[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]
To use the managed object context from a different thread, use performBlock: (asyncronous) or performBlockAndWait: (synchronous), e.g.
__block NSArray *results;
[[self managedObjectContext] performBlockAndWait:^{
results = [[self managedObjectContext] executeFetchRequest:request error:&error];
}];
// do something with results
The documentation says you don't need to use the block API from the thread that created the managed object context.
Another option is to create separate managed object contexts for each thread.
See the iOS 5 release notes for more info.
I have an NSManagedObject that has some of its properties initialized at the start of the program. When I refer to this object later, it appears to be faulted, and the properties are not accessible. I'm not sure what I need to do.
This is related to a new feature added to a program that has been operating smoothly with core-data in all other ways.
Here is a code snippet where it is initialized as a property value of a singleton. (That singleton is accessible by many parts of my code):
favoritesCollection = [[SearchTerms alloc] initWithEntity:[NSEntityDescription entityForName:#"SearchTerms" inManagedObjectContext:moc] insertIntoManagedObjectContext:moc];
favoritesCollection.keywords = #"Favorites List";
favoritesCollection.isFavoritesCollection = [NSNumber numberWithBool:YES];
favoritesCollection.dateOfSearch = [NSDate NSCExtendedDateWithNaturalLanguageString:#"4000"];
favoritesCollection.pinColorIndex = 0;
[moc save:&error];
NSLog(#"(favoritesCollection) = %#", favoritesCollection);
}
return favoritesCollection;
When I look at favoritesCollection with the NSLog, I see this (I added some newlines to make it easier to read):
(favoritesCollection) =
<SearchTerms: 0x5c28820>
(entity: SearchTerms; id: 0x5a6df90
<x-coredata://3936E19F-C0D0-4587-95B6-AA420F75BF78/SearchTerms/p33> ;
data: {
dateOfSearch = "4000-09-25 12:00:00 -0800";...*more things after this*
After the return, another NSLog shows that contents are intact.
When I refer to this instance later, I can see this in the debugger:
<SearchTerms: 0x5c28820>
(entity: SearchTerms; id: 0x5a6df90
<x-coredata://3936E19F-C0D0-4587-95B6-AA420F75BF78/SearchTerms/p33> ;
data: <fault>)
and that's all.
So I believe that the object is retained (I explicitly retain it where it is returned). I have zombies on and it doesn't look like a zombie.
I have only one managedObjectContext in the program, maintained in the singleton.
So what is happening, and how do I get to the properties that were saved?
There is nothing wrong with your object and I think you might be misinterpreting the meaning of "fault" here.
From Apple's documentation:
"Faulting is a mechanism Core Data employs to reduce your
application’s memory usage..."
Once you try and access any of the object's properties it will hit the database for all of the object's properties.
More details here http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdFaultingUniquing.html
Faults are CoreData's way of having loose links to other entities. Just access the values via properties or valueGorKey and you will see them populated just in time.
I'm a little late getting back to this, but I found out that some steps in my program were out of order. Instead of deleting the database contents (something I do at startup every time, for now) and then creating and adding this entity, I had created and added the entity and then deleted the database contents.
The pointer to the favoritesCollection entity is held for the lifetime of the program, so I would have expected it be able to see its contents any time after it was created.
From the Core Data Programming Guide
Fault handling is transparent—you do not have to execute a fetch to
realize a fault. If at some stage a persistent property of a fault
object is accessed, then Core Data automatically retrieves the data
for the object and initializes the object (see NSManagedObject Class
Reference for a list of methods that do not cause faults to fire).
This process is commonly referred to as firing the fault.
Core Data automatically fires faults when necessary (when a persistent
property of a fault is accessed).
From what I can tell by reading the programming guide, seeing faults on relationships (links to other entities) is normal when looking at any particular entity. But seeing faults on the persistent property values is not mentioned. I believe that, in general, if the object is in memory, then its properties should not be faulted, but its relationships may be faulted.
The fact that the favoritesCollection entity was fully faulted (properties and relationships) and the fault did not get resolved revealed a problem. In this case it is consistent with the entity no longer existing in the database.