View controller release before delegate return - iphone

I have a problem. My view controller (ViewController) implement a delegate method of a object (DataPuller, data get from the internet). DataPuller will retrieve data on the internet without blocking user interaction with the view. But when I navigate between screen, in some cases, that ViewController release before DataPuller return the list of objects. The DataPuller return, it checks:
if (delegate && [delegate respondsToSelector:#selector(getCommentDidDownloadFinish:)]) {
[self.delegate performSelector:#selector(getCommentDidDownloadFinish:) withObject:self];
}
And the application crash here because ViewController release, it becomes a zombie object. Does anyone have this problem before and how to solve it? I think another way is using NSNotification, but I wonder any other better solutions. Any ideas, solutions are welcomes. Thanks.

Your view controller must remove itself as the DataPuller delegate at some point. Typicially, this is handled in the dealloc method:
- (void)dealloc {
dataPuller.delegate = nil;
[dataPuller release];
[super dealloc];
}
You may also decide to do this in -viewDidUnload or -viewDidDisappear:.

Delegation (usually) implies some sort of ownership - i.e., if you make an object a delegate of another object, usually the delegate object holds a strong reference (i.e., retains) the delegating object.
As an example, a UITableViewController is the delegate of its UITableView. This is okay, because the controller retains the tableview through the "view" property.
If your design does not allow ownership, use notifications, like you already suggested. As a bonus, notifications can signal multiple listeners if you would ever need that.
Don't forget to remove your observer in the dealloc of the view controller!

Related

NSDictionary retain crash

I have a NSDictionary, storing value by parsing. I am allocating and storing value in -viewDidLoad. And accessing the values in -viewWillAppear. It works perfectly fine.
Then, I have UIPageControl with UIScrollView. While scrolling the UIScrollView, again I am accessing the same dictionary, it crashes saying
[CFString respondToSelector:]: send to deallocated....
- (void) viewDidLoad {
[super viewDidLoad];
scoresDict = [[NSDictionary alloc] initWithDictionary:[scoreObj scores]];
}
- (void)loadScrollViewWithPage:(int)page {
if (page < 0)
return;
if (page >= kNumberOfPages)
return;
NSLog(#“scoresDict %#”,scoresDict);
}
I tried using retain in the same function, it didn’t work out. And copy, it also didn’t work. Any help would be appreciated. Thanks!
You don't say so, but it looks like all of these methods are being called from an instance of your custom UIViewController or UIScrollViewController subclass.
The most likely problem is that this instance itself isn't being retained.
When you first load the view from the nib, both -viewDidLoad and -viewWillAppear are called. It's possible, however, that garbage collection is happening in between those calls and your call to -loadScrollViewWithPage, and that no object has any connection to the view controller instance itself.
If that's the case, using copy or retain on the scoresDict won't solve the problem. You need to make sure that you are copying or retaining the view controller instance itself. Figure out what object needs to be retaining your view controller and make sure it is being retained.
A quick way to test whether this is the problem or not: create a "myViewController" property in your application delegate. In viewDidLoad, add a line:
[[[UIApplication sharedApplication] delegate] setMyViewController:self];
If this fixes your problem, it means the problem was the view controller getting released. That doesn't mean this is your best solution, though--it's just a diagnostic. You need to figure out what should be retaining your view controller and make sure it does so.

Deallocated in delegate paradigm

I have ParentViewController that allocates ChildViewController, pushes it onto controller stack and releases it.
ChildViewController implements the protocol ProductDownloadDelegateProtocol required by the Product class.
At some point, ChildViewController creates a Product object and sets itself as its downloadDelegate.
While downloading, Product class updates ChildViewController via methods defined in ProductDownloadDelegateProtocol.
If the user presses the back button in the navBar of ChildViewController while downloading, the next update of download percentage from Product causes an EXC_BAD_ACCESS.
Although Product checks if downloadDelegate is nil, problem still occurs since ChildViewController/downloadDelegate is deallocated, but not set as nil. I don't know which point is best to set ChildViewController to nil.
Is my design wrong?
If your ChildViewController creates an instance of Product and sets itself as the delegate, it should be its responsibility to remove itself as the delegate when it's about to be unloaded. Either in it's viewDidUnload or dealloc method you should be setting the Product delegate to nil.
If ChildViewController stays around (say you are reusing the view controller), maybe you can remove it as the delegate in the viewWillDissappear method.
Another solution to fix this particular EXC_BAD_ACCESS issue is to move to ARC and use Zeroing Weak References (see a good writeup here, http://www.mikeash.com/pyblog/friday-qa-2010-07-16-zeroing-weak-references-in-objective-c.html).
Still, I would recommend you move to ARC for the right reasons, and not to fix this particular issue you are facing.
My rule of thumb goes like this: You should never be the delegate of an object you do not own. Here, "own" means "hold a strong reference to" (in ARC terms). The main exception is when the delegate retains you, and UIApplication.delegate because that's a bit weird.
Usually I bundle the logic into the setter like this:
-(void)setProduct:(Product*)p
{
product.delegate = nil;
[product release];
product = [p retain];
product.delegate = self;
}
-(void)dealloc
{
self.product = nil;
}
However, an underlying problem with this design is that a "product download" can only have one delegate. What if you navigate away from the ChildViewController and then back into (a new instance of) it? Are they different Product instances, both being downloaded?
A better way might be to have a download manager singleton (as much as I hate singletons) that manages downloads and uses NSNotification/NSNotificationCenter for progress notifications.

Check if delegate still exists before calling respondsToSelector

I have made a UIView sub class to deal with iAds and adMob. The view controller is the sub class delegate and everything works ok. Sometimes however, the view controller disappears before the ad has been fetched. To resolve this I set the delegate to nil in my view controller's dealloc implementation.
The problem I have is that sometimes the respondsToSelector: method is ran at the same time as the view controller is getting deallocated. This causes a crash. Does anyone know how I may rearrange this?
The ad view is on about 10 different view controllers so I would like one place to create the ad code.
Many Thanks
One easy way is retaining the view until you had a chance to set the delegate to nil, and then release it. Then you can ensure that the object is still alive and prevent crashes.
If you can't retain the view, then use a static method to get the instance which is cleared in dealloc. ie: instead of:
if (delegate && [delegate respondsToSelector:#selector(...)])
Do this:
if (s_myViewDelegate && [delegate respondsToSelector:#selector(...)])
In the class:
- (id) init {
s_myViewDelegate = self;
...etc...
}
- (void) dealloc {
s_myViewDelegate = nil;
}
if (delegate && [delegate respondsToSelector:#selector(...)])
Although this is a question long ago, I really messed with it a bit and finally found something that might help.
Set a completion block rather than a delegate for finished or failed event, and this would help.
Yes it's a problem with iAd and admob. I had also this kind of problem. I have solved the problem by adding add view on main window and make delegate to app delegate so app delegate will never deallocated until you close the application.
There is actually a fast and not really good solution - to use #try/#catch block. Just if you get to the #catch block your delegate fails for sure... like:
#try{
if (delegate && [delegate respondsToSelector:#selector(...)])
[delegate callBackMethod];
}
#catch (NSException *e){
// if you get here then delegate is no longer valid regardless its reference is still valid
}
You should not have 10 individual ad views, that's wrong on so many levels. Instead you should have just one that you either move between individual views or - smarter - just keep on top.
You can for example add a view to a tabBarController.view and this will stay present even if you switch tabs. For views that you don't want the ad on you can simply hide it.

Knowing when -viewWillAppear triggered but -viewWillDisappear hasn't

Is there a way to know that a view controller is somewhere in the view controller lifecycle between -viewWillAppear and -viewWillDisappear?
I ask because I want to be damned sure that a method isn't fired when my view is either not on screen, or is about to disappear from the screen. When the view is about to disappear from the screen, certain objects which I cannot explicitly check at runtime may or may not be deallocated, so, obviously, interacting with them can lead to message sent to deallocated instance errors.
At present, I'm keeping track with a BOOL, like so:
- (void)viewWillAppear:(BOOL)animated {
isOnScreen = YES;
[super viewWillAppear:animated];
}
- (void)willWillDisappear:(BOOL)animated {
isOnScreen = NO;
[super viewWillAppear:animated];
}
And my method looks like this:
if (isOnScreen) [self doSomething];
Is there a simpler way to do this?
your way seems to be the simplest approach, if not the most robust. (simply checking if that instance of the view exists seems like the correct approach (if it hasn't been dealloced yet)).
I also don't REALLY understand the purpose of this, unless you have another view controller running methods that pertain to the view controller being showed that you are using the boolean for. In that case, its more a design problem than an upkeep problem.
Use viewDidDisappear
- (void)viewDidDisappear:(BOOL)animated {
// Do stuff that has to be done when this view is off screen.
}

Managing calls to objects that get deallocated when the view is backed out of

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.