how to loop through nsfetchedresultcontroller - iphone

in my app I need loop through all my entities in Core Data and I'm using NSFetchedresultcontroller.
I'm doing it like this at the moment:
NSArray *tempArray = [[NSArray alloc] initWithArray:self.fetchedResultsController.fetchedObjects];
for (MyClass *item in tempArray)
{
// do something
}
[tempArray release]; tempArray = nil;
Is there any better way to do it without creating the tempArray?
Thanks a lot

Depends on what you want to do. If you are just changing a value then yes, there is an easier way:
[[[self fetchedResultsController] fetchedObjects] setValue:someValue forKey:#"someKey"]
Which would loop through all of the objects setting the value. This is a standard KVC operation. Note that this will expand memory as each entity will get realized during the mutation.
If you need to do something far more involved with each entity or you are running into a memory issue then things get a bit more complicated. NOTE: Do not worry about memory until the optimization stage of coding. Pre-optimization of memory issues, especially with Core Data, is a waste of your time.
The concept is that you will loop over each entity and change it as needed. In addition, at a certain point you should save the context, reset it and then drain a local autorelease pool. This will reduce your memory usage as you will push the objects you just manipulated back out of memory before you pull the next batch in. For example:
NSManagedObjectContext *moc = ...;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSInteger drainCounter = 0;
for (id object in [[self fetchedResultsController] fetchedObjects]) {
//Do your magic here
++drainCounter;
if (drainCounter = 100) {
BOOL success = [moc save:&error];
NSError *error = nil;
NSAssert2(!success && error, #"Error saving moc: %#\n%#", [error localizedDescription], [error userInfo]);
[moc reset];
[pool drain], pool = nil;
pool = [[NSAutoreleasePool alloc] init];
drainCounter = 0;
}
}
BOOL success = [moc save:&error];
NSError *error = nil;
NSAssert2(!success && error, #"Error saving moc: %#\n%#", [error localizedDescription], [error userInfo]);
[pool drain], pool = nil;
This will keep memory usage down but it is expensive!! You are hitting disk after every 100 objects. This should only be used after you have confirmed that memory is an issue.

Sorry, I think the answer is obvious:
for (MyClass *item in self.fetchedResultsController.fetchedObjects)
{
//do something
}
Is it a good way to do it memory-wise?

Related

valueForKey on data NSManagedObject not releasing memory

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.)

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.)

iOS 5 Core Data freeze

I try to do the following simple thing:
NSArray * entities = [context executeFetchRequest:inFetchRequest error:&fetchError];
Nothing fancy. But this freezes in iOS 5, it works fine in iOS 4. I don't get exceptions, warnings or errors; my app just simply freezes.
Please help me out! I'm dying here! ;)
I don't know if you also use different Thread. If yes the issue comes from the fact that NSManagedObjects themselves are not thread-safe. Creating a ManagedContext on the main thread and using it on another thread freezes the thread.
Maybe this article can help you :
http://www.cimgf.com/2011/05/04/core-data-and-threads-without-the-headache/
Apple has a demo application for handling Coredata on several threads (usually main & background threads) : http://developer.apple.com/library/ios/#samplecode/TopSongs/Introduction/Intro.html
What I've done to solve this issue is :
In the application delegate : create the persistent store (one for all thread) and create the Coredata managed Context for the main thread,
In the background thread, create a new managed context (from same persistent store)
Notifications are used when saving, to let the mainContext know when background thread has finished (inserting rows or other).
There are several solutions, using a NSQueueOperation. For my case, I'm working with a while loop. Here is my code if it may help you. However, Apple documentation on concurrency and their Top Songs example application are good points to start.
in the application delegate :
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.cdw = [[CoreDataWrapper alloc] initWithPersistentStoreCoordinator:[self persistentStoreCoordinator] andDelegate:self];
remoteSync = [RemoteSync sharedInstance];
...
[self.window addSubview:navCtrl.view];
[viewController release];
[self.window makeKeyAndVisible];
return YES;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator == nil) {
NSURL *storeUrl = [NSURL fileURLWithPath:self.persistentStorePath];
NSLog(#"Core Data store path = \"%#\"", [storeUrl path]);
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:nil]];
NSError *error = nil;
NSPersistentStore *persistentStore = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error];
NSAssert3(persistentStore != nil, #"Unhandled error adding persistent store in %s at line %d: %#", __FUNCTION__, __LINE__, [error localizedDescription]);
}
return persistentStoreCoordinator;
}
-(NSManagedObjectContext *)managedObjectContext {
if (managedObjectContext == nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
}
return managedObjectContext;
}
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator == nil) {
NSURL *storeUrl = [NSURL fileURLWithPath:self.persistentStorePath];
NSLog(#"Core Data store path = \"%#\"", [storeUrl path]);
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:nil]];
NSError *error = nil;
NSPersistentStore *persistentStore = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error];
NSAssert3(persistentStore != nil, #"Unhandled error adding persistent store in %s at line %d: %#", __FUNCTION__, __LINE__, [error localizedDescription]);
}
return persistentStoreCoordinator;
}
-(NSManagedObjectContext *)managedObjectContext {
if (managedObjectContext == nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
}
return managedObjectContext;
}
-(NSString *)persistentStorePath {
if (persistentStorePath == nil) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths lastObject];
persistentStorePath = [[documentsDirectory stringByAppendingPathComponent:#"mgobase.sqlite"] retain];
}
return persistentStorePath;
}
-(void)importerDidSave:(NSNotification *)saveNotification {
if ([NSThread isMainThread]) {
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification];
} else {
[self performSelectorOnMainThread:#selector(importerDidSave:) withObject:saveNotification waitUntilDone:NO];
}
}
In the object running the background thread :
monitor = [[NSThread alloc] initWithTarget:self selector:#selector(keepMonitoring) object:nil];
-(void)keepMonitoring{
while(![[NSThread currentThread] isCancelled]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
AppDelegate * appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
//creating the cdw here will create also a new managedContext on this particular thread
cdwBackground = [[CoreDataWrapper alloc] initWithPersistentStoreCoordinator:appDelegate.persistentStoreCoordinator andDelegate:appDelegate];
...
}
}
Hope this help,
M.
Thanks for the hints given in this page on how to solve this freezing issue which appeared on upgrading from iOS4. It has been the most annoying problem I have found since I started programming on iOS.
I have found a quick solution for cases where there are just a few calls to the context from other threads.
I just use performSelectorOnMainThread:
[self performSelectorOnMainThread:#selector(stateChangeOnMainThread:) withObject: [NSDictionary dictionaryWithObjectsAndKeys:state, #"state", nil] waitUntilDone:YES];
To detect the places where the context is called from another thread you can put a breakpoint on the NSLog on the functions where you call the context as in the following piece of code and just use performSelectorOnMainThread on them.
if(![NSThread isMainThread]){
NSLog(#"Not the main thread...");
}
I hope that this may be helpful...
I had the same issue. If you run under the debugger and when the app "hangs" stop th app (use the "pause" button on the debugger. If you're at the executeFetchRequest line, then check the context variable. If it has a ivar _objectStoreLockCount and its greater than 1, then its waiting on a lock on the associated store.
Somewhere you're creating a race condition on your associated store.
This really sounds like trying to access a NSManagedObjectContext from a thread/queue other than the one that created it. As others suggested you need to look at your threading and make sure you are following Core Data's rules.
Executing fetch request must happen from the thread where context was created.
Remember it is not thread safe and trying to executeFetchRequest from another thread will cause unpredictable behavior.
In order to do this correctly, use
[context performBlock: ^{
NSArray * entities = [context executeFetchRequest:inFetchRequest error:&fetchError];
}];
This will executeFetchRequest in the same thread as context, which may or may not be the main thread.
In my case the app would freeze before 'executeFetchRequest' without any warning. The solution was to wrap all db operations in #synchronized(persistentStore).
Eg:
NSArray *objects;
#synchronized([self persistentStoreCoordinator]) {
objects = [moc executeFetchRequest:request error:&error];
}
Delete all object with fetchrequest doesn't work for me, the sqlite looks corrupted. the only way I found is
//Erase the persistent store from coordinator and also file manager.
NSPersistentStore *store = [self.persistentStoreCoordinator.persistentStores lastObject];
NSError *error = nil;
NSURL *storeURL = store.URL;
[self.persistentStoreCoordinator removePersistentStore:store error:&error];
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
//Make new persistent store for future saves (Taken From Above Answer)
if (![self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
// do something with the error
}

NSOperation(s) leaks only on iOS 3 device

I have some NSOperations subclasses that handle CoreData imports. I believe i've ticked most of the non-main thread issues
I create my own autorelease pool in the
main method
I create a NSManagedObjectContext for each
operation
These operations are loaded into a NSOperationQueue, with the maximum number of concurrent operations set 1.
The code works perfectly on a iOS 4.0.1, however on a iOS 3.1.3 device a get a lot of log messages like the following
*** _NSAutoreleaseNoPool(): Object 0x5f926c0 of class NSCFDictionary autoreleased with no pool in place - just leaking
NSOperation Subclass main method
-(void) main{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#try {
//Start Operation
//====================================
NSManagedObjectID *objID = nil;
NSError *err = nil;
id user = nil;
if( !(userID = [self __lookup:[self userID] inContext: [self threadContext]]) ){
//Set the name of the element
user = [[self threadContext] objectWithID:objID];
//Update the name
[user setValue:#"John Doe" forKey:#"name"];
[user setValue:#"Hello world" forKey:#"status"];
}
if( ![[self threadContext] save:&err] ){
DebugLog(#"Couldn't savechanges %#", err);
}
//====================================
//End Operation
}
#catch (NSException * e) {
DebugLog(#"Exception %#",e);
}
//====================================
[pool drain];
}
The __lookup:inContext: method
-(NSManagedObjectID*) __lookup:(id)aID inContext:(NSManagedObjectContext*) aContext{
NSPredicate *predicate = nil;
NSEntityDescription *entity;
NSFetchRequest *fetchRequest = nil;
NSError *err = nil;
predicate = [NSPredicate predicateWithFormat:#"userID == %#",aID];
entity = [NSEntityDescription entityForName:#"User" inManagedObjectContext:aContext];
fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setPredicate:predicate];
[fetchRequest setEntity:entity];
//Only fetch id's for speed
[fetchRequest setResultType:NSManagedObjectIDResultType];
return [[aContext executeFetchRequest:fetchRequest error:&err] lastObject];
}
Most of the other methods instance methods, ie threadContext look similar to the __lookup:inContext: method. I'm aware that i don't create Autorelease pools for the instance methods, but according to my understanding of how autorelease works, as long as these methods are only called inside the main method, after the NSAutoreleasePool has been created, the outer most pool should be used. I create objects such as the NSManagedObjectContext lazily, and in most cases don't use the start method
Solved it, This operation was launching a NSURLConnection using the advice in an article by Dave Dribin. However, where possible i try to never cut and paste other peoples code, so i can always filter what i am putting into my own code.
Turns out i forgot to add this check
if( ![NSThread isMainThread] ){
[self performSelectorOnMainThread:#selector(start)
withObject:nil
waitUntilDone:NO];
return;
}
Which ensures that the start method runs on the main thread, where a NSAutoreleasePool already exists. Simple mistake, easy solution.

iPad background thread NSAutoReleasePool confusion about [object release]

I have an app where I import a bunch of data and I want to to happen on a background thread. This was working well for a small amount of data but now it seems I am running into this error midway through the parsing and importing of large amounts of my data into Core Data:
Program received signal: “EXC_BAD_ACCESS”.
Here is the call to the background thread:
[self performSelectorInBackground:#selector(importAllData) withObject:nil];
Here is an example of what I am doing in my code:
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// load manufactuers from DB
NSFetchRequest *crequest = [[NSFetchRequest alloc] init];
NSEntityDescription *manufacturer = [NSEntityDescription entityForName:#"Manufacturer" inManagedObjectContext:managedObjectContext];
[crequest setEntity:manufacturer];
NSError *cerror=nil;;
NSArray *manufacturers = [[managedObjectContext executeFetchRequest:crequest error:&cerror]mutableCopy];
[crequest release];
for (int m=0; m < [manufacturers count]; m++) {
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:kClientListURL, [[manufacturers objectAtIndex:m]ManufacturerID]]];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setNumberOfTimesToRetryOnTimeout:2];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSString *responseString = [request responseString];
NSArray *items = [responseString JSONValue];
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
NSNumberFormatter *dec = [[NSNumberFormatter alloc]init];
[dec setNumberStyle:NSNumberFormatterDecimalStyle];
for (int i = 0; i < [items count]; i++)
{
Client *entity = (Client*) [NSEntityDescription insertNewObjectForEntityForName:#"Client" inManagedObjectContext:managedObjectContext];
[entity setCompanyName:[[items objectAtIndex:i] objectForKey:#"CompanyName"]];
// set a bunch of other properties
[entity setManufacturer:[manufacturers objectAtIndex:m]];
statusMessage = [NSString stringWithFormat:#"importing client: %#", entity.CompanyName];
[self performSelectorOnMainThread:#selector(setStatus) withObject:nil waitUntilDone:YES];
}
[f release];
[dec release];
} else {
NSLog(#"%#",[NSString stringWithFormat:#"JSON parsing failed: %#", [error localizedDescription]]);
}
NSError *entityerror;
if (![managedObjectContext save:&entityerror]) {
// //Handle the error.
NSLog(#"\n\n\n Error saving clients: %# \n\n\n\n",entityerror);
}
}
//More data importing code
[pool release];
As I said this was working fine for a small amount of data but now in he simulator it throws bad access errors at random points.
Then I read this article which confused me:
http://www.cocoadev.com/index.pl?DebuggingAutorelease
Do I not need to release my objects if they are inside of the NSAutoReleasePool? Am I causing them to be released twice at some point?
If I remove the release statements I get potential leaks errors when building using Command-Shift-A?
The rules are:
If you are the owner, you need to either call [object release] or [object autorelease]. The later will delegate the release call to the NSAutoReleasePool, when the pool gets flushed or released.
You are an owner if you called [object retain] or if you got the object through a method that has either alloc or copy in its name.
If you got the object from a method that has neither alloc nor copy in its name and you haven't retained it you must not call release or autorelease on it (except in the very rare case that the documentations states that you are the owner).
So yes, if you got an autoreleased object and you call release on it you are over-releasing it. The effects are various but almost always lead to a crash, normally with EXC_BAD_ACCESS. You can debug this by setting the NSZombieEnabled environment variable. That way, whenever an object is deallocated it is replaced by a dummy that knows what object lived at that address before and can then tell you which object got over-released.
But reading through your code I didn't spot any immediately obvious problem, but I don't know Core Data (which you seem to be using) well enough. Another possibility is a threading/timing issue. Also, you said that's not the whole method ? Maybe the error is in the missing part.
I am assuming that importAllData is the method you are using. If so, you are missing the : at the end in your call.
[self performSelectorInBackground:#selector(importAllData) withObject:nil];
should be
[self performSelectorInBackground:#selector(importAllData:) withObject:nil];
Hope that helps!