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.)
Related
I want to deal with a data attribute, named originalImage in every Media entity in the store.
The problem is that despite the autoreleasing, memory builds up every time it's accessed via valueForKey, and eventually the app crashes. Or perhaps it's loading large individual NSData items which is the problem, but Instruments shows it to be a steadily inclining graph of memory usage, until it eventually gives me a memory warning and then crashes.
I haven't started on the rest of the code for this yet, so i'm not performing some hidden task that i'm not showing you.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Media"
inManagedObjectContext:[self managedObjectContext]];
[request setEntity:entity];
[request setFetchBatchSize:10];
NSArray *mediaItems = [[self managedObjectContext] executeFetchRequest:request error:nil];
for (NSManagedObject *media in mediaItems) {
#autoreleasepool {
[media valueForKey:#"originalImage"];
}
}
EDIT: Today it seems that even just mentioning the NSManagedObject media is enough to cause this media leak. So even without the valueForKey line, i have a leak. I've tried this:
while (i < count) {
#autoreleasepool {
NSManagedObject *media = [mediaItems objectAtIndex:i];
[[self managedObjectContext] refreshObject:media mergeChanges:NO];
NSLog(#"i: %i", i);
i++;
}
}
This also didn't work, and crashed at the same point.
Did you try something like:
for(int i=0;i<[mediaItems count]; i++) {
#autoreleasepool {
NSManagedObject *media = [mediaItems objectAtIndex:i];
[media valueForKey:#"originalImage"];
}
}
Maybe this way "media" gets released properly at every loop.
(Sorry, I put this on a comment. I'm new on SO and not enough rep.)
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.
I am working with several NSManagedObject types with several relationships. How can I tell Core Data to automatically populate object IDs for me? I'm looking for something like an index key in SQL, so that no two instances of a given object are allowed to have the same ID.
Edit:
I'd like for all of my "Account" objects to have unique IDs on them. I was just adding one to the `countForFetchRequest, but I realized that when deleting the second to last object and then adding one, the last two objects now have the same IDs.
How can I ensure that a given value has a unique value for all instances of my "Account" NSManagedObject?
EDIT2:
I need to have a separate ID for sorting purposes.
All NSManagedObjects automatically have a unique NSManagedObjectID. There is no notion of a custom auto-incrementing attribute, but it's certainly easy to write one yourself.
The way I resolved this is with Core Data aggregates. I actually end up assigning the ID myself.
Essentially, I query Core Data for all of the entity IDs of my entity and then iterate through them. If I find an ID which is higher than the current temporary one, I make the temporary ID higher one higher than the aggregated one. When I'm done, I automatically have an ID which is higher than the highest one in the list. The only flaw I see with this is if there is a missing ID. (I believe that there is a simple fix for this as well.)
//
// Create a new entity description
//
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:self.managedObjectContext];
//
// Set the fetch request
//
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:entity];
//
// We need to figure out how many
// existing groups there are so that
// we can set the proper ID.
//
// To do so, we use an aggregated request.
//
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:#"entityID"]];
NSError *error = nil;
NSArray *existingIDs = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error != nil) {
//
// TODO: Handle error.
//
NSLog(#"Error: %#", [error localizedDescription]);
}
NSInteger newID = 0;
for (NSDictionary *dict in existingIDs) {
NSInteger IDToCompare = [[dict valueForKey:#"entityID"] integerValue];
if (IDToCompare >= newID) {
newID = IDToCompare + 1;
}
}
//
// Create the actual entity
//
MyEntity *newEntity = [[MyEntity alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
//
// Set the ID of the new entity
//
[newEntity setEntityID:[NSNumber numberWithInteger:newID]];
//
// ... More Code ...
//
Accroding to your EDIT2 and Edit3, following answer will help you.. Assume your id field as NSNumber having unsignedInt as ID.
1) Fetch all records for corresponding entity.
NSError *error = nil;
NSArray *array = [self fetchAllFileEntity:&error];
2) Find maximum number belonging to that result.
NSNumber *maxValue = nil;
if (array)
maxValue = [array valueForKeyPath:#"#max.uniqueId.unsignedIntegerValue"];
else
maxValue = [NSNumber numberWithUnsignedInteger:0];
3) Assign maxValue+1 to your new entity
entity.uniqueId = [NSNumber numberWithUnsignedInteger:maxValue.unsignedIntegerValue+1];
I have come up with this solution for the said problem, hope it's gonna be helpful for some one.
AppDelegate *appdelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appdelegate managedObjectContext];
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *chatHist = [NSEntityDescription
entityForName:#"ChatHistory" inManagedObjectContext:context];
[fetchRequest setEntity:chatHist];
int chatIdNumber = 0;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if ([fetchedObjects count] > 0) {
ChatHistory *chatHistObj = [fetchedObjects objectAtIndex:[fetchedObjects count]-1];
chatIdNumber = [chatHistObj.chatId intValue];
}
chatIdNumber = chatIdNumber+1;
ChatHistory *chat_History = [NSEntityDescription insertNewObjectForEntityForName:#"ChatHistory" inManagedObjectContext:context];
chat_History.chatId = [NSString stringWithFormat:#"%d",chatIdNumber];
I have the following code, and I was wondering if theres any way to make this faster. Basically my app downloads some JSON (about 4000 records) from the net, and updates or creates my managed objects based on the data. At the moment it's quite slow, and I can see why, but I'm new to core data so I was wondering if there's anything I can do to make it faster?
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Company" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
NSMutableArray *coreDataArray = [[managedObjectContext executeFetchRequest:request error:nil] mutableCopy];
[request release];
for (NSDictionary *dict in arr) {
NSArray *filtered = [coreDataArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(code == %#)", [dict objectForKey:#"Code"]]];
//NSLog(#"COREDATA ARRAY: %d FILTERED ARRAY: %d CODE: %# COREDATA FIRST CODE: %#", [coreDataArray count], [filtered count], [dict objectForKey:#"Code"], [[coreDataArray objectAtIndex:0] code]);
if ([filtered count] > 0) {
Company *c = [filtered objectAtIndex:0];
if ([dict objectForKey:#"Defunct"]) {
NSLog(#"DELETED DEFUNCT COMPANY");
[managedObjectContext deleteObject:c];
} else {
[c populateWithJSONDictionary:dict];
}
} else {
Company *c = (Company *)[NSEntityDescription insertNewObjectForEntityForName:#"Company" inManagedObjectContext:managedObjectContext];
[c populateWithJSONDictionary:dict];
}
float percent = (float)[arr indexOfObject:dict]/[arr count];
[self performSelectorInBackground:#selector(updateProgressView:) withObject:[NSString stringWithFormat:#"%f",percent]];
}
[coreDataArray release];
Many thanks for any help you can give.
You should check out the Core Data Programming Guide: Performance section
It has some specific advice for data import performance.
In case Apple moves the documentation again, here is a good search query on Google site:developer.apple.com core data import performance
I have a problem that whenever I'm inserting data using coredata, everything's going fine. But while retrieving, I'm getting the same object all the time retrieved. I'm inserting objects of actors with multiple attribues like id,name,address etc. in add method, I can see everything getting inserted(which actually I'm retrieving from an xml file). my set methods are like:=
[poi setActorCity:[NSString stringWithFormat:#"%#",[poi1 objectAtIndex:j]]];
where, poi is an object of my managedObjectClass POI1 . Are those a problem? & j index is simply for keeping track of xml values from poi1 array. Please help...
(void)addEvent
{
[actorsArray removeAllObjects];
NSEntityDescription *entity1 = [NSEntityDescription entityForName:#"POI1" inManagedObjectContext:self.managedObjectContext];
POI1 *poi = (POI1 *)[NSEntityDescription insertNewObjectForEntityForName:#"POI1" inManagedObjectContext:managedObjectContext];
for(NSInteger i=0;i<[Actors count];i++)
{
NSMutableArray *poi1=[[NSMutableArray alloc]init];
poi1=[Actors objectAtIndex:i];
for(int j=0;j<[poi1 count];j++)
{
if(j==1)
{
[poi setActorName:[NSString stringWithFormat:#"%#",[poi1 objectAtIndex:j]]];
} //Like this it inserts for every attribute
}
[actorsArray insertObject:poi atIndex:i];
[poi release];
}
[self saveAction]; //saving the managedObjectContext
}
This' my fetch method...
-(void)fetchResult
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity1 = [NSEntityDescription entityForName:#"POI1" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity1];
NSArray *items = [self.managedObjectContext
executeFetchRequest:fetchRequest error:&error];
for(NSInteger k=0;k<[items count];k++)
{
POI1 *_poi=[[POI1 alloc]init];
_poi = [items objectAtIndex:k];
NSString *str=[NSString stringWithFormat:#"%#",[_poi actorName]]; //This' for testing... Shows me same name every time..,
}
[fetchRequest release];
}
It sounds like you have a problem with your fetch. Check your predicate. If it returns the same object the most likely cause is that your predicate is written such that it only finds that one object.
Edit01:
This line is your problem:
POI1 *poi = (POI1 *)[NSEntityDescription insertNewObjectForEntityForName:#"POI1" inManagedObjectContext:managedObjectContext];
Despite the fact that the class called is 'NSEntityDescription' this method returns a managed object instance. Right now you create a single POI1instance and then just keep assigning it different attributes. You're seeing the same values because you've only created, populated and saved one object.
Move the object creation inside the loop:
for(NSInteger i=0;i<[Actors count];i++)
{
POI1 *poi = (POI1 *)[NSEntityDescription insertNewObjectForEntityForName:#"POI1" inManagedObjectContext:managedObjectContext];
NSMutableArray *poi1=[[NSMutableArray alloc]init];
poi1=[Actors objectAtIndex:i];
for(int j=0;j<[poi1 count];j++)
{
if(j==1)
{
[poi setActorName:[NSString stringWithFormat:#"%#",[poi1 objectAtIndex:j]]];
} //Like this it inserts for every attribute
}
[actorsArray insertObject:poi atIndex:i];
[poi release];
}
This will create a new POI1 at each pass so that the each element of the Actors array will have a corresponding POI1 instances containing its data.
A little hard to answer precisely with this information, but my educated guess would be that you do not create new poi instance every time and keep adding the same reference to your array.
Make sure that you're saving your managedObjectContext after doing all of those inserts(probably after each insert), otherwise the information will never leave temporary memory.