i need some information about memory management with NSManagedObjectContext objects.
I`m programming on an ipad-App and i work with core data objects.
My UIApplicationDelegate controls the NSManagedObjectContext:
- (NSManagedObjectContext *)managedObjectContext {
if (__managedObjectContext != nil)
{
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
I`m using instances of the ManagedObjectContext to display my Core Data Objects.
like this.
example table view controller:
CRMAppDelegate *appDelegate = (CRMAppDelegate*) [[UIApplication sharedApplication] delegate];
self.managedObjectContext = appDelegate.managedObjectContext;
// Initialize NSFetchedResultsController to retrieve data from the database.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Contact" inManagedObjectContext:self.managedObjectContext]];
// Sort results ascending by last name, first name.
NSSortDescriptor *sortDescriptorFirstLetterLastName = [[NSSortDescriptor alloc] initWithKey:#"firstLetterOfLastName" ascending:YES];
NSSortDescriptor *sortDescriptorLastName = [[NSSortDescriptor alloc] initWithKey:#"name_last" ascending:YES];
NSSortDescriptor *sortDescriptorFirstName = [[NSSortDescriptor alloc] initWithKey:#"name_first" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptorFirstLetterLastName, sortDescriptorLastName, sortDescriptorFirstName, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptorFirstLetterLastName release];
[sortDescriptorFirstName release];
[sortDescriptorLastName release];
/*
if ([self.managedObjectContext countForFetchRequest:fetchRequest error:NULL] == 0) {
[self reloadContactData];
}
*/
// Create NSFetched Results controller instance.
fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"firstLetterOfLastName"
cacheName:#"ContactsViewCache"];
[fetchRequest release];
self.fetchedResultsController.delegate = self;
// Load data.
NSError *error;
if (![fetchedResultsController performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
Is it ok to instance the NSManagedObjectContext like this? Because, the retainCount of the context is increasing til 10 while i`m testing my APP, even though i'm releasing the the context in "viewDidUnload" and "dealloc".
I`ve tried retain the context in a handler class like this:
- (NSManagedObjectContext *)getManagedObjectContext
{
NSManagedObjectContext *context = [self.managedObjectContext autorelease];
return context;
}
Whats the best way to interact with NSManagedObjectContext - objects and instances?
Thx and sry for my stuoid question :)
The value returned by retainCount is meaningless. Don't bother looking at it.
Use the Leaks instrument or heapshot analysis to determine if your app is leaking.
Related
I am having this strange issue which has been plaguing me all day, i am updating an object in a background thread which is saving correctly
NSFetchRequest *fetchRequest1 = [[NSFetchRequest alloc] init];
NSEntityDescription *entity1 = [NSEntityDescription entityForName:#"STMilestone"
inManagedObjectContext:tmpContext];
[fetchRequest1 setEntity:entity1];
[fetchRequest1 setReturnsObjectsAsFaults:NO];
NSError *error;
NSString *idNum = [obj valueForKey:#"id"];
// NSUInteger TrackerExists = [tmpContext countForFetchRequest:fetchRequest1 error:&error];
[fetchRequest1 setPredicate:[NSPredicate predicateWithFormat:#"identiferNumber = %#", idNum]];
NSArray *logs = [tmpContext executeFetchRequest:fetchRequest1 error:&error];
NSManagedObject *updateObj = [logs objectAtIndex:0];
[updateObj setValue:[NSNumber numberWithInt:20] forKey:#"progress"];
When i then get this object in the same background thread this gets the correct updated value
However when i go to get this updated value on my main thread it returns the old value, then when i clear the cache and reload the app it pulls the correct data
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"STTracker"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSError *error = nil;
NSMutableArray *array = [NSMutableArray arrayWithArray:[context executeFetchRequest:fetchRequest error:&error]];
I think this may be a context issue, my background thread context is being created with this
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
tmpContext.persistentStoreCoordinator = [appDelegate persistenceCoordinator];
and the main thread is
NSPersistentStoreCoordinator *coordinator = [self persistenceCoordinator];
if(coordinator != nil)
{
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
NSUndoManager *undoManager = [[NSUndoManager alloc] init];
[undoManager setGroupsByEvent:NO];
[_managedObjectContext setUndoManager:undoManager];
}
[_managedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
However i have updated other objects in my app and they are being pulled through, so any help or insight on why this might be happening would be great
This will not work:
NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
You need to need to init the context with the right concurrency type and assign it the correct parent context (or persistent store coordinator). See the docs
initWithConcurrencyType:
setParentContext:
When child and parent contexts are set up correctly, you an save the child context which does not really write to the persistent store but just "pushes" the changes up to the parent context. The changes are written to the store once you save the parent context again.
When my main view controller is loaded and it calls viewDidLoad I am performing a fetch request and retiring an array or my core data objects:
+ (NSArray *)getData {
// Fetch Data
NSError *error = nil;
if (![[[AppDelegate instance] fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
NSArray *items = [[AppDelegate instance].fetchedResultsController fetchedObjects];
return items;
}//end
For some reason, this returns an empty array when called from viewDidLoad.
If I call the same method from viewDidAppear: it works correctly and returns the NSArray of my CoreData objects.
Is there a reason why this won't work in viewDidLoad?
EDIT:
Fetched Results Controller method:
/**
* The controller the gets our results from core data
*
* #version $Revision: 0.1
*/
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
// Create and configure a fetch request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"SiteConfig" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Create the sort descriptors array
NSSortDescriptor *sectionTitle = [[NSSortDescriptor alloc] initWithKey:#"createdDate" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sectionTitle, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Create and initialize the fetch results controller
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
self.fetchedResultsController = aFetchedResultsController;
fetchedResultsController.delegate = self;
// Memory management
return fetchedResultsController;
}//end
My guess is that your view controller is part of your MainWindow.xib, and so its viewDidLoad is being called before your app delegate gets the core data context ready.
You probably want to run this in viewWillAppear anyway, so that you'll get new data if you leave the screen and return. The other option would be to ensure the app delegate responds to fetchedResultsController by getting the core data stack ready if it isn't already.
When I put a break point right on the line
"if (coordinator!= nil){"
the table view that is created from core data works correctly.
If I remove or put the breakpoint anywhere before that in my code it does not show the table view cells.
- (NSManagedObjectContext *) sharedManagedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator: coordinator];
[_managedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
}
return _managedObjectContext;
}
I have no clue as to how this is happening and had very little luck finding any other situations similar to this one.
Thank you for your help in advance!
Edit 1:
Here is the code that is being used to fetch the data from coredata
- (BOOL) setupFetchedResultsController:(NSError **) error {
self.managedObjectContext = [[PhotoBreezePersistentStoreManager shared] sharedManagedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"GeoTrack" inManagedObjectContext:self.managedObjectContext]];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"trackName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
controller.delegate = self;
self.fetchedResultsController = controller;
return [controller performFetch:error];
}
If I put a breakpoint in any of the above code...
after: "self.managedObjectContext = [[PhotoBreezePersistentStoreManager shared] sharedManagedObjectContext];"
... it will still work.
I'm pretty new to objective-c/cocoa so im not absolutely sure if a queue or thread is involved. I will however look into what you mentioned
Thanks!
Not enough information to really say; you would need to post details related to the creation/population of the table view.
However, this does sound like a classic race condition. Any threads or queues involved?
I am using core data, and have a UITableViewController which loads the data from core data. This is a modal view and if I load the modal view for the third time, it crashes with EXC_BAD_ACCESS.
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
// Create and configure a fetch request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Ride" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// Create the sort descriptors array
NSSortDescriptor *sectionTitle = [[NSSortDescriptor alloc] initWithKey:#"dateStart" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sectionTitle, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Create and initialize the fetch results controller
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
self.fetchedResultsController = aFetchedResultsController;
fetchedResultsController.delegate = self;
// Memory management
[aFetchedResultsController release];
[fetchRequest release];
[sectionTitle release];
[sortDescriptors release];
return fetchedResultsController;
}//end
The crash says that it is coming from this line:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Ride" inManagedObjectContext:managedObjectContext];
This is in viewDidLoad:
if (managedObjectContext == nil) {
managedObjectContext = [MyAppDelegate instance].managedObjectContext;
}
Presenting the modal view:
History *history = [[[History alloc] init] autorelease];
UINavigationController *nav = [[[UINavigationController alloc] initWithRootViewController:history] autorelease];
[self presentModalViewController:nav animated:YES];
Dismissing Modal:
- (void)done {
[self dismissModalViewControllerAnimated:YES];
}
Stack Trace that gives EXC_BAD_ACCESS:
Now, in order to setup this view with core data, I followed the Core Data Books example project and my code looks almost identical. Why would it be crashing after a few times of loading the modal view?
Ok, I figured it out, my managedObjectContext was not being retained because I was not using self. So, I fixed it by doing:
// Core Data
if (managedObjectContext == nil) {
self.managedObjectContext = [MyAppDelegate instance].managedObjectContext;
}
This is probably a very straight forward application, but I am new to Objective-C (coming from Java) and the whole memory management and "EXC_BAD_ACCESS" errors are breaking my heart.
I have a normal NavigationController iPhone App, with Core Data. in the AppDelegate the NSManagedObjectContext is created and passed to the RootViewController. A view things are looked up directly from the main thread to populate the table, and that seems to work fine.
The App is somekind of RSS-type reader, so as soon as the App starts I fire a thread to fetch new data and update the view:
-(void)updateData:(id)sender {
UIActivityIndicatorView *activityIndicator =
[[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
[activityIndicator startAnimating];
UIBarButtonItem *activityItem =
[[UIBarButtonItem alloc] initWithCustomView:activityIndicator];
[activityIndicator release];
self.navigationItem.leftBarButtonItem = activityItem;
[activityItem release];
// Start thread to update the data
[NSThread detachNewThreadSelector:#selector(doUpdateData) toTarget:self withObject:nil];
}
-(void)doUpdateData{
NSLog(#"Update data Thread (in 5 sec.)");
[NSThread sleepForTimeInterval:5];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
DataManager *data = [[DataManager alloc] initWithContext:managedObjectContext];
[data updateData];
[data release];
data=nil;
[self performSelectorOnMainThread:#selector(finishUpdateData) withObject:nil waitUntilDone:NO];
[pool release];
}
-(void)finishUpdateData{
self.navigationItem.leftBarButtonItem = updateBttn;
DataManager *data = [[DataManager alloc] initWithContext:managedObjectContext];
objects = [data getArticles];
[data release];
data=nil;
NSLog(#"Amount of records after update: %d", [objects count]);
[self.tableView reloadData];
}
The problem is that this doesn't work. In the DataManager, first settings need to be retrieved, and as soon as the NSEntityDescription is created I get the "EXC_BAD_ACCESS":
- (NSFetchedResultsController *)fetchedResultsController {
// Set up the fetched results controller if needed.
if (fetchedResultsController == nil) {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Setting" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"key" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchLimit:1];
.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
}
return fetchedResultsController;
}
I guess the pointer to the ManagedObjectContext is wrong, as a result from running in a different thread and memory-pool. So how do you create such an application if that is the issue), how do I get a reference to the original ManagedObjectContext format he thread?
[EDIT]
I also tried to use
iDomsAppDelegate *appDelegate = (iDomsAppDelegate *)[[UIApplication sharedApplication] delegate];
DataManager *data = [[DataManager alloc] initWithContext:appDelegate.managedObjectContext];
in doUpdateData (as hinted by other posts), but that gives the same result
Managed Object Contexts are not thread safe. Apple's guidelines indicate that you must have a separate instance of NSManagedObjectContext per thread.