I am developing my first iPhone app for a couple of months and now I got stuck on a memory error “message sent to deallocated instance”. I have written down all my observations (from the debugger) but can't find the reason. I will really appreciate any help.
SITUATION:
I have created pick_ListBrowseController, a subclass of UITableViewController.
It is called to pick an item from a scrolling list. The objects presented come from a CareData database.
I useNSFetchedResultsController.
In the implementation of pick_ListBrowseController, there is a typical method returning a fetched results controller:
- (NSFetchedResultsController *) fetchedResultsController {
// If the fetched results controller already exists, return it:
if (fetchedResultsController != nil) { return fetchedResultsController;}
(…) // constructing fetchRequest object with entity and sort descriptors
[NSFetchedResultsController deleteCacheWithName:#"pick_List"];
// Create fetched request controller:
NSFetchedResultsController *aFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:#"pick_List"];
// Set the fetched results controller's delegate to self;
// it will send controllerDidChangeContent:
[aFetchedResultsController setDelegate: self];
// Assign this new fetched results controller to the property:
[self setFetchedResultsController: aFetchedResultsController];
// Return fetched request controller:
return fetchedResultsController;
}
BUG SCENARIO A:
STEP 1) I call my pick_ListBrowseController, select an item and navigate back. The view disappears.
In the debugger, I can see that the pick_ListBrowseController object had memory address 0x729b950
STEP 2) I call pick_ListBrowseController once again. It is now another instance, with memory address 0x755a4e0
STEP 3) When I try to pick an item, I get error message:
[pick_ListBrowseController controllerDidChangeContent:]: message sent
to deallocated instance 0x729b950.
The address is from STEP 1) ! For some reason, the framework code (which is not accessible for me) sends controllerDidChangeContent: to the already deallocated object from STEP 1), not to the object from STEP 2), as it should.
BUG SCENERIO B:
Even worse, it happens even when in STEP 1) I call a different controller object , also a subclass of UITableViewController. Steps 2) and 3) remain the same. The same bug occurs and in the debugger I can see that even now controllerDidChangeContent: is sent to the object from STEP 1). Hence it is sent not only to the wrong (and deallocated) object, it is even sent to an object from a different class!
QUESTIONS:
1) What may be the reason? How to fix it?
2) My app is really big. Is it possible that it became too big causing the memory problems described above? If yes, what can I do to get things right?
Currently, I use Xcode Version 4.2 build 4C199 and I use Automated Reference Counting (ARC) - I went through the refactor process “convert to Objective-C ARC”.
Any help will be highly appreciated.
It is likely that your FRC is not getting released when your VC is dealloc ed. If it is hanging around and has your VC as a reference to the delegate then you have a dangling pointer. Double check that you are cleaning up the FRC.
Related
I will try and explain this as best as possible if I have this code here
ViewTwoController *home = [[ViewTwoController alloc] initWithNibName:#"contentscreen" bundle:nil];
[self presentModalViewController:home animated:YES];
[home release];
I will start a new .m and .h class. What I would like to try and do however is when this is called, have the .m and .h class where it was called from running in the background so I do not lose data.
The best example I can think of is with Android. If you begin a new class, and don't add the finish() statement in the class the call was made from, the previous class runs behind the current class (that was pushed to the front) and maintains all the data it originally had, so if you hit a return button, you will see the information you had moments ago. Is this possible? I can try add more detail if people cannot understand what I am trying to do.
You need to understand a objects life cycle a little better.
An object is brought into existence generally with a 2 part process.
allocation - (grabbing the memory for the object and its members)
initialization - (setting the object up for use)
This can be combined into single step with the +new class method which combines alloc and init.
lets introduce an example class called MyClass and an object of that class called myObject. By convention classes start with uppercase letters and objects start with lowercase letters. So Without further ado, some code:
MyClass * myObject;
this this makes an object pointer, but doesn't allocate any memory for it or direct the pointer to reference anything.
myObject = [[MyClass alloc] init];
this actually creates an instance of MyClass, passes the -init message to it, then assigns the return value of the init message to myObject. At this point the reference count of this object is 1.
myObject can potentially go out of scope but that alone doesn't free the memory that was allocated during the alloc step.
in order to free that memory a release message will need to be passed to the object.
[myObject release];
The effect of release is to decrement the reference count, if the reference count is already 1 then the object will then be passed the -dealloc indicating that it is actually being freed.
back to your question... essentially [self presentModalViewController:home animated:YES]; ends up calling -retain on home, so that it will not be destroyed until you dismiss the modal view controller. In affect when you call release or autorelease you aren't dealloc'ing the object, just telling the object:
"Hey, I don't need you anymore, and if no one else does either then free up all the memory that you grabbed earlier".
Your problem has nothing to do with "class running in the background" but more with how you manage your data.
When you present a modal view controller, its parent (the view controller you presented it from) isn't destroyed (unless you specifically release it, which would probably crash your app later). So if you're wondering whether its still in memory; it is. As for tasks still running, it depends on what those tasks are. For example, you can still send it messages (call methods) and it will gladly receive those messages from your or from a delegate and perform whatever action it has to while it's off-screen.
Hope that helped.
In this case you are presenting new view controller. The main thread will be in the new controller presented. If you want something to run in background in the previous view controller then you can create a background thread. This can be done using [self perfomselectorInThebackground ... ] Or some other methods like GCD. (The main thing is you should not block Main thread)
I'm using core data and NSFetchedResultsController in an app, for feeding a UIViewTable with Car objects. I have a button that, when tabbed, takes the app to another view when the user can add a new Car. The problem I have is that even if the user doesn't create the car in the second view, it's added to the table. If I restart the application, the Car wasn't added to the DB.
This is related to the fact that I create an instance of the Car in the second view, in the viewDidLoad method, using something like this:
car = [NSEntityDescription insertNewObjectForEntityForName:#"Car"
inManagedObjectContext:context];
This is added even if I don't save the context.
I tried to delete the object when the second view is about to be closed, using this:
[context deleteObject:car];
This partially works. The car is not added to the table in the first page, but looks like the indexes of the data source are altered. If I scroll all the way down I got this error:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[_PFBatchFaultingArray objectAtIndex:]: index (9) beyond bounds (9)'
Any ideas will be appreciated.
Try creating your car object by passing a nil context:
car = [NSEntityDescription insertNewObjectForEntityForName:#"Car"
inManagedObjectContext:nil];
From there, if the user decides to save it you can move the object to the main context.
The other alternative is to have a separate MOC for this view (which adds unnecessary complications) and then again move the object between contexts if/when required.
Cheers,
Rog
PS: if you're wondering whether you can pass nil as the managedObjectContext, this is straight from Apple's docs on NSManagedObject:
...If context is not nil, this method
invokes [context insertObject:self]
(which causes awakeFromInsert to be
invoked)...
More details here
[EDIT]
In addition to this, I just came across something interesting when looking at the NSFetchRequest documentation and thought you might want to have a go (I haven't tried myself). It appear that you can tell the fetchRequest whether to include pending changes (i.e. not saved) or not when fetching objects:
- (void)setIncludesPendingChanges:(BOOL)yesNo
FYI the default value is YES - more details here
First, try delaying the insertion of the new object when the user commits the change.
Only if it's not appropriate:
When the context has changed, NSFetchedResultsController is automatically informed to modify its data passed to the table view controller. However, the table view itself is not modified, so you have to update the table view by yourself.
That's why NSFetchedResultsController has a delegate object (conforming to NSFetchedResultsControllerDelegate protocol) which is responsible to update the table view.
For example, in CoreDataBooks sample project, you will see delegate methods of this protocol in RootViewController class. The most related delegate method is controller: didChangeObject: atIndexPath: forChangeType: newIndexPath:. You may probably want to do something like the following:
if (changeType == NSFetchedResultsChangeDelete) {
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
This is a very very... very odd bug. It's hard to describe the exact project I have, but I will try and create a simpler representation of the classes I have. It goes like this:
Assume I have a navigation controller as my top view controller. Inside it, at one moment in time I have a UIViewController, let's say a ContactsScreenController. The view for this contains multiple UITableView that each is controlled by a separate object of type MyTableController (delegate&datasource). I do this by keeping an array of controllers
// This is the interface for my screen controller. An object of this type goes in a top-
// level navigation controller
// MainScreenController.h
#interface ContactsScreenController : UIViewController
NSMutableArray* tableControllers;
#end
// MainScreenController.m
- (UITableViewCell*)cellForRowAtIndexPath..something..
{
// Here what I do is create a new controller if needed, and add it to tableControllers
// Memory allocations & releases are good because I checked with instruments
}
#define SAFE_DEL(x) { if (x != nil) { [x release]; x = nil; } }
- (void)dealloc
{
SAFE_DEL(tableControllers);
[super dealloc];
}
Now, MyTableController is a more complicated object as it handles fetching data from a web service, but basically what I do is I want to make sure that when the object is deleted, I cancel any pending data requests, like this:
// MyTableController.m
- (void)dealloc
{
[globalDataProvider cancelRequestsForController:self];
// release other objects i might have
[super dealloc];
}
OK, so this is my objects setup. The crash occurs when I am deleting the object tableControllers. It decrements the retainCount for my MyTableController objects and it reaches 0 (checked using Instruments). But for some UNKNOWN reason, I get calls for cancelRequestsForController, AFTER the retain count has been zero.
Obviously, I get a EXC_BAD_ACCESS.
Before you start thinking it's a problem with my retain/release pairs, the application runs perfectly if I am releasing the main screen controller while the inner tables are static. As soon as the are scrolling and I hit the Back button in the navigation controller I experience the bug.
I've checked using instruments the entire history of retain count changes for my inner controllers and it is good (no unusual stuff). When the bug occurs, my last entry in the history is from QuartzCore run_animation_callbacks with a retain count of -1.
Any ideas? :)
PS: As a quick solution to get the project going, I've moved the cancelRequestsForController in a separate method and I'm manually calling it for each object in tableControllers before the release. This way I am sure that there will be no calls after the release, no matter the state of the tableview.
- (void)dealloc
{
for (TableController* c in tableControllers)
[c cancelRequests];
SAFE_DEL(tableControllers);
[super dealloc];
}
But I don't like this solution for several reasons.
Thanks to everybody for the answers/comments.
The problem was generated by a call to [super dealloc] in an object, before I got the chance to release my objects. This caused a lot of crazy stuff to happen. I moved the [super dealloc] at the end of my dealloc method (after my releases) and it works fine now.
The SAFE_DEL macro is unnecessary and makes the code less readable. Simply do:
[someObject release], someObject = nil;
It won't matter if someObject is already nil and it makes the code more directly readable.
As soon as the are scrolling and I hit
the Back button in the navigation
controller I experience the bug.
Any time you have non-memory management logic, you have fragility. Namely, when dealloc is being executed, it is quite likely because there is an entire sub-graph of objects in your application that are being deallocated. Since deallocation order is largely non-deterministic, you can't safely trigger any complex behavior in dealloc without risk that you are going to message an already deallocated object.
The best solution would be to get that cancellation mechanism out of dealloc!
Two things to check:
1. does your globalDataProvider (or any other class for that matter) have a reference to your controller (i.e. to call it back with data?) Your retain counts could be zero but for the wrong reason. How are you allocating the array, as well as each controller in the array? Do you have #properties?
2. In your executables properties Arguements screen, set NSZombieEnabled=YES That will tell you exactly which object is being called when it has a zero retain count.
HTH,
-Mike
I'm using NSFetchedResultsController for my table view. I call -performFetch inside my controller's -viewDidLoad method.
Sometimes my controller gets unloaded and then re-loaded, resulting in another call to -viewDidLoad and -performFetch. I found that this was causing an error: "NSFetchedResultsController error: section '(null)' not found in controller". I found that calling -performFetch multiple times like this was causing the problem, and modified my -viewDidLoad: method to do the following:
if( fetchedResCtrlr.fetchedObjects == nil )
{
NSError *error;
if ( ![fetchedResCtrlr performFetch:&error] )
...
}
Being new to Core Data, I'm wondering if this is the correct action to take. Should I actually be able to call -performFetch: more than once without error? Should I be doing something in -viewDidUnload:?
Thanks!
Normally, there is nothing that needs to be done with the NSFetchedResultsController in the -viewDidUnload:. Also, checking for nil against the -fetchedObjects is normally not worth it. Sounds like your code has other flow issues. Calling -performFetch: more than once would only harm performance on its own, without any other ill effects.
My app involves a main screen with several sorting/viewing options of a set of data. Depending on what the user chooses, I may list them, e.g. alphabetically, N most recent, or grouped somehow.
I started the app as a Core Data Table-based navigation app; my app delegate sets up the Core Data stack (unchanged generated code), gives the NSManagedObjectContext to the controller for the initial screen, and it passes it to the UITableViewController implementing my "list of entities".
Since my three different views of the same data all end up showing a table listing out the data, I expanded this class to have three different NSFetchedResultsControllers, each with the one UITableViewController instance as their delegate. Before pushing this view controller on the stack, I call a method to switch which NSFetchedResultsController to use, e.g.
-(void)configureForMostRecent {
self.activeFetchedResultsController = self.mostRecentResultsController;
}
Now I am getting random crashes from Core Data, e.g. NSInternalInconsistencyException and other things like that. Sometimes, I use the app and everything's fine, other times, it crashes almost instantly.
So, my instinct is that my design is just a Bad Idea(tm).
Should I basically stick to a "One UITableViewController to one NSFetchedResultsController" sort of model and just use other coding styles to reduce boilerplate?
Using multiple NSFetchedResultsController instances is a perfectly valid design based on the description you have given so far.
Are you trying to use the same cache for each of these NSFetchedResultsController instances? Are you calling -reloadData on the table whenever you switch to a different NSFetchedResultsController? Both of those could be causing the crash you are seeing.
Update
The delegate is not an issue but not calling -reloadData is going to be a killer. The delegate methods really are there just to update the UITableView when the NSFetchedResultsController changes. The fact that a reference to is passed into those delegate methods is a hint that they are designed to handle multiple NSFetchedResultController` instances calling into them.
You could use one fetch controller, adjusting the fetch predicate and refetching as needed.
EDIT
Following my example case:
[NSFetchedResultsController deleteCacheWithName:#"MyObjectsCache"];
NSPredicate *_predicate = nil;
if (condition) {
_predicate = [NSPredicate predicateWithFormat:mySearchPredicateString];
self.currentTableView = searchDisplayController.searchResultsTableView;
}
else {
_predicate = [NSPredicate predicateWithFormat:myDefaultPredicateString];
self.currentTableView = tableView;
}
[fetchedResultsController.fetchRequest setPredicate:_predicate];
NSError *_error = nil;
if (![fetchedResultsController performFetch:&_error]) {
// handle error
}