I'm using RestKit to grab objects from my RoR service and using CoreData to persist some of the objects (more static-type lookup table objects). TasteTag is one of those persisted objects:
#ifdef RESTKIT_GENERATE_SEED_DB
NSString *seedDatabaseName = nil;
NSString *databaseName = RKDefaultSeedDatabaseFileName;
#else
NSString *seedDatabaseName = RKDefaultSeedDatabaseFileName;
NSString *databaseName = #"Model.sqlite";
#endif
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:kServerURL];
manager.objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:databaseName usingSeedDatabaseName:seedDatabaseName managedObjectModel:nil delegate:self];
.. lots of fun object mapping ..
RKManagedObjectMapping* tasteTagMapping = [RKManagedObjectMapping mappingForClass:[TasteTag class]];
[tasteTagMapping mapKeyPath:#"id" toAttribute:#"tasteTagID"];
[tasteTagMapping mapKeyPath:#"name" toAttribute:#"name"];
tasteTagMapping.primaryKeyAttribute = #"tasteTagID";
[[RKObjectManager sharedManager].mappingProvider setMapping:tasteTagMapping forKeyPath:#"taste_tags"];
[[RKObjectManager sharedManager].mappingProvider addObjectMapping:tasteTagMapping];
.. some more mapping ..
I have the data coming back from the RoR server and it's getting mapped to objects as expected. The Core Data entity also seems mapped fine after RestKit gets the request back:
"<TasteTag: 0x6e87170> (entity: TasteTag; id: 0x6e85d60 <x-coredata://03E4A20A-21F2-4A2D-92B4-C4424893D559/TasteTag/p5> ; data: <fault>)"
The issue is when I try to access properties on the objects the fault can't seem to be fire. At first I was just calling the properties, which always came back as nil (even though that should fire the fault):
for (TasteTag *tag in self.vintage.tasteTags) {
[tagNames addObject:tag.name]; //get error of trying to add nil to array
}
After looking into manually triggering faults (http://www.mlsite.net/blog/?p=518) I tried calling [tag willAccessValueForKey:nil] which results in:
Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x6e7b060 <x-coredata://03E4A20A-21F2-4A2D-92B4-C4424893D559/TasteTag/p5>''
Looking up the entity in the .sqlite based on the key (TasteTag/p5) does show it mapped to the one I'd expect.
Other posts relating to RestKit recommend disabling the object cache (which I'm not using) since this is usually caused by an entity being deleted. But at this stage I'm just reading, not deleting, and I have no cache in place.
If I just call [TasteTag allObjects] I'm able to get all the objects back fine and they load without issue. It's just in the case when they are faulted it seems.
I found a solution that worked for me (I am unsure of how applicable it will be to your situation, but I'm adding it as an answer since it solved this (or very similar) issue for me):
A couple days back, I ran the RKTwitterCoreData example and noticed it worked perfectly while mine, with very simple code at this point and doing nearly the same thing, didn't. I got a lot of unfulfilled faults. So I decided to modify all of my code dealing with RestKit to reflect how the RKTwitterCoreData example does it.
I'll split this into chunks to try and help you follow my line of thinking at the time (since I don't think our problems are identical).
My Original Implementation Assumption
Since RestKit can back objects to Core Data, I assumed that those managed objects could be used interchangeably. For example, I could use the objects from Core Data in the exact same way as the ones retrieved from a remote web service. I could even merge them together to get all the data.
I Was Wrong
I noticed that RKTwitterCoreData's code did not flow this way in the least. A decent chunk of my code matched up with theirs, but the largest difference was that they didn't treat these objects as interchangeable. In fact, they never used the objects they got from remote data stores. Instead, they just let that "fall through the cracks". I can only assume that means they're added to Core Data's data store since it works for them and, now, for me.
Details
My app worked after modifying my code to utilize this flow. I can only then surmise that the unfulfillable faults we are seeing are related to using the Core Data backed objects that we get back from the web service. If instead you just ignore those and then do a fetch, you will get everything back (including the most recent request) and you shouldn't get any unfulfillable faults.
To elaborate, if you look at RKTwitterViewController you will notice that lines 45-61 handle loading of objects:
- (void)loadObjectsFromDataStore {
[_statuses release];
NSFetchRequest* request = [RKTStatus fetchRequest];
NSSortDescriptor* descriptor = [NSSortDescriptor sortDescriptorWithKey:#"createdAt" ascending:NO];
[request setSortDescriptors:[NSArray arrayWithObject:descriptor]];
_statuses = [[RKTStatus objectsWithFetchRequest:request] retain];
}
- (void)loadData {
// Load the object model via RestKit
RKObjectManager* objectManager = [RKObjectManager sharedManager];
[objectManager loadObjectsAtResourcePath:#"/status/user_timeline/RestKit" delegate:self block:^(RKObjectLoader* loader) {
// Twitter returns statuses as a naked array in JSON, so we instruct the loader
// to user the appropriate object mapping
loader.objectMapping = [objectManager.mappingProvider objectMappingForClass:[RKTStatus class]];
}];
}
Everything looks normal (at least compared to how I was doing this loading initially). But take a look at the objectLoader:didLoadObjects: delegate method:
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:#"LastUpdatedAt"];
[[NSUserDefaults standardUserDefaults] synchronize];
NSLog(#"Loaded statuses: %#", objects);
[self loadObjectsFromDataStore];
[_tableView reloadData];
}
The sample doesn't even touch the objects parameter! (Aside from the NSLog of course...)
Conclusion/tl;dr
Don't use the managed objects you get back in objectLoader:didLoadObjects: as if they are fully backed by Core Data. Instead, ignore them and re-fetch from Core Data. All objects, including the ones from the last request are there. Otherwise, you will get unfulfillable faults (at least I did).
Documenting my fix (read:hack) per Ryan's suggestion.
The error seems to be in how RestKit assumed you'll be using the objects returned from their objectLoader:didLoadObjects: method. They seem to assume it will all be Core Data backed (and follow the flow similar to what Ryan talked about - let it sync to Core Data, then re-query) or that you'll be using all non Core Data backed objects and just keep those results around.
In my case I had a mix - a root array of non Core Data backed objects which each then contained an array of Core Data backed entities. The top-level objects are ones I don't mind querying the server for and have no reason to persist locally beyond the view they're shown in. It seems once objectLoader:didLoadObjects: is complete the managed object context backing the Core Data entities within the objects param is disposed of (under the assumption you'll be re-querying for them), causing any future calls to the entities to result in being treated as faults, even though you can't trigger the fault and load the data (results in NSObjectInaccessibleException).
I got around it with an ugly hack - within objectLoader:didLoadObjects: I access one of the Core Data entity's managed object context and copy it to a property within the view (self.context = [tag managedObjectContext];). This prevents the context it being released after objectLoader:didLoadObjects: is complete, allowing me to access the entities without issue later in the view.
Another solution would be to manually re-query for each entity using a new context and copy that back to the stored return objects. One could do this when one goes to display them, or possibly some post-processing in objectLoader:didLoadObjects:, using a new context. The entity ID is still around on the faulted object so one could use that to re-query without issue even after the original RestKit context disappears. But it seems silly to have to re-query for every entity in the object graph like that.
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 am trying to get the following working.
I have a table view that is displaying data fetched from an API in a table view. For that purpose I am using a NSFetchedResultsController:
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.database.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
I create my entities in a background context like this:
NSManagedObjectContext *backgroundContext;
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.parentContext = document.managedObjectContext;
[backgroundContext performBlock:^{
[MyAPI createEntitiesInContext:backgroundContext];
NSError *error = nil;
[backgroundContext save:&error];
if (error) NSLog(#"error: %#",error.localizedDescription);
[document.managedObjectContext performBlock:^{
[document updateChangeCount:UIDocumentChangeDone];
[document.managedObjectContext save:nil];
}];
Now, whenever I get new data (and insert/update entities like shown just above), my NSFetchedResultsController doesn't quite work as it should. In particular, I am always updating one entity (not creating a new one), but my table view shows two entities. Once I restart the app it shows up correctly.
If I perform the creation of the entities ([MyAPI createEntities]) in self.database.managedObjectContext, everything works fine.
Any idea what I am doing wrong? Looking through the existing threads here on SO makes me think that I'm doing it the right way. Again, if I do not do the core data saves in the background context (but on document.managedObjectContext) then it works fine...
I read about a similar problem on the Apple dev forums today. Perhaps this is the same problem as yours, https://devforums.apple.com/message/666492#666492, in which case perhaps there is a bug (or at least someone else with the same issue to discuss it with!).
Assuming it isn't, it sounds like what you want to do should be perfectly possible with nested contexts, and therefore assuming no bugs with UIManagedDocument.
My only reservation is that I've been trying to get batch loading working with UIManagedDocument and it seems like it does not work with nested contexts (https://stackoverflow.com/q/11274412/1347502). I would think one of the main benefits of NSFetchedResultsController is it's ability to improve performance through batch loading. So if this can't be done in UIManagedDocument perhaps NSFetchedResultsController isn't ready for use with UIManagedDocument but I haven't got to the bottom of that issue yet.
That reservation aside, most of the instruction I've read or viewed about nested contexts and background work seems to be done with peer child contexts. What you have described is a parent, child, grandchild configuration. In the WWDC 2012 video "Session 214 - Core Data Best Practices" (+ 16:00 minutes) Apple recommend adding another peer context to the parent context for this scenario, e.g
backgroundContext.parentContext = document.managedObjectContext.parentContext;
The work is performed asynchronously in this context and then pushed up to the parent via a call to save on the background context. The parent would then be saved asynchronously and any peer contexts, in this case the document.managedObjectContext, would access the changes via a fetch, merge, or refresh. This is also described in the UIManagedDocument documentation:
If appropriate, you can load data from a background thread directly
to the parent context. You can get the parent context using
parentContext. Loading data to the parent context means you do not
perturb the child context’s operations. You can retrieve data loaded
in the background simply by executing a fetch.
[Edit: re-reading this it could just be recommending Jeffery's suggestion i.e. not creating any new contexts at all and just using the parent context.]
That being said the documentation also suggests that typically you do not call save on child contexts but use the UIManagedDocument's save methods. This may be an occasion when you do call save or perhaps part of the problem. Calling save on the parent context is more strongly discouraged, as mentioned by Jeffery. Another answer I've read on stack overflow recommended only using updateChangeCount to trigger UIManagedDocument saves. But I've not read any thing from Apple, so perhaps in this case a to call the UIManagedDocument saveToURL:forSaveOperation:completionHandler: method would be appropriate to get everything in sync and saved.
I guess the next obvious issue is how to notify NSFetchedResultsController that changes have occurred. I would be tempted to simplify the setup as discussed above and then subscribe to the various NSManagedObjectContextObjectsDidChangeNotification or save notifications on the different contexts and see which, if any, are called when UIMangedDocument saves, autosaves, or when background changes are saved to the parent (assuming that is allowable in this case). I assume the NSFetchedResultsController is wired to these notifications in order to keep in sync with the underlying data.
Alternatively perhaps you need to manually perform a fetch, merge, or refresh in the main context to get the changes pulled through and then somehow notify NSFetchedResultsController that it needs to refresh?
Personally I'm wondering if UIManagedDocument is ready for general consumption, there was no mention of it at WWDC this year and instead a lengthy discussion of how to build a much more complicated solution was presented: "Session 227 - Using iCloud with Core Data"
In my method where I fetch data from server, I first create the Entities and after that I call these two methods to save the changes to the document :
[self.managedObjectContext performBlock:^{
// create my entities
[self.document updateChangeCount:UIDocumentChangeDone];
[self.document savePresentedItemChangesWithCompletionHandler:^(NSError *errorOrNil) {
...
}];
}];
Because you are updating the results on a different context, I think you will need to call [self.fetchedResultsController performFetch:&error] in your view controllers -viewWillAppear: method.
After Updates
OK, you should not be calling [backgroundContext save:&error] or [document.managedObjectContext save:nil]. See: UIManagedDocument Class Reference
You should typically use the standard UIDocument methods to save the document.
If you save the child context directly, you only commit changes to the parent context and not to the document store. If you save the parent context directly, you sidestep other important operations that the document performs.
I had to use -insertedObjects and obtainPermanentIDsForObjects:error: to persist new objects created in a context.
Next, I don't think you need to create a new context to run in the background. document.managedObjectContext.parentContext should be an available background context to run updates in.
Finally, I don't call [document updateChangeCount:UIDocumentChangeDone] very often. This is taken care of by the document automatically. You can still do it any time you want, but it shouldn't be necessary.
Here is how I would call Your -createEntitiesInContext method.
[document.managedObjectContext.parentContext performBlock:^{
[MyAPI createEntitiesInContext:document.managedObjectContext.parentContext];
NSSet *objects = [document.managedObjectContext.parentContext insertedObjects];
if (objects.count > 0) {
NSError *error = nil;
[document.managedObjectContext.parentContext obtainPermanentIDsForObjects:objects error:&error]
if (error) NSLog(#"error: %#",error.localizedDescription);
}
}];
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.
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.