I have been under the assumption for a while that viewDidUnload is always called when a controller is deallocated. Is this a correct assumption?
I've just been exploring some odd things, and set a breakpoint in my controller's viewDidUnload and it's dealloc. It appears that dealloc is called, but the viewDidUnload method is never called. I even added a self.view = nil to my dealloc and it still didn't seem to call it.
Does this mean that retained view objects I have been releasing in the viewDidUnload method also need to be released in my dealloc method to be sure they really go away?
I know there are many other questions on StackOverflow about viewDidUnload, but none specifically address this issue about duplication of release statements between the 2 methods.
A more concrete exmaple in a fresh project on the 3.1.2 SDK:
#implementation TestViewController
#synthesize label;
- (IBAction)push {
TestViewController *controller = [[[TestViewController alloc] initWithNibName:#"TestViewController" bundle:nil] autorelease];
[self.navigationController pushViewController:controller animated:YES];
}
- (void)viewDidUnload {
self.label = nil;
NSLog(#"viewDidUnload was called");
}
- (void)dealloc {
[super dealloc];
NSLog(#"label retain count: %i", [label retainCount]);
}
#end
My app delegate creates a simple navigation controller with one of these as it's root controller. When I tap the button linked to push 3 times, and then hit the back button three times, the following output is generated.
ViewDidUnloadTest[2887:207] label retain count: 2
ViewDidUnloadTest[2887:207] label retain count: 2
ViewDidUnloadTest[2887:207] label retain count: 2
Which is 2 higher that I would think it would be. Retained once by the view and once by the controller. But after the dealloc I would have expected the view to be gone releasing my label, and the controller to be gone calling viewDidUnload and releasing it. Although there may be an autorelease in there throwing off the count at this point.
But at least it's clear that viewDidUnload is not getting called at all, which contrary to this answer here: Are viewDidUnload and dealloc always called when tearing down a UIViewController?
Perhaps I should simply call [self viewDidUnload] in all my dealloc methods on controllers? Worse than can happen is that I set a property to nil twice, right?
Unless you need to break a retain cycle, you should generally only be releasing objects in your dealloc method. viewDidUnload is an exception; it is invoked in low memory situations and should be used to release anything that you can.
If you do need to release them anywhere else, then always set the reference to nil after the release. That'll protect your app from blowing up later (likely in dealloc).
Note that the documentation quite explicitly calls out that the view property will already be nil when viewDidUnload is called.
Related
There's a way to remove self as observer from all notifications:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Is there a similar way to remove self as a delegate of all objects that the viewController is a delegate of?
I want to place it in dealloc method in my prototype viewController. The reason is that sometimes when I dismiss a viewController, I get a crash with a message that a UIScrollView (or any of the official subclasses) called a method on its delegate (the viewController) that has since been deallocated. Since I consciously intend to call no such methods, I'd rather set self.delegate = nil. But since this is a prototype viewController, I don't have outlets to those scrollViews, hence my question.
No, there is no such way. Once we are done with the delegate, it requires to simply nil that reference.
Also before calling any delegate method make sure, to check nil condition as well method availability condition like:
if (_delegate != nil && [self.delegate respondsToSelector:#selector(myDelegateMethod:)]) {
// then only call that delegate method
[self.delegate myDelegateMethod:param];
}
Once you are done with all your delegate things nil your reference:
self.delegate = nil;
I don't know any built-in mechanism for it. I think, that the code that was responsible for making your object a delegate should be responsible for freeing it of this responsibility. You could provide some interface for it, but it depends on your code.
Also, since delegates are weak-referenced, they will be automatically set to nil when the delegate object is deleted, but it's not your case, I believe.
UPDATE:
Since in your case delegates don't seem to be declared as weak, I guess, the only option is to keep track of all objects that set your viewController as their delegate manually.
For instance:
In your ViewController:
#interface YourViewController
#property (nonatomic, strong) NSMutableDictionary *objectsThatDelegateSomethingToUs;
#end
#implementation YourViewController
-(void)makeDelegateOfObject:(id)obj withDelegatePropertyName:(NSString*)delegatePropertyName {
[self.objectsThatDelegateSomethingToUs setObject:delegatePropertyName forKey:obj];
}
-(void)dealloc {
for (id obj in self.objectsThatDelegateSomethingToUs.allKeys) {
[obj setValue:nil forKey:[self.objectsThatDelegateSomethingToUs valueForKey:obj]];
}
}
#end
Where you set your viewController as a delegate:
scrollView.delegate = viewController;
[viewController makeDelegateOfObject:scrollView withDelegatePropertyName:#"delegate"];
But, sadly, in this case you'll have to set your ViewController as a delegate programmatically. If delegate property is always called delegate, NSMutableArray should do the trick.
Actually, it's weird that scrollView keeps working when it's parent ViewController is deallocated. May be this is the real problem, and it can be fixed somehow, but, unfortunately, I can't give you any advice on it now, so my answer is trying to deal with the problem that you originally asked about. I recommend you to leave the question open for a while, may be someone else will be able to suggest a better solution.
P.S. Check out Logan's comment to your question. If all objects that use your ViewController as their delegate, are parts of ViewControllers' view hierarchy, then his solution is simpler and more elegant.
Placing this code in my superclass' dealloc method solved my crashes:
for (id view in self.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
[view setDelegate:nil];
}
}
I was considering doing this in my viewController where I have direct access to the UIScrollView. However, because ARC disallows calling [super dealloc], this does not allow the dealloc code in my superclass to be called.
When I release a UIViewController, the UIViewController is correctly destroyed but its dealloc method is not called.
If the UIViewController has been destroyed (it is nil in the console), then the retain count should be 0, consequently I expect the -(void)dealloc method to be called.
I've also checked for subclasses of my UIViewController, overriding dealloc without calling the superclass method, but this is not the case.
This is how I initialize it:
myViewController = [[MyViewController alloc] initWithViewController:statusPicker];
What could be the reason ?
Thanks
If the UIViewController has been destroyed (it is nil in the console),
then the retain count should be 0, consequently I expect the
-(void)dealloc method to be called.
Well, no. The fact of being nil, doesn't mean the UIViewController has been released. Put it simple, the pointer for the UIViewController, is now pointing to nil, but the memory where the UIViewController reside is still being occupied. Instead of being called dealloc, two things might be happening:
1) There is something else with a reference to the UIViewController (example: when you pushViewController B from A, A got a reference to B).
2) You got a memory leak.
Again I am basing my answer in what you said:
#Lefteris Automatic Referencing Counting = NO
Check that you haven't got any circular references. For instance if your view controller implements a delegate protocol, check that your code doesn't retain this delegate.
Because there are multiple situations in which I would want to pop a view controller from the navigation stack, I have one method that does it and it is called from three different places.
- (void)dismissSelfCon {
NSLog(#"dismiss");
[locationManager stopUpdatingHeading];
[locationManager stopUpdatingLocation];
locationManager.delegate = nil;
mapView.delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[[Trail_TrackerAppDelegate appDelegate] navCon] popViewControllerAnimated:YES];
}
In one situation, if the mapView has an annotation placed on it (I'm not sure if that is the defining characteristic, but I think it is), this method is called (and I am sure that it is called because #"dismiss" is printed to the console), but the location manager does not stop sending location updates! Also, because the delegate is not set to nil, the app crashes because the view controller receives respondsToSelector: from one of the objects of which it is a delegate.
How is this possible?
The most likely cause of this is that locationManager at this point is nil. First rule: always use accessors; don't directly access your ivars except in init and deallloc.
My suspicion from your description would be that this object (the one with dismissSelfCon) doesn't clear locationManager.delegate during dealloc, and that you're being deallocated without calling dismissSelfCon.
The solution was this:
The way I have my view controller set up (which is a little strange I know, and is something I'm trying to change/fix if you will see my question here: Can't allocate CLLocationManager), the CLLocationManager is being allocated, delegate set, etc in viewDidAppear. I present a MFMessageComposeViewController during the app, and when it gets dismissed, viewDidAppear is called again, re-allocating the CLLocationManager and causing my problem. With a little boolean magic, I adjusted the viewDidAppear code so that the CLLocationManager is only set up and allocated one time.
This may sound a newbie question ... but however I'm new to iOS development.
I've created UITabController object programmatically like this.
mTabBarController = [[UITabBarController alloc] init];
...
mTabBarController.viewControllers = [NSArray arrayWithArray:tabBarItems];
[tabBarItems release];
And releasing mTabBarController in dealloc like this.
- (void)dealloc {
[mTabBarController release];
}
Now my question : will I get a memory leak ? When I assign value t viewController the ref count of tabBarItems is still 1. When I release mTabBarController does it also release all its viewcontrollers ?
Yes, the tab controller owns an array of view controllers (and everything in an array is retained). You're not creating a leak as long as you properly release or autorelease the items you're adding to the tabBarItems array.
It really helps to think of object relationships as ownerships.
UITabBarController should never be placed as the child of another ViewController, so you will always have to release it in dealloc. If your TabBarController's view is the childview of your application's window, it's ok not to release it in dealloc since the only time dealloc will ever get called is when your program is closing, in which case your controller will be released anyways. However, some people like to release it in dealloc anyways just to keep their code consistent. What you're doing is fine.
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
AViewController * aView = [[AViewController alloc] initWithNibName:#"myNib" bundle:[NSBundle mainBundle]];
return vintagePriceRowView.view;
}
I know this code needs a release... somewhere. But where? If I set the allocated AviewController to autorelease, then touching elements within the view results in a "message sent to deallocated instance 0xfb5780"
The Leaks instrument is not actually showing a leak, but obviously Clang does not like the above code. I know Clang is not the gospel as far as determining everything that could possibly be wrong in your code, but in this case, it feels like it is probably right. I've allocated it, I need to release it.
Any ideas on what I am doing wrong?
For efficiency's sake, you should create the view that you'll be using in the footer before it's required. Maybe create it in viewDidLoad in your tableViewController and store it in a member variable.
Then in your viewForFooter method simply return the view you stored earlier.
Then in your tableViewController's dealloc method, release the view.
You need to hold on to it until it is not longer needed.
I suggest making a private property, set it to nil initially, and then lazy load the nib and assign the returned view to the property. Then in dealloc or in viewDidUnload simply set it to nil via the setter.
You will of course need to release or autorelease once you assigned it to the private property, since the setter will retain it for you.