views:

40

answers:

1

My program is crashing because my TextField is sending messages to its delegate after the delegate has been deallocated. I have an object that serves as a UITableViewDataSource and a UITextFieldDelegate. In cellForRowAtIndexPath, I create a TextField inside each TableViewCell and assign self as the TextField's delegate. When I click a button on the screen, the view reloads (it runs all the same code that is run when the view was loaded the first time):

DetailsViewController *controller = [[DetailsViewController alloc] initWithNibName:@"ClientView" bundle:nil];
self.detailsViewController = controller;
[controller release];

NRFCAppDelegate *appDelegate = UIApplication.sharedApplication.delegate;
for (UIView *view in [[[appDelegate.splitViewController.viewControllers objectAtIndex:1] view] subviews])
{
    [view removeFromSuperview];
}
CGRect rect = [[[appDelegate.splitViewController.viewControllers objectAtIndex:1] view] frame];
self.detailsViewController.view.frame = CGRectMake(0, 0, rect.size.width, rect.size.height);
[[[appDelegate.splitViewController.viewControllers objectAtIndex:1] view] addSubview:self.detailsViewController.view];

self.detailsViewController.detailsTitle.title = self.currentClient.name;
self.menuViewController.clientLabel.text = self.currentClient.name;

self.menuViewController.propertyLabel.text = @"Properties:";
self.menuViewController.addPropertyButton.hidden = NO;
self.menuViewController.editPropertiesButton.hidden = NO;

ClientMenuDelegate *menuDelegate = [[ClientMenuDelegate alloc] initWithRootController:self];
menuDelegate.properties = self.currentClient.properties.allObjects;
self.menuViewController.tableView.delegate = self.menuViewController.tableView.dataSource = menuDelegate;
self.menuViewController.delegate = menuDelegate;
[menuDelegate release];

ClientDetailsDelegate *detailsDelegate = [[ClientDetailsDelegate alloc] initWithRootController:self];
detailsDelegate.client = self.currentClient;
self.detailsViewController.tableView.delegate = self.detailsViewController.tableView.dataSource = detailsDelegate;
self.detailsViewController.detailsDelegate = detailsDelegate;
[detailsDelegate release];  

[self.menuViewController.tableView reloadData];
[self.detailsViewController.tableView reloadData];

self.detailsViewController.detailsDelegate = detailsDelegate; causes the previous ClientDetailsDelegate to be released (and therefore dealloced), because it is a retain-type property. The problem is that if my TextField was the FirstResponder when the reload button is clicked, it still sends it's textFieldShouldEndEditing, textFieldEditorDidChangeSelection, etc, messages to the now dealloced ClientDetailsDelegate. It seems like those messages should get sent before any of the above code is executed, because the TextField is losing focus the moment the button is clicked. Also, once the removeFromSuperview is called, the TextField itself shouldn't exist anymore anyway.

How can I make sure that the TextField is destroyed when I reload the view, and prevent it from sending messages to its delegate after the delegate has been dealloced?

A: 

The text field only sends those messages after all this stuff has happened possibly because the current control might refuse to accept first responder. Anyway, the reason is not important. The point is you are destroying a delegate before a control that uses it has finished with it. Before the line:

self.detailsViewController.detailsDelegate = detailsDelegate;

you need to set the delegate property of the control that the old delegate was for to nil. You could probably do it in the dealloc method of the delegate object.


On another note:

ClientMenuDelegate *menuDelegate = [[ClientMenuDelegate alloc] initWithRootController:self];
menuDelegate.properties = self.currentClient.properties.allObjects;
self.menuViewController.tableView.delegate = self.menuViewController.tableView.dataSource = menuDelegate;
self.menuViewController.delegate = menuDelegate;
[menuDelegate release];

looks wrong. None of the properties you assign menuDelegate to are retain properties. I think it will go away as soon as you release it leaving dangling pointers (unless menuViewController is your own class and you have made the delegate property retain).

JeremyP
Thanks, that does make sense, but how I can set the TextField's delegate to nil? The UITextFields are added as a subview of my UITableViewCell in cellForRowAtIndexPath, so I don't have any pointers to them in my class. The only thing I can think of is to maintain a separate array of pointers to my TextFields; do I really have to do that?
GendoIkari
Oh, and as for the menuDelegate (and also the detailsDelegate), my "menuViewController.delegate" and "detailsViewController.delegate" are retain properties. Thanks though.
GendoIkari
Well I went ahead and created an NSArray to keep a pointer to my TextFields, and in my dealloc method I set all their delegates to nil. This fixes the problem. Thanks!! My first question on SO; definitely pleased.
GendoIkari
@GendoIkari: The reason why delegates are usually not retained is that they oten have a reference to the view they are delegate of in their ivars. There's no reason why you can't set a property in the delegate to the text field at the same time you set the text field's delegate.
JeremyP
@JeremyP, thanks, I'd read the documentation about how delegates are usually assign instead of retain, but never found a good explanation of why until now. In this case though, my delegate object is a different object from my ViewController, which means it does not have a reference to the objects that it is a delegate of. When I set menuViewController.delegate = menuDelegate, it's different than setting a TableView's delegate, because my delegate object isn't a delegate _of_ menuViewController like it is of a TableView.
GendoIkari
The only reason menuViewController needs a strong reference to the menuDelegate is because someone needs sto maintain ownership of that delegate to stop it from being dealloced. I guess in a more typical situation, menuViewController would also be the delegate, but because I load up different data on the same-looking view, it made sense for the controller and the delegate to be separate objects. Thanks again!
GendoIkari