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.
Related
I am pushing my UIViewController subclass onto the navigation stack; however, since it is being retained by the navigationController, I 'release' my pointer to it after pushing it onto the stack so that when it is eventually popped the viewController will be dealloc-ed.
However, it's not working, the dealloc method for the viewController is never called. The code looks something like this:
MyViewController *newViewController =
[self.storyboard instantiateViewControllerWithIdentifier:#"foo"];
[self.navigationController pushViewController:newViewController animated:YES];
newViewController = nil;
Even in the following code, my newViewController is not being dealloc-ed:
MyViewController *newViewController =
[self.storyboard instantiateViewControllerWithIdentifier:#"foo"];
newViewController = nil;
From what I understand, under the new Automatic Reference Counting (ARC) system, objects are dealloc-ed once nothing points to it. I put a NSLog method in the dealloc method of the viewController that is being created, but it is never called.
What am I missing here?!
Thanks for reading my first post on stack overflow :)
**EDIT:**
I apologise. I tried the second piece of code wrapped in an autorelease pool and it was deallocated. I then tried it without the autorelease pool and it deallocated properly too. I don't know what happened last night.
(2nd edit: and now its stopped working again. fffffffuuuuuuuuuuuu)
In your first example, it would be very odd if the new view controller were deallocated, since you just pushed it onto the navigation controller, and thus the navigation controller has a strong reference to it. The object will only be deallocated when ALL strong references are gone.
In your second example, it's possible that the returned object still has pending releases from autorelease calls that were performed internal to the implementation of instantiateInitialViewController. Thus, until the current autorelease pool is drained, you wouldn't see it disappear. Further, it's possible that the UIStoryboard class caches the returned object as an optimization.
In short, even with ARC you still cannot assume that objects you receive from framework methods will be deallocated immediately when you're done with them, because you cannot guarantee that autorelease was not used in that method's implementation.
I have been developing iphone applications for around 3months now and theres a few things that stump me and i don't really have an idea how to work round them.
I have a navigation controller controlling the views in my application however every screen that is loaded, used then pushed back loses all the information as it seems to be reinstantiated... I believe this is a possible memory management issue?
But how to i create an app that navigates and retains all information in its views until the application is closed.
Thanks :)
Possible you didn't keep a reference to the view controller, the issue is for UIVIewController not to be released.
Make the view controller an ivar you will instanciate only one time when you push it on stack.
// in .h
MyViewController *mVC;
// in .m
// maybe when the user selects a row in a tableview
if(mVC == nil) {
// first time use, alloc/init
mVC = [[MyViewController ....];
}
// then push on the stack
[self.navigationController ....];
Of course don't forget to release it later.
In this part:
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];
You will probably have something like this... Instead, make your myViewController a class's property so you have a reference to it. And drop the [myViewController release]; statement.
Possibly your app is receiving a didReceiveMemoryWarning.
In such cases, when the super class is called, the framework does memory cleaning by unloading all the views that are not currently displayed. This could explain the behavior you are seeing.
To check it further, override didReceiveMemoryWarning in one of your view controllers or applicationDidReceiveMemoryWarning in your app delegate, and put a breakpoint in it. Don't forget to call [super...] appropriately, otherwise pretty soon your app will be killed. What you should see in this way is that the views do not disappear before hitting the breakpoint, and do disappear after that.
If the hypothesis is correct, you should find a way to save the state of your view in viewDidUnload and restore it in viewDidLoad. Look also at didReceiveMemoryWarning reference.
Try to save data in NSUserDefaults it its small or use plist or it its too small like 5-10 objects save in in some variable in appDelegate, and if its too large use sqlite and for saving something like images of files like xml use Document directory
The UINavigationController works like a stack: you push and pop UIViewControllers on it. That means when a UIViewController get popped, it will have its retain count decremented by 1, and if no other object holds a reference to it, it will be deallocated. You can avoid the UIViewControllers getting dealloced by keeping a reference to them yourself by calling -retain on the objects, for instance in your appDelegate.
You can use NSUserDefaults to save the states of the UIControls in the view.
So whenever u r loading a view, set the values to the controls so that it looks like it resume from the place where we left.
I recently changed my app to use a UINavigationController, I was using a UINavigationBar before, with cascade subView adding, which was a bit tenuous.
I'm facing a problem of memory usage. Leaks tool doesn't show any leak, but ViewControllers I create and add to the UINavigationController never seem to be released. So memory usage grows everytime I create a new VC and then press the NavigationController's back button.
I simply create and add my VCs this way:
DetailViewController* detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
// setups
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
The app never goes through ViewController's dealloc and viewDidUnload methods. Shouldn't these be called everytime I press the back button?
I've searched many tutorials and read Apple's memory management, but there's nothing about VC's lifetime in memory when using NavigationController.
Maybe you are not doing something wrong and instead you are facing something like this
In the Blog post it was the question whether we have to manually release IBOutlets or not. As it turns out we should. This was reproduceable in iOS 3.1.3 but I didn't test it in iOS 4.0 yet.
The second aproach is to override your view controllers retain and release method and print out the retain count. I had a simimlar problem, that some view controllers dealloc method did not called so I override this methods to see wether someone has still a retain on it. As it turns out it did.
Edit:
When I printed my retain count, it would sometimes reach ~98 caused from the framework, so thats not really to worry.
If your last retain count stays at 2 and the dealloc method won't be called, than there is someone that has still a retain on it.
In this case you should search on other places.
For example another problem I encountered during this same problem:
Sometimes I would use
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateUI) userInfo:nil repeats:YES]
to constantly update the UI. But what I forgot was, that the NSTimer will retain the target object (which was the ViewController). Because the NSTimer retained your view controller your dealloc will never be called because someone (NSTimer) has still a retain on it. So you have to make sure to invalidate the NSTimer BEFORE dealloc method to properly release the view controller.
Edit2 in response for a comment below:
A retain declaired property does as follows (exsample):
- (void)setTarget:(id)value {
if (value != target) {
[target release];
target = [value retain];
}
So it does first release your current self.target then retains the new value. Since you are assigning nil your target will be nil afterwards. Further info about Properties can be found in the Apple doc.
I have seen this as well. As you pointed out, I haven't seen anything definitive in the docs, but my belief is that they are retained in memory until memory is needed. It makes sense from a performance perspective as doing so allows the app to quickly navigate between the different views.
Bottom line is, I wouldn't worry about it. You could fire off some Low Memory Warnings in the Simulator and see if it actually releases your VCs.
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.
I am pushing a UIViewController onto a UINavigationController. This view controller immediately starts a download of an xml feed and then parses it. However, if you hit the back button before it is done downloading, and crashes with EXC_BAD_ACCESS. The line that is crashing it is in parserDidEndDocument and is this line:
if (self.delegate && [self.delegate conformsToProtocol:#protocol(ModelDelegate)]) [self.delegate modelDidFinishParsing:self];
I assume it is crashing because it is trying to access self.delegate which is not assigned anymore. How do I get around this?
Also, I would release the model object in the modelDidFinishParsing method. How would I release this model if it never reaches this method.
I set up objects to handle my downloads (and other asynchronous or long running tasks) in the AppDelegate, then trigger them as required from various controllers. That way they are owned and have persistence through the life of the application.
The best way to do this is to pass them to the viewControllers that will need them (rather than the viewController "expecting" the appDelegate to have such and such an object ready and waiting) - dependency injection.
These objects update my model in some way when they finish and if I need to, I use NSNotifications to announce they are done. This isolates me from the mess I used to get into trying to cancel or swap delegates in viewWillDisappear etc to avoid the kind of issues you are running into.
The reason your app is crashing is probably because NSURLConnection retains its delegate (so it can call back to it reliably) but objects that this delegate has weak references to have been deallocated.
Ie, in your case what self.delegate points to has probably been deallocated when the view controller is popped but the delegate property has not been cleared (set to nil).
The solution to your problem is to clear (nil) self.delegate at the appropriate time when the UIViewController subclass is being popped off the navigation stack.
Note: retaining delegates is not usual behaviour for Cocoa classes. In situations where it happens contrary to standard practice it is documented (see the NSURLConnection docs).