Core Data. ExecuteFetchRequest with fault NSManagedObjects (not in RAM) - iphone

I need to execute fetch request. But when I do it I get not fault NSManagedObjects (each of the objects is about 5 Mb, that's why I get the memory warning). Apple provides faulting possibility for Core Data (when objects are not loaded in RAM). And I wanna my objects to use this possibility.
Here is my code
+ (NSMutableSet *)GetImagesWithPredicate:(NSPredicate *)predicate
{
NSString *entityName = kEntityName;
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];;
NSManagedObjectContext *context = appDelegate.managedObjectContext;
NSEntityDescription *entityDesctiption = [NSEntityDescription
entityForName: entityName
inManagedObjectContext:context];
// find object
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesctiption];
[request setPredicate:predicate];
NSArray *objects = [context executeFetchRequest:request error:nil];
[request release];
if (objects == nil)
{
NSLog(#"there was an error");
return nil;
}
NSMutableSet *set = [NSMutableSet setWithArray:objects];
return set;
}
where predicate is (id < 500).
App crashes after
NSArray *objects = [context executeFetchRequest:request error:nil];
because all the data of objects appears in the RAM of iPhone.
It seems that default option returnsObjectsAsFaults = YES doesn't work.

The objects are probably being returned as faults; you can verify this with isFault. The issue is that Core Data automatically pre-fetches the property values for those objects and places them in the row cache (in memory). You can disable this behaviour by setting includesPropertyValues to NO on the NSFetchRequest object.
See the includesPropertyValues documentation for details of all this and the performance implications.
As an aside, you might not want to store lots of large objects in the database directly. You probably should look into using external storage if you're targeting iOS 5, or else using separate files yourself with their names/paths/ids in Core Data.

you could set the - (void)setResultType:(NSFetchRequestResultType)type for the NSFetchRequest and only get the relevant attributes for your Object with the -(void)setPropertiesToFetch:(NSArray *)values Method.
And only lazy loading the needed attributes.

Related

Loading from Core data

