I understand the reason why message sent to deallocated instance 0xebba1b0 is called, it is because I am sending a message to an object which is no longer in memory.
So here's my scenario. I have a ZoomedViewController which has a UITableView in it. The UITableView has a custom UITableViewCell, which has an attributed label as a subview. When a link is pressed on the attributed label (which in turns triggers didSelectRowAtIndexPath) it delegates to my MainViewController and calls the method closeZoomedImageVC in MainViewController:
-(void) closeZoomedImageVC
{
[self.zoomedImageContainer_ removeFromParentViewController];
[self.zoomedImageContainer_.view removeFromSuperview];
}
the issue is that when that didSelectRowAtIndexPath is triggered, then zoomedImageContainer_ is already gone. How do I solve this then?
To illustrate the point better, basically when I do:
[self performSelector:#selector(closeZoomedImageVC) withObject:nil afterDelay:1.0];
this doesn't cause the crash anymore, but this is not a solution as it is hacky. What this does is it lets didSelectRowAtIndexPath to be executed first before it is deallocated.
Store a reference to your UITableView in ZoomedViewController:
#property (nonatomic, strong) IBOutlet UITableView *tableView;
Make sure to connect the outlet in Interface Builder. Now, when your zoomedImageContainer_.view is removed, it won't dealloc the UITableView until you release that reference as well.
You also need to store a strong reference to your ZoomedViewController in MainViewController, and only set that to nil after you have saved the selected row back in MainViewController.
When tapping on a cell in a table will invoke an animation to highlight selected cell, and it needs a short duration complete, so the best way is what you are doing, perform selector after a delay, I think 0.5 second is enough.
I found the solution to my self is to just set the allowSelection = NO in the tableView property. This will let the attributedLabel inside the UITableViewCell to have interaction but will disable didSelectRowAtIndexPath being called
Related
normally when I'm using a viewcontroller that will push the current viewcontroller out of the way, I use a UINavigationController and push/pop the new viewcontrollers and let them handle all the dealloc themselves.
However, for example, in this case, I have a MainViewController, which is the default view when the app starts up. I have a second view, called SecondaryViewController, that is a popup on the main screen (sort of like a lightbox).
Here is the code to illustrate:
//From within mainViewController:
secondaryViewController = [SecondaryViewController alloc] initWithNibName:#"SecondaryViewController" bundle:nil];
[self.view addSubview:secondaryViewController.view];
The secondaryViewController interface looks like this:
//interface
#interface SecondaryViewController : UIViewController
{
IBOutlet UILabel *httpLabel;
IBOutlet UIScrollView *scrollView;
}
#property(retain, nonatomic) IBOutlet UILabel *httpLabel;
#property(retain, nonatomic) IBOutlet UIScrollView *scrollView;
As for the implementation, I have the #synthesize for the #property ivars, but I'm not doing any manual allocs. However, I did put a dealloc method:
- (void)dealloc
{
[httpLabel release];
[scrollView release];
[super dealloc];
}
But I'm not sure I need the above.
So my questions would be the following:
1) Do I need the above dealloc method in this case? Or more generally, when would a subview need a dealloc method?
2) If I do or dont need it, does it depend on whether I'm adding the secondaryViewController via addSubview or pushViewController? For instance, if I wanted to replace the entire mainViewController, with this:
[self.navigationController pushViewController:secondaryViewController animated:NO]
Would the secondaryViewController need a dealloc method?
Thank you!
Yes, you do need the dealloc method exactly as you have it, in this case. You are on the right track because you're assuming that since you are not doing any manual allocating, you don't need to do any dealloc/releasing... however, by specifying the property as (retain, nonatomic), you are doing implicit retaining.
This means that if you ever set those properties, what's actually occurring under the covers is something like this:
-(void)setHttpLabel:(UILabel *)newlabel
{
if (newLabel != httpLabel)
{
[httpLabel release];
httpLabel = [newLabel retain];
}
}
As you can see, your synthesize is causing a retain to occur on an object. If you never balance that retain out with a release, it will leak. So the only logical place to put it, is in your dealloc method. This creates the circle of life.
If you never set these properties and don't have release in dealloc, then it won't leak anything, but you obviously wouldn't want to make those assumptions.
If you didn't have any retain properties or any manual allocing of ivars, then and only then, can you nuke the dealloc method.
Hope that helps.
I think this is allowed in the latest iOS 5+ but previously you were not supposed to add another viewcontrollers view to your main viewcontroller. This is clear misuse and can lead to issues.
The concept of viewcontroller is one who controls all the views. A view controller should not control another viewcontroller unless it is a container viewcontroller such as UINavigationController/UITabBarController.
So please rethink the design. why do you need the SecondaryViewController. Why cannot the mainviewcontroller manage the secondary view as well?
Lastly, every viewcontroller should have the dealloc in it.
If you need to access secondaryViewController from your main view controller after you've added its view to the hierarchy, you should not deallocate it at that point. If you don't need to access the secondary controller after you've displayed it, you can dealloc it at that point.
In practical terms, if secondaryViewController is an ivar, it probably makes sense to keep a retained reference to it. If it's a local variable and you're not accessing it later, you should dealloc it.
EDIT:
Solved the problem myself. Turned out it was a leftover in the dealloc method that caused a UIButton to be released twice...
I'm trying to display a UIViewController on top of another UIViewController like a popup. Problem is that the view seems to be getting overreleased. With NSZombieEnabled, I get the following error:
[CALayer release]: message sent to deallocated instance 0x784bf40
I use this code to add the view:
//self.someViewController is declared as (nonatomic, retain)
self.someViewController = [[[SomeViewController alloc] initWithDelegate:self] autorelease];
[self.view addSubview:self.someViewController.view];
Then later on, I remove the view like this:
[self.someViewController.view removeFromSuperview];
self.someViewController = nil;
Should earlier comments not solve this perhaps this may help. I'm assumning you've created your someViewController property like this
#property (nonatomic, retain) NSViewController* someViewController;
in which case I believe your code is correct (at least I can see how it should work) and you might be seeing a secondary crash here.
I.e. when you're calling
self.someViewController = nil;
this should be freeing the memory up immediately (assuming a frame has gone by where the VC exists so the autoreleased count has already been decreased). Therefore if you have ANOTHER object being used in that someViewController VC who still exists and has a delegate set to your someViewController object and is doing a background task it will cause a crash when it trys to call back to your now deallocated object. (If you don't free your VC you wouldn't see this crash)
For example if you have a MKMapKit being displayed in someViewController and the delegate is set to someViewController... if you have implemented the method in someViewController
mapViewDidFinishLoadingMap:(MKMapView*)mapView
the MKMapKit may still be calling this from another thread if you haven't destroyed MKMapView object before yours.
I would always set other object delegates pointing to your VC (like MKMapView) to nil before destroying the said VC it uses to avoid this risk. For PDF rendering (CALayer?) you may find you need to explicitly release the object too given this uses a different memory alloc/free paradigm.
Solved the problem myself. Turned out it was a leftover in the dealloc method that caused a UIButton to be released twice...
I have a table view set up in IB. It's delegate/datasource are connected to this class:
#interface EditPlayersViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
I'm trying to call the reloadData method. If I use [self.view reloadData]; it doesn't respond, I guess because technically self.view isn't a UITableView. I tried to add the following:
IBOutlet UITableView *myTableView
and then I connected it to my table view in IB. Now my program crashes when the view loads. Any ideas?
Make EditPlayersViewController a subclass of UITableViewController rather than UIViewController. Dump the explicit interface implementations and the IBOutlet. Then, [self.tableView reloadData] should work.
Watch the console when it crashes and then bring up the debugger window. Both of these windows will give you great insight into what is happening. The data you're trying to load may be to culprit and not the reloadData call.
Make sure you have implemented the UITableView data source methods.
put break points in data source methods and try to find out where it is crashing.
UITableViewDataSource_Protocol/Reference
I've got a UITextField on UITableViewCell, and a button on another cell.
I click on UITextField (keyboard appears).
UITextField has the following method called:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
NSLog(#"yes, it's being called");
owner.activeTextField = textField;
return YES;
};
Where owner.activeTextField is a (retain, nonatomic) property.
The problem
When the keyboard is visible I scroll the cell out of the view.
I then click a button that is on a different cell. The button calls:
[owner.activeTextField resignFirstResponder]
And that causes EXC_BAD_ACCESS.
Any idea? The cell is most definitely in the memory. My guess is that once it disappears it is removed from the view and one of it's properties (parent view?) becomes nil and that causes the said error..
Am I right?
TL;DR; How can I remove the keyboard (resign first responder) when UITextField is removed from the view?
Sometimes the problem can be another level deep... Check and make sure that the next object in the responder chain (the one that's subsequently receiving the becomeFirstResponder message) isn't garbage. Just a thought.
Have you checked owner.activeTextField to see if it has been deallocated / set to nil? Not sure if that would call a EXC_BAD_ACCESS but worth a try.
Also do you have any calls to NSNotificationCenter? I was struggling with something similar today which was causing an EXC_BAD_ACCESS on becomeFirstResponder, which was due to me calling [[NSNotificationCenter defaultCenter] removeObserver:keyboardObserver]; on the incorrect delegate.
A bit old, but since I just had the same problem with a legacy manual reference counting app, I'll give it a try. Note: this problem shouldn't happen with ARC anymore (and if it does, my solution most definitely doesn't fit there...).
What seems to happen is that:
the textfield is saved into the cell and is (possibly over)retained
when you scroll the cell with the textfield, the cell get's recycled (which is good), but the textfield is not (which is why the textfield is still in memory when the bug happens)
at that point, dismissing the keyboard correctly resign the first responder for the textfield, but as the call travels down the view hierarchy, it hits the cell, which is not in memory anymore.
a simple (and imo elegant) solution for the problem would be to
fix the over retention of uitextfield if any
subclass UITextField to resign first responder status before being deallocated
a single method,like this:
- (void) dealloc {
[self resignFirstResponder];
[super dealloc];
}
would be required, which will have the side-effect benefit of removing the keyboard as soon as the cell gets out of view.
Another solution (which is the one I chose, for various reasons) would be to manually retain and recycle the cell with the textfield until the table is deallocated.
i'm sure you already solved your problem, but I hope this will help someone else...
I have a view controller managed in a UINavigationController. My view controller puts up a "loading" screen, and uses ASIHTTP to asynchronously load table data. Then it renders its table, and in each cell there's an image that it uses ASIHTTP to asynchronously load. When it lands each image, it uses [self.tableView reloadRowsAtIndexPaths] to reload that row, inside which the image is fed to the UIImageView in each row.
Works great. But if I back out of the view before it's done loading, it crashes.
Where it crashes is in my -tableView:numberOfRowsInSection method, and NSZombies tells me it dies because it's asking for the -count of an NSArray called self.offers that has been deallocated.
That method looks like this:
-(NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section
{
return [self.offers count];
}
Wrapping that return in if (self.offers) made no difference.
My -dealloc releases and sets-to-nil every one of these properties, including both self.offers and self.tableView itself. I even tried setting up a BOOL disappearing property, hitting it with YES in -viewWillDisappear, and hanging conditional behavior off that, but it doesn't work because viewWillDisappear doesn't seem to get called! Far as I can tell we're not getting ANY method called when the navigation bar pops us off.
What do I do about this?
EDIT:
Thanks to #cduhn (who's bucking for a check!), I did a bunch more looking at this. The problem has been, my -dealloc just isn't getting called when I pop this viewcontroller (nor my -viewWillDisappear nor -viewDidUnload or anything else I could use to unhook the delegation structure that's at the root of this problem).
Then I realized: THIS viewController isn't the one on the NavController stack! What's at the top of the stack right here is a shell view, just a segmented controller and a big empty UIView. I toggle the contents of that UIView between two other UIViewController subclasses depending on the state of my segmented controller. So when my view with the table on it's PARENT view gets popped from the nav stack, this CHILD I'm working on doesn't seem to get any notice about it. Which is odd, because I'm definitely releaseing it.
I can call its -dealloc from my shell controller. I could call its -viewWillDisappear too, for that matter. Is that how I should be handling this? Probably I should put something into my shell controller's viewWillDisappear like:
[[self.mainView subviews] makeObjectsPerformSelector:#selector(viewWillDisappear)];
...so that message propagates down to my child views.
Am I on the right track here, you think??
(Oh man... and that also explains why actions from inside this child table view can't get to self.navigationController! I've been puzzled about that for weeks!)
Bugs like this, where a method gets called on an object after it's been deallocated, often happen when a method gets called on a delegate after that delegate has been deallocated. The recommended practice to avoid bugs like these is to set any delegate (or delegate-like) properties of an object to nil before you release that object in the delegate's dealloc method. I know that's a confusing sentence, so I'll explain it in the context of your bug.
You have an asynchronous image download that finishes after you've backed out of your table view controller. When this happens, you're calling reloadRowsAtIndexPaths:withRowAnimation:, which results in a call to tableView:numberOfRowsInSection: on the table view's dataSource. This call is failing because that dataSource no longer exists.
The problem is that the table view object still has your controller set as its dataSource and delegate properties, even after your controller has been deallocated. The solution is to simply set these properties to nil in your controller's dealloc, like this:
- (void)dealloc {
self.tableView.dataSource = nil;
self.tableView.delegate = nil;
self.tableView = nil; // Releases as a side-effect due to #property (retain)
[super dealloc];
}
Now when your table view tries to call tableView:numberOfRowsInSection: on its dataSource, it will send the message to the nil object, which swallows all messages silently in Objective C.
You should also do the same thing with your ASIHTTPRequest's delegate, by the way.
Any time you have an asynchronous operation that calls delegate methods upon completion, it's particularly important that you set that delegate to nil. When using UITableViewController under normal circumstances you can typically get away without setting the dataSource and delegate to nil, but in this case it was necessary because you're calling methods on the tableView after its controller has gone away.
As far as I can tell, the user cannot actually "back out" of a view while the UITableView is loading it's data. The methods are not run on a thread and block the main one, also blocking UI interaction. I cannot replicate your results. Even, scrolling the table view quickly and then pressing the back button.
I suggest that the stack popping is not the problem here.