A Second In-Memory-Store As A Cache - multithreading

Dear community.
I try to discover opportunity to using 2 persistent stores for improve performance of my application.
What i do here:
CREATE 2 PERSISTENT STORES
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:[NSNumber numberWithBool:YES]
forKey:NSMigratePersistentStoresAutomaticallyOption];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType
configuration:nil
URL:[NSURL URLWithString:#"memory://store"]
options:dict
error:&error])
{
[[NSApplication sharedApplication] presentError:error];
[persistentStoreCoordinator release], persistentStoreCoordinator = nil;
return nil;
}
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:url
options:dict
error:&error])
{
[[NSApplication sharedApplication] presentError:error];
[persistentStoreCoordinator release], persistentStoreCoordinator = nil;
return nil;
}
ASSIGN new created objects to in-Memory store
NSManagedObject *objectCarrier = [NSEntityDescription
insertNewObjectForEntityForName:#"Carrier"
inManagedObjectContext:managedObjectContext];
[objectCarrier setValue:startForCarrier
forKey:#"name"];
NSURL *url = [NSURL URLWithString:#"memory://store"];
[managedObjectContext assignObject:objectCarrier
toPersistentStore:[[appDelegate persistentStoreCoordinator] persistentStoreForURL:url]];
SAVE FINAL OBJECT
A difference between in-memory and particular persistent store using is
i have wrong using objects from predicates for same code.
If i just change persistent store type, i pickup object:
NSManagedObject *destination = [[codeAfterComparing lastObject] valueForKey:codeRelationshipName];
But set values for this object is doesn't work.
If i try to assignObject for received object, i have error (it's doesnt matter, how this object was save as inMemory or asSqlLite store object, error is start every time).
2011-02-16 14:32:45.037 snow
server[44411:1803] * Terminating app
due to uncaught exception
'NSInvalidArgumentException', reason:
'Can't reassign an object to a
different store once it has been
saved.'
Attempt to save a final object's graph with two different stores gives me error "CoreData does not support persistent cross-store relationships", and it's doesn't matter, where cureent object assing.
Migration was as :
for (NSPersistentStore *persistentStore in [persistentStoreCoordinator persistentStores]) {
if (persistentStore.type == NSInMemoryStoreType) {
// migrate the in-memory store to a SQLite store
NSError *error;
[persistentStoreCoordinator migratePersistentStore:persistentStore toURL:[NSURL fileURLWithPath:[[self applicationSupportDirectory] stringByAppendingPathComponent:#"storedata.sql"]] options:nil withType:NSSQLiteStoreType error:&error];
if (! newPersistentStore) {
Product error: "Can't add the same store twice"
So, the result is a very strange for me:
1. Looks like managed object context have no difference for objects between 2 stores. If i ask save, it take whole object and save so same sqlite store
2. maybe a way to using different persistent store coordinator's but i don't know exactly, how is easy transfer objects between 2 stores. Of course, i can do a copy (include relationships e.t.c.) but this is a hard code for this simple issue, i guess.
Maybe somebody can suggest about my code wrong or good working examples of code to review and understand a good way to do in memory cache with core data? Google search gives not too much examples.

If you look at the Core Recipe example code on Apple's website, they use multiple stores to save objects in memory and on disk.

Thought I'd take a stab here.
I've had this problem in the past. I ended up removing the functionality for two persistent stores in the same coordinator. If I understand Apple correctly, Object Entities cannot be shared between persistent stores. So to make things easier, I usually just do the following (though I suspect there is an efficiency issue with using an additional Coordinator)
1 NSPersistentStore per NSPersistentStoreCoordinator
break up the scratchpad work to the NSManagedObjectContexts
create a deep-copy method to your NSManagedObject subclasses
And then, when whatever class you have managing each persistent store utilize the copy function to import the managed objects.
I can't really think of an instance where you'd want to go through the extra trouble of individually assigning the managed objects to a specific store that wouldn't be taken car of in this way.
I have a program that utilizes two stores - one in memory for transient objects and another managing the document. It's working just fine.

In iOS 5 Apple introduce Nested Managed Object Contexts where you can work with two Managed Object contexts.
This may replace your approach with the in memory store because e.g. you can now use one of the (also) new concurrency types to run one context in the background (e.g. for background fetching) and another as your main context.
Take a look in the WWDC2011 Session 303.

Related

Core Data: NSObjectID and NSTemporaryObjectID leaks

Before I send my app to the App Store I like to check it for memory leaks and other fishy stuff with instruments. There is one Core Data issue that I can't seem to solve, so I've decided to create a small test app to illustrate the problem.
What's the problem?
When I save an entity in a (child) NSManagedObjectContext it is propagated to its parent NSManagedObjectContext. During this process Core Data creates internal instances of _NSObjectID and NSTemporaryObjectID. For some reason these instances are left behind and the only way to get rid of them is to reset the parent NSManagedObjectContext.
My app is of course a lot more complex than this little test app and resetting the NSManagedObjectContext isn't an option for me.
Test app
The test app is a standard iOS app based on the single view template with the CoreData option checked. I've used objective-c to keep it similar to my production app.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Initialize the Core Data stack
self.persistentStoreCoordinator = [self persistentStoreCoordinator];
// Create the a private context
self.rootContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.rootContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
// Create a child context
self.childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.childContext.parentContext = self.rootContext;
// Create a person
[self.childContext performBlockAndWait:^{
Person *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.childContext];
person.name = #"John Smith";
person.age = 30;
// Save the person
[self.childContext save:nil];
// Save the root context
[self.rootContext performBlockAndWait:^{
[self.rootContext save:nil];
}];
}];
return YES;
}
When you run the code above with instruments and the allocations instrument you can see that Core Data leaves some stuff behind.
You can find the full project here: https://github.com/Zyphrax/CoreDataLeak
Things I've tried
I've tried things like [context refreshObject:... mergeChanges:YES], adding #autoreleasepool and/or [context processPendingChanges] inside the blocks, it all doesn't help. The only way to get it clean is to do a [context reset] (sledgehammer approach).
It's hard to find other people reporting this problem.
This blog post seems similar:
http://finalize.com/2013/01/04/core-data-issues-with-memory-allocation/
I hope you guys can help me with this.
Here is what I see, which is very similar to yours...
However, I don't know that I would be concerned, unless you see lots of these, and they never go away. I assume the internals of Core Data (including the row cache has) some sort of object caching going on.
On the other hand, my Core Data usage has changed a bit over the past year or two.
Unless it is a very simple app, I almost never create new objects in a child context. I will fetch and modify them, but if I end up creating a new object, I make sure that is done in a sibling context.
However, if you modify your code slightly, by adding this line (with your appropriate error handling - it returns BOOL) before the initial save...
NSArray *inserted = self.childContext.insertedObjects.allObjects;
[self.childContext obtainPermanentIDsForObjects:inserted error:&error];
you should get something like this instruments report, which shows all objects created as being transient...
Thus, I don't necessarily think it is a permanent leak, because once I force the context to convert to a permanent ID, the objects go away. However, who knows how long they keep those object ID objects cached.
In general, when I create objects in a context that contains a hierarchy, I will always obtain permanent IDs first (for many reasons). However, as I said earlier, I usually create new objects in a context that is directly created to the persistent store (because I have had to deal with other issues related to hierarchies temporary object IDs, especially when using multiple non related contexts).

How can a custom NSManagedObject be created without store it in context?

How can I create a custom managed object, but without to save, just keep it in the memory and when app stops, temporary managed object can be dealloched too. But same time other managed objects I need to save.
There are a couple of possibilities depending on how your app works.
One is to just create the object and just not insert it. It's just that simple. Pass a nil value for the context.
NSManagedObjectModel *managedObjectModel =
[[self.managedObjectContext persistentStoreCoordinator] managedObjectModel];
NSEntityDescription *entity = [[managedObjectModel entitiesByName] objectForKey:#"EntityName"];
NSManagedObject *myObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
If you later want to insert the object, use [NSManagedObjectContext insertObject:].
Another is to create an in-memory Core Data store. Create a second persistent store, but replace NSSQLiteStoreType with NSInMemoryStoreType. Then create and use objects as usual. When the app exits, the in-memory store will just disappear with all of its objects.

insert and modify a record in an entity using Core Data

I tried to find the answer of my question on the internet, but I could not.
I have a simple entity in Core data that has a Value attribute (that is integer) and a Date attribute. I want to define two methods in my .m file.
First method is the ADD method. It takes two arguments: an integer value (entered by user in UI) and a date (current date by default). and then insert a record into the entity based on the arguments.
Second method is like an increment method. It uses the Date as a key to find a record and then increment the integer value of that record.
I don't know how to write these methods. (assume that we have an Array Controller for the table in the xib file)
The second method you are looking for is not appropriate to add to the entity itself. It needs to be somewhere above the entities, most likely in your controller object.
The first method is as follows:
- (void)updateDate:(NSDate*)date andValue:(NSInteger)value
{
[self setValue:date forKey:#"date"];
[self setValue:[NSNumber numberWithInteger:value] forKey:#"value"];
}
This is fairly straight KVC (Key-Value Coding) and I highly recommend that you read Apple's docs on the subject.
For your other method, that should be in a controller, you need to perform a fetch to find the record.
- (id)findRecordForDate:(NSDate*)date inManagedObjectContext:(NSManagedObjectContext*)moc
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:moc]];
[request setPredicate:[NSPredicate predicateWithFormat:#"date == %#", date]];
NSError *error = nil;
NSArray *objects = [moc executeFetchRequest:request error:&error];
NSAssert1(error == nil, #"Error fetching object: %#", [error localizedDescription]);
return [objects lastObject];
}
- (void)incrementEntityWithDate:(NSDate*)date
{
id entity = [self findRecordForDate:date inManagedObjectContext:[self managedObjectContext]];
NSInteger value = [[entity valueForKey:#"value"] integerValue];
value += 1;
[entity setValue:[NSNumber numberWithInteger:value] forKey:#"value"];
}
This is also very straightforward Core Data access. I recommend you read up on how Core Data works.
As an aside, using a date as a unique is a very bad design.
UPDATE
Marcus, Thanks for the answer. It is really helpful. I am new to core data so I have a few questions to make things clearer.
The code for the first method sets two values, but it doesn't insert the new record into the table. How can I insert the newly created record into the table?
You need to read up on how Core Data works. Apple has great documentation on how Core Data works and if that fails you can buy my book. There is a TON of information about how to use Core Data on the internet.
Where should I put the first method? in my .m file?
This is basic Objective-C. If you are asking this question you need to step way back and learn the fundamentals of the language first.
You mentioned that I need to add the second method in the controller. But the controller is defined in the xib file. How can I add the second method to that?
The controller is never defined in the xib file. It is referenced in the xib file. Again, you need to go back to the beginning of how Objective-C works and learn the basics before you dive this deep.

How can I use Core Data Objects outside of a managed object context?

I would like to use Core Data Managed Objects outside of a managed object context. I've seen other threads on this site that say you should never do this, but here's my issue:
I have a 'Feed' object and a 'story' object. Feed is like an RSS feed, and story is like a single story from that feed. I have the ability to bookmark feeds, and I use Core Data to persist those, but I when I download stories from a feed, I don't want to insert those stories into the managed object context. The only way to create my objects, however, is by doing this:
[NSEntityDescription insertNewObjectForEntityForName:name inManagedObjectContext:managedObjectContext];
Which means that it will be persisted at the next save event.
I don't want these objects to be persisted until the user selects them.
I tried defining a "TransientStory" and a "PersistentStory" with a protocol called "Story" that both of them implement, but it's a nightmare. Any ideas?
You can create these objects and just not insert them in the context:
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName
inManagedObjectContext:managedContext];
ManagedObjectClass *volatileObject = [[ManagedObjectClass alloc] initWithEntity:entity
insertIntoManagedObjectContext:nil];
And if you want to save it you just insert it to the context:
[managedContext insertObject:volatileObject];
(if you forget to add it, it will give you a dangling object error when you try to save it in the context)
Create a new NSManagedObjectContext with an in-memory store. Then you can put your transient objects into this context, and they won't be persisted.
NSManagedObjectModel *mom = [NSManagedObjectModel mergedModelFromBundles:[NSArray arrayWithObject:[NSBundle mainBundle]]]; // though you can create a model on the fly (i.e. in code)
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
NSError *err;
// add an in-memory store. At least one persistent store is required
if([psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:&err] == nil) {
NSLog(#"%#",err);
}
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setPersistentStoreCoordinator:psc];
If you did want to then persist them, just move them to the proper store afterwards, or merge the context.
Alternatively, if you're eventually going to put them into that context anyway (i.e. you just don't want them appearing in lists until they're saved), then just set setIncludesPendingChanges to NO in your NSFetchRequest.

"Core Data could not fulfill a fault.." error

I am developing an application in cocoa. I am facing a critical problem.
I am deleting entries of an object named "Directory" in Core Data using the following code:
NSEnumerator *tempDirectories = [[folderArrayController arrangedObjects] objectEnumerator];
id tempDirectory;
while (tempDirectory = [tempDirectories nextObject]){
[managedObjectContext deleteObject:tempDirectory];
}
But sometimes an exception like "Core Data could not fulfill a fault.." occurs while trying to save after deletion. I am using the code [managedObjectContext save];
I am new in Core Data... Looking forward to a solution.
This is an old question, I have struggled resolving this for some time now. So, thought it would be best to document it.
As Weichsel above mentioned, the Apple documentation rightly points out reason for this exception. But it is a hectic job to identify the module due to which the NSManagedObject subclass' object is being retained (if 1st cited reason in the documentation is the root cause of the problem).
So, I started out by identifying the parts of my code which was retaining the NSManagedObject, instead I retained the NSManagedObjectID and create the managed object out of it whenever needed. The discussion in similar lines can be found in Restkit documentation:
https://github.com/RestKit/RestKit/commit/170060549f44ee5a822ac3e93668dad3b396dc39
https://github.com/RestKit/RestKit/issues/611#issuecomment-4858605
Updated my setter and getter so that the interface with rest of the modules remain same while internally we now depend upon NSManagedObjectID and avoid retaining of NSManageObject:
-(CSTaskAbstract*)task
{
CSTaskAbstract *theTask = nil;
if (self.taskObjectID)
{
NSManagedObjectContext *moc = [(CSAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
// https://github.com/RestKit/RestKit/commit/170060549f44ee5a822ac3e93668dad3b396dc39 &
// https://github.com/RestKit/RestKit/issues/611#issuecomment-4858605
NSError *theError = nil;
NSManagedObject *theObject = [moc existingObjectWithID:self.taskObjectID
error:&theError];
if ([theObject isKindOfClass:[CSTaskAbstract class]])
{
theTask = (CSTaskAbstract*)theObject;
}
}
return theTask;
}
-(void)setTask:(CSTaskAbstract *)inTask
{
if (inTask!=self.task)
{
// Consequences of retaining a MO when it is detached from its MOC
[self setTaskObjectID:[inTask objectID]];
}
}
The above is the first half of the problem solved. We need to find out dependency in suspicious parts of our app and eliminate.
There was some other problem too, instruments -> allocations is a good source to find out which modules are actually retaining the managed objects, the exception object would have details about which managed object is creating the problem, filter results for that object as shown below:
We were performing KVO on a managed object. KVO retains the observed managed object and hence the exception is thrown and it's back trace would not be from within our project. These are very hard to debug but guess work and tracking the object's allocation and retain-release cycle will surely help. I removed the KVO observation part and it all started working.

Resources