I am saving various entities into core data. Each time I need to access this data I am reading from Core data and assigning the required entity to a mutable array.
if (managedObjectContext == nil)
{
managedObjectContext = [(iForgetAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil)
{
// Handle the error.
NSLog(#"mutableFetchResults == nil");
}
[self setEventsArray:mutableFetchResults];
[eventsTableView reloadData];
The problem is, I am retrieving this data from many different pages in the app.
What is the best practice when using Core Data? Should I just load the data in the App Delegate and assign the arrays there?
All advice is greatly appreciated.
Your setup with mutable arrays is already bad practice. Core data can sort, filter and display, arguably much better than mutable arrays.
You should consider using a NSFetchedResultsController to display your data. You can do the filtering and sorting by using predicates and sort descriptors. Such a solution should be infinitely more robust and scalable.
As for your specific question about saving, you can use the managed object context (keep a reference in your class or get it from the app delegate) to save that data anywhere.
Using NSFetchedResultsController gives you the functionality you want. Make sure to also use the NSFetchedResultsControllerDelegate protocol. Use NSFetchRequest to do the retrieval with sorting etc.

Xcode loading core data lag

I have 3 entities saved in core data. I am loading these in several view controllers in the app - sometimes loading data from all 3. Below is how I am loading this data and assign it to an array. Once it is in the array, then I sort, filter, count or whatever I need to do depending on the current page of the app.
if (managedObjectContext == nil)
{
managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
[request setReturnsObjectsAsFaults:NO];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil)
{
// Handle the error.
NSLog(#"mutableFetchResults == nil");
}
[self setEventsArray:mutableFetchResults];
The problems I am having are:
I don't like to have lots of duplicate code - and this is appearing on every view controller where core data is needed.
From one entity, I am saving binary data of images which is causing a lag when I load that data
So, is there a way to load from core data using conditions such as eventId = [NSString stringWithFormat:#"%#", currentEventId]
OR (and probably more suitable) have a separate class that loads the data when the app starts. And then I can access the classes arrays (of the loaded data) to use for the current page. And then just reload the data if I save, edit or delete an object.
Any help is much appreciated.
Fro your first question, you should look at MagicalRecord which brings Ruby on Rails' Active Record to CoreData. it will shorten clear your core data code.
Pay attention that if your images are not small you should store them on a separate entity with a relationship to your main entity. this should help you with the lag problem since you will load the image trough the relationship only when you will explicitly ask it to. You can see here the answer of Marcus Zarra (wrote a great book on core data). There is always an option that your images are too big for core data.
Hope it helps

Core Data issue. Insert new NSManagedObject

I want to insert 200 5Mb records in my Core Database. But when I save the NSManagedObject, the memory wasn't released (autoreleased pool didn't help), and after inserting 30 records I got the memory warning and the application crashed. Here is my code
- (void)SaveItem
{
NSString *entityName = kEntityName;
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = appDelegate.managedObjectContext;
NSEntityDescription *entityDesctiption = [NSEntityDescription
entityForName: entityName
inManagedObjectContext:context];
// check if town exists
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id == %d", self.imageID];
NSFetchRequest *requestToCheckExistense = [[NSFetchRequest alloc] init];
[requestToCheckExistense setEntity:entityDesctiption];
[requestToCheckExistense setPredicate:predicate];
NSArray *objects = [context executeFetchRequest:requestToCheckExistense error:nil];
[requestToCheckExistense release];
if (objects == nil)
{
NSLog(#"there was an error");
}
NSManagedObject *object;
if ([objects count] > 0)
{
// edit item
object = [objects objectAtIndex:0];
}
else
{
// if object doesn't exist, find max id to imlement autoincrement
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesctiption];
request.propertiesToFetch = [NSArray arrayWithObjects: #"id", nil];
NSArray *allobjects = [context executeFetchRequest:request error:nil];
[request release];
NSInteger newID = 1;
if ([allobjects count] > 0)
{
NSNumber *maxID = [allobjects valueForKeyPath:#"#max.id"];
newID = [maxID intValue] + 1;
}
// write item
object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
[object setValue:[NSNumber numberWithInt:newID] forKey:#"id"];
self.imageID = newID;
}
// fill NSManagedObject
// size of objNSData is about 5MB
NSMutableData *objNSData = [[DatabaseManager sharedDatabaseManager] encryptedDataFromImage:bigImage];
[object setValue:objNSData forKey:#"big"];
[context save:nil];
}
When I commented
[object setValue:objNSData forKey:#"big"];
everything was OK.
I tried to add the code into #autoreleasepool , but that didn't help.
I know, that now, when I save data to database, it's still in iPhone RAM. How to release it from this memory? When I get a set of Managed Objects from the database, they are not in the RAM (I can easyly get 100 object, each of them has 5Mb fields)
object =(tblEntity *) [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
try to type cast the object,this may solve the problem
I've solved the issue.
after call of [self SaveItem];
I used
[context save];
[context reset];
[context save];
all the NSManagedObjects from the context will be released.
After that operation I can add as many big objects as I want
Because you don't own an NSManagedObject when you create it, it may be retained by the core data stack even after releasing it (when using an autoreleasepool contained inside the loop).
This may help:
Set the undo manager of your managedobjectContext to nil:
[context setUndoManager:nil];
Be sure that no properties of that object are retained anywhere, because then the managed object will not be released on time inside your loop.
Be sure to add an autorelease pool inside every loop execution, not wrapping all the loop itself, similar to:
for(i;i<n;i++) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[obj saveItem];
[pool drain];
}
If that object belongs to a hierarchy of NSManagedObjects, then you need to release the owner of this object too, for this one to be deallocated from memory.
You can check apple's documentation about memory management in CoreData.
Warning: big objects (> 1MB) are not recommended by Apple to be stored inside CoreData (Check this other question/answer.)

How to completely clear out a Core Data model?

How I could completely clear out EVERYTHING in a Core Data model i.e remove all objects for all entities?
I will be using the code to clear out the saved history in a the model.
Here is another way to do...
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest * allMovies = [[NSFetchRequest alloc] init];
[allMovies setEntity:[NSEntityDescription entityForName:#"Movies" inManagedObjectContext:context]];
[allMovies setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * error = nil;
NSArray * movies = [context executeFetchRequest:allMovies error:&error];
//error handling goes here
for (NSManagedObject * movie in movies) {
[context deleteObject:movie];
}
NSError *saveError = nil;
[context save:&saveError];
It will clear all the objects.
Delete the .sqlite DB Core Data creates (it's probably stored on your /Documents directory) and get your app to re-create it.
Here is a good answer to this question.
Delete/Reset all entries in Core Data?
Then just make sure to re-create the store.

Query interface for iPhone CoreData store

another iPhone newbie question...
I have the following:
NSPersistentStoreCoordinator
NSManagedObjectContext
NSManagedObjectModel
Is it possible to run queries directly on the store (since its a sqlite DB)? I'm trying to delete all the records from a tableview, and figured a "DELETE FROM table" would be nice and quick as opposed to looping through the records and removing them manually (which i'm also struggling with).
Thanks for your time,
James
Core data acts as a wrapper for the underlying data store, so it's not really a great idea to begin circumventing core data. Additionally, core data adds additional information to your DB, so directly accessing the DB may (or may in the future) cause problems.
To delete all records via core data, I have the following:
+ (void) deleteAll {
NSManagedObjectContext *managedObjectContext = [(myAppDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:[[self class] description] inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *items = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
for (NSManagedObject *managedObject in items) {
[managedObjectContext deleteObject:managedObject];
NSLog(#"%# object deleted",[[self class] description]);
}
}