开发者

Pop to root view controller without animation crash for the table view

I have 3 view controller in a tab bar controller. Clicking on any tab loads its root view controller in the navigation stack.

e.g. tab1, tab2, and tab3.

The 2nd view controller in the navigation stack (tab2VC2), has a tableView. Click on tab2 show VC in tab2, then tap on tab1, tries to go to its rootVC. Then the app is crashing saying

[UserDetailVC tableView:cellForRowAtIndexPath:]: message sent to deallocated instance 0xe0a23b0

If I popToRootVC with animation then its okay. I found viewDidAppear in the tab2VC2 is called where the tableView.reloadData is called, then dealloac, seems in the meantime reloadData starts working, the table is released. in case of animation, it gets some time, so it dont crash. But without animation, it is crashing. Do you think, its an iPhone bug? or I am doing wrong? Since pop to root controller have an option without animation, it should wo开发者_如何学运维rk, not it?

#pragma mark Tab bar controller delegate
- (void)tabBarController:(UITabBarController *)tbController didSelectViewController:(UIViewController *)viewController {
    int i = tbController.selectedIndex;
    NSArray *mycontrollers = tbController.viewControllers;
    [[mycontrollers objectAtIndex:i] popToRootViewControllerAnimated:NO];
}


I consider this a bug or at least a weakness in UIKit, but I've already blown half my day on it, so I'm not going to write it up with example code and report it to Apple right now. If someone else wants to do that, I would appreciate it.

Here's what I think is going on under the hood. You have a UITableViewController, let's call it myTable, on the stack of a UINavigationController, and that navigation stack is hidden because it's on an unselected tab or whatever. Then, you call [myTable.tableView reloadData], and iOS cleverly optimizes by not reloading the data right away, because the user won't be seeing it anyway if it's on a hidden tab. Instead, the reload request is deferred and stored somewhere for when the view is shown. But before it can be shown, you pop myTable off the navigation stack. When myTable's original tab is shown, the reload request gets executed, but its dataSource is no longer there, so it's a bad access.

Now from my tests with a subclass of UITableViewController that uses the automatically provided tableView property (not loaded from a NIB file), the UITableView is not being deallocated when myTable deallocates as in the situation above. That would be fine, except the default dealloc implementation for UITableViewController does not clear the dataSource property of the UITableView (which was set by the default implementation of init).

So, there are probably a couple good workarounds, like deferring the request to reloadData yourself, but the simplest one I can think of is putting this in the implementation of your UITableViewController subclass:

- (void)dealloc {
  ...
  self.tableView.delegate = nil;
  self.tableView.dataSource = nil;
  [super dealloc];
}

Any additional wisdom would be most welcome.


Jesse's answer works perfect. I just made a slight modification for ARC Support

- (void)dealloc 
{
  self.tableView.delegate = nil;
  self.tableView.dataSource = nil;
}


Double-click your executable in the Groups&Files list; click Arguments tab; click "+" in lower pane, enter "NSZombieEnabled", set value to YES, check the box, click red dot to dismiss. Now run your test case again, and it will tell you which object has been deallocated. I suspect it's the array backing your tableView in tab2V2. Make sure your memory handling is correct (is it retained and released correctly?).


You can try this to avoid the problem:

-(void)viewWillAppear:(BOOL)animated
{
   if (animated)
   {
      [self.tableView reloadData];
   }
}

When navigating from a tab bar controller, the view will not be animated. So if the view is appearing just for an instant before popping to the root, it won't attempt to reload the table data.


Had a similar problem and Im pretty sure it has something to do with the new SDK to be honest. The following code worked fine before. I have one navigationcontroller and a new one pushed on to that with 3 different views that gets put onto it (Think: changing password where you have 3 different steps: control, change, repeat.) If the user types the wrong password you will first be pushed of the "changing password controller" and then be pushed to the first page (logged out).

This worked before:

-(void)logout {
    [self.presentedViewController dismissModalViewControllerAnimated:TRUE];

    self.localNavigationController = nil;

    [self.navigationController popToRootViewControllerAnimated:TRUE];
 }

The way you could solve this is by change ViewWillAppear to ViewDIDAppear. You might have some lagg but atleast it doesnt crash :)


While Jesse's answer provided insight into the semantics around this problem, in my case the underlying cause was different. Just thought I'd mention it in case someone finds himself in the same situation.

I was seeing the error message

* -[UIAnimator removeAnimationsForTarget:]: message sent to deallocated instance ...

where the object in question was a UITableViewCell. On inspection, there were no memory allocation issues in my code.

The problem turned out to be that I was calling one of the popViewController:animated: family of methods from a non-UI thread. This thread had been created to run some network operations and database processing tasks. I simply moved the invocation of the pop method to the UI thread and found that I stopped getting the error.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