Placing elements into the UITableView footer, when to release them? - objective-c

- (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.

Related

Modal view using UIModalPresentationFormSheet stays in memory (ARC)

I'm presenting a modal view with the UIModalPresentationFormSheet presentation style.
MPMediaItemCollection *albumItem = [self.albums objectAtIndex:index];
AlbumViewController *destination = [[AlbumViewController alloc] initWithAlbum:albumItem];
destination.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:destination animated:NO];
When the user taps outside the modal view, it is closed. However, it's still in memory, it's not released.
What's the correct way to deal with this?
Edit: is seems there's something inside AlbumViewController that's not being released properly. A bug on my end.
Yes, this is correct. ARC will take care of deallocating the memory when it is needed.
From here:
ARC deallocs any object to which there are no more strong references. So to dealloc something, simply set all the variables pointing to it to nil and make sure the object is not involved in any circular reference.

MKMapView still sending messages to delegate after it's superview has been de-alloc'ed

EDIT: changed the title. I didn't know it at the time but this is a duplicate of Why am I crashing after MKMapView is freed if I'm no longer using it?
This question is similar to Why is object not dealloc'ed when using ARC + NSZombieEnabled but different enough that I thought it worth throwing out there in case anyone understands and can explain to me what is happening. The other question may be an XCode bug so I presume this could be similar.
Scenario:
RootViewController has a tableView displaying a bunch of items
Selecting a cell presents a modal detailViewController containing another tableView
One of the table cells in detailViewController contains an MKMapView showing the location of the item
mapView.delegate = detailViewController
Dismiss the modal detailViewController
Soon after this, the app crashes b/c the MKMapView sends mapView:viewForAnnotation: to the now dealloc'ed detailViewController. This crash repro'ed on a users device with an ad-hoc distribution build so the issue has nothing to do with NSZombieEnabled.
I was able to resolve the crash by adding:
_mapView.delegate = nil;
to the dealloc method of the tableViewCell containing the mapView.
QUESTION: why is it necessary to nil the delegate when the cell is dealloc'ed? It seems like the mapView should be dealloc'ed by ARC when the cell is dealloc'ed leaving this unnecessary. It is good practice to nil delegates but I didn't think it would be required in this case.
EDIT: all subviews of both detailViewController and the UITableViewCells are declared as (nonatomic, strong) properties ala:
#property (nonatomic, strong) MKMapView * mapView;
EDIT 2: Guess I need to get better at reading the docs. #fluchtpunkt is correct. Here's the relevant info from the MKMapView documentation:
Before releasing an MKMapView object for which you have set a
delegate, remember to set that object’s delegate property to nil. One
place you can do this is in the dealloc method where you dispose of
the map view.
MKMapView is not compiled with ARC and because of that the property for delegate is still declared as assign instead of weak.
From the MKMapView documentation:
#property(nonatomic, assign) id<MKMapViewDelegate> delegate
And from the Transitioning to ARC Release Notes:
You may implement a dealloc method if you need to manage resources other than releasing instance variables. You do not have to (indeed you cannot) release instance variables, but you may need to invoke [systemClassInstance setDelegate:nil] on system classes and other code that isn’t compiled using ARC.
For delegates of system classes (NS*, UI*) you have to use the "old" rule of setting delegates to nil when you deallocate the delegate object.
so add a dealloc method to your detailViewController
- (void)dealloc {
self.mapView.delegate = nil;
}
While it's true that the delegates for such classes should be explicitly set to nil, doing it in dealloc is already too late. You already lost your reference to the mapview during viewDidUnload. You should do the self.mapView.delegate = nil BEFORE viewDidUnload (so probably viewWillDisappear or viewDidDisappear)
From my experience, only MKMapView and UIWebView behave this way.

Can't set CLLocationManager delegate to nil

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.

How correctly release UITabController

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.

Release in viewDidUnload and dealloc both?

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.