I am going over my code and trying to get a handle on proper memory management. This code:
imageView = [[[UIImageView alloc] initWithImage:myImage] autorelease];
Causes my application to crash. I am using multiple view controllers within a nav bar controller. The app works fine: I can select a person from the first view controller (tableview) and it puts me to a list of that persons photos, I can then select a photo from that view controller (tableview) and move to a final view with a scrollview for viewing the photo. Once I hit back on the navbar the previous view loads (list of photos in a tableview) however the app crashes right before the row is deselected using this code:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if(RowSelected != nil)
{
[MainTableView deselectRowAtIndexPath:RowSelected animated:YES];
}
}
The selected row is stored when a the user clicks a row.
If I leave the code as:
imageView = [[UIImageView alloc] initWithImage:myImage];
The app runs fine. Am I doing something wrong? Do I not need to autorelease this?
Make sure you create that view in your view controller's -loadView or -viewDidLoad, not in its initializer. When the controller's view goes offscreen it usually gets released, which in turn releases its subviews; thus, you should not expect your reference to imageView to remain valid. If you for some reason need the image view to stay in memory even when the view controller's offscreen, then it's okay not to call -autorelease when you create it; just make sure to call [imageView release]; in your controller's -dealloc.
When you mark imageView for autorelease, it will be released the next time thru the run loop. If you still are referencing or using imageView somewhere then you are using a pointer to heap space that has been released. The heap space will get overwritten (sooner or later) and you will be referencing garbage and crash.
I think the correct solution is that imageView should be a property that is retained, but I'm not sure what you are doing with imageView so I'm only guesstimating here. If you add imageView to your view Controllers view it will retain it in the subviews array. Bottom line, it has to be retained by whoever is using it.
You would make imageView a retained property in the .h file:
#property (nonatomic, retain) UIImageView* imageView;
Then use the setter, which will retain it:
UIImageView* tmpIV = [[UIImageView alloc] initWithImage:myImage];
self.imageView = tmpIV; // copy the pointer, setter will retain it
[tmpIV release]; // release the original alloc
Don't forget the #synthesize to create the setter/getter.
The ImageView is in my .h. I am using this line it in my -viewDidLoad, i do not need the view once it goes offscreen:
imageView = [[[UIImageView alloc] initWithImage:myImage] autorelease];
I think that what i am doing is assigning the allocated UIImageView to my imageView and if i auto release then the retaincount will be off because my -dealloc releases? (corret me if im wrong)
if i check my -dealloc:
- (void)dealloc {
NSLog(#"%i", [photo retainCount]);
[photo release];
NSLog(#"%i", [imageView retainCount]);
[imageView release];
[super dealloc];
}
The retaincount of the imageView is 1 in NSLog, im guessing that when it goes to do [super dealloc] that it tries to take 1 more retain count off of the imageView when it is 0 already because of the [imageView release] ?
if i have the line: imageView = [[UIImageView alloc] initWithImage:myImage];
then the retain count is 2 in NSLog, 1 going into the [supper dealloc]
if i use kk6yb's method, then the app works no problems, however i think i shouldn't have to do it this way because using his way if i check my retain count in dealloc it is also 2 in the NSLog...
I am confused, i read alot about memory management yesterday to get a better grip on things, since i am releasing the imageView in the -dealloc i believe i do not need to autorelease on that line?
Is there a way to check memory leaks in xCode?
Thanks!
Related
I dynamically add a subview to another uiview;
in the subview ,click a button to go back by the following method:
[self removeFromSuperView ];
but after manny times added subview and removed it,my app could crash ,the log said it was killed .
I found that calling [self removeFromSuperView] didn't release self. So what's the best methods to releas it?
If you are retaining the UIView on creation (or adding it to an array) the retain count will increase. For example:
// Retain count is 1
UIView *myView = [[UIView alloc] initWithFrame:myFrame];
// Retain count is 2
[myParentView addSubview:myView];
// Retain count is 1 again
[myView removeFromSuperView];
In the above example you can autorelease the view if it is immediately added as a subView or release it in your dealloc if it is an iVar.
EDIT: (other reasons your view could be retained)
// Retain count +1
[myArray addObject:myView];
// Retained in the setter created by the #synthesize directive
#property(nonatomic, retain) UIView *myView;
Anything else that states in the documentation that the property is retained.
You should also be careful of creating objects in the loadView method of a VC, if you do make sure you release them, as they will be created again when the loadView is called. This will happen if you VC's view is unloaded and then reloaded.
u should release at first. counterpart of "alloc" is "release", and counterpart of "addSubview" is "removeFromSuperView":keep those balance.
add view:
UIView *myView = [[UIView alloc] initWithFrame:myFrame];
[myParentView addSubview:myView];
[myView release];
remove view (the view will clear up in memory after removeFromSuperView):
[myView removeFromSuperView];
Looks like you are adding retained view as a subview. Its parent view retains it once again.
So when you cell [self removeFromSuperView]; it gets release message from superView, but still have to be releasd by creator.
I am creating a Table View in viewDidAppear, because my app requires it to be created here for multiple reasons. However I notice that I get a memory leak when I analyze my application.
I thought using the instance variable _tableView is not a good idea in any other method besides init and dealloc. Should I just use autorelease, I want to make sure the table gets released at the appropriate time.
There is a property for my Table View.
#property (nonatomic, retain) UITableView *tableView;
And I create the Table View as such:
- (void)viewDidAppear:(BOOL)animated
{
self.tableView = [[UITableView alloc]
initWithFrame:CGRectMake(0, 0, 320, 300)
style:UITableViewStyleGrouped];
// Table View properties
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.view addSubview:self.tableView];
}
- (void)viewDidDisappear:(BOOL)animated
{
self.tableView = nil;
}
- (void)dealloc
{
[_tableView release];
}
self.tableView is a retain property, so your synthesized setter increases the retain count. But when you create a new UITableView using alloc/init, you also increase the retain count. So this line results in tableView being retained twice:
self.tableView = [[UITableView alloc]
initWithFrame:CGRectMake(0, 0, 320, 300)
style:UITableViewStyleGrouped];
Once when you use alloc/init, and once when you call the synthesized setter using self.tableView =.
You do not have two corresponding release calls.
The proper way of handling this would be to autorelease the alloc/init'd UITableView object that you set self.tableView to, like so:
self.tableView = [[[UITableView alloc]
initWithFrame:CGRectMake(0, 0, 320, 300)
style:UITableViewStyleGrouped]
autorelease];
The rest of your code will work as expected.
As an aside, you probably don't want to create your UITableView in viewDidAppear. By that time your view has already appeared (hence the name), and you likely wanted your UITableView before that. You also probably don't want the CPU expense of creating a new UITableView every time the view appears. You probably want to create a UITableView in viewDidLoad and then reuse it, unless there is a really good reason not to.
Autorelease. You get one retain for the alloc and another for the property assignment (assuming it's a retained property).
Your dealloc should handle one and an autorelease the other.
Setting property is good, i set the property for instance variable when you want to use that variable in another class, otherwise it is not necessary ,
Since you are creating in viewDidAppear it will create always when the view appears for that controller so better relesase the tableview in ViewDidDisappear method.
If you use autorelease we wont get when it will be released so some times accessing tableview will cause crash of application,
On top of what the other guys have said, I'd discourage you from doing alloc in viewDidAppear because that method can be called multiple times depending upon what else goes on (and, by the way, cause leaks unless you start checking for the existence of a tableView before alloc'ing another). Doing it in viewDidLoad seems to be much safer.
As an aside, I believe that your viewDidAppear, viewDidDisappear and dealloc should all be calling their super versions, too.
Should I release my subviews of UIView in the viewDidUnload when I have references to them as instance variables which retains them? I have build the GUI programmatically. I should do that right? Since both uiview and ivars retain then the objects would have 2 in retain-count, when view receives e.g. memory-warning then the UIView will release the subviews, but they still have +1 in retain count so I have to setself.myIvar = nil; In the viewDidUnload?
Thanks for your time.
You actually can release all retained subviews in viewDidUnload. But I used to do it in another way:
-(void) viewDidLoad {
someInstanceView1 = [[UIView alloc] init];
[self.view addSubview: someInstanceView1];
[someInstanceView1 release];
someInstanceView2 = [[UIView alloc] init];
[self.view addSubview: someInstanceView2];
[someInstanceView2 release];
//etc...
//you have a references to someInstanceView1 and someInstanceView2 with retained counts 1
}
In this case even if memory warning will arise, the view controller will remove all it's view subviews. And then call viewDidLoad again. So there would be no leaks and you don't need to care about releasing that ivars at all cause the only owner (it has the strong reference to the views) is the view controller's view and it will release them automatically.
I created a subclass of UITableView and wanted to use it with a UITableViewController to get the benefits of auto-scrolling when the keyboard appears. In the loadView for my view controller (derived from UITableViewController) I did the following:
- (void)loadView
{
[super loadView];
self.tableView = [[MyCustomTableView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame style:UITableViewStyleGrouped];
self.tableView.delegate = self;
self.tableView.dataSource = self;
}
Shouldn't this cause a leak with whatever self.tableView was referencing before the reassignment? I ran Build And Analyse and it didn't report it as a leak.
However, if I try to "be good"...
- (void)loadView
{
[super loadView];
[self.tableView release];
// reassign code...
}
...all sorts of nasty crashes happen when my view is displayed. Can anyone explain to me whether simple reassignment causes a leak and, if so, how to do this properly?
Thanks in advance.
No, it won't cause a leak because the setter method setTableView: (which is called when you assign a new value to the property) will automatically release the old value. This is what properties are for.
As Ole Begemann said, When you assign using dot notation, there's more happening than just a plain assignment. Since setTableView: is a "retain property", it looks something like this:
- (void)setTableView:(UITableView *)newTable
{
if(newTable != tableView) {
[tableView release];
tableView = [newTable retain];
}
}
So the problem in your "be good" method is the following.
If you just call release on the tableview object, you are not using the property setter, which means, you are not setting the tableView to nil. When you call self.tableView = [[MyCustomTableView alloc] init... the current tableView is pointing to a non-nil, already deallocated block of memory. This block of memory is the one that setTableView: will try to release again, raising a EXC_BAD_INSTRUCTION.
When you call release on some object the pointer of which may be reused in the future, always set it to nil afterward. This way you mark the pointer as deallocated and you won't risk to over-relese. Or just use a retain property, that will do the release for you when you assign it to nil.
Actually your example will cause a leak.
self.tableView = [[MyCustomTableView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame style:UITableViewStyleGrouped];
Setting self.tableView will increase retain by 1, and the alloc will increase retain by 1.
When you later on set self.tableView to another value, retain count is decreased by 1, leaving 1, thus your allocated MyCustomTableView will never be released.
The reason your loadView crashes is because the initial table view that is there before you alloc and add your own, is properly set up, with 1 retain. So when you release it, the system will try to release it too but by then it's already deallocated, and nastiness ensues.
The proper way to do it is to (auto)release the allocated MyCustomTableView:
self.tableView = [[[MyCustomTableView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame style:UITableViewStyleGrouped] autorelease];
That way, it will have 1 retain count only, which will drop to zero when you (re)set self.tableView.
- (void)launchSearch
{
EventsSearchViewController *searchController = [[EventsSearchViewController alloc] initWithNibName:#"EventsSearchView" bundle:nil];
[self.navigationController pushViewController:searchController animated:YES];
//[searchController release];
}
Notice the [searchController release] is commented out. I've understood that pushing searchController onto the navigation controller retains it, and I should release it from my code. I did just alloc/init it, after all, and if I don't free it, it'll leak.
With that line commented out, navigation works great. With it NOT commented out, I can navigate INTO this view okay, but coming back UP a level crashes with a *** -[CFArray release]: message sent to deallocated instance 0x443a9e0 error.
What's happening here? Is the NavigationController releasing it for me somehow when it goes out of view?
The boilerplate that comes on a UINavigationController template in XCode has the newly-pushed controller getting released. But when I do it, it fails.
---EDIT----
So this morning, I sit down, and it works. No real clue why. Sigh.
Taking what I thought I learned, then, and applying it to another piece of this same controller, I did the following. Yesterday I had this code WITHOUT the release statements, because it didn't work right with them. So this morning I added them to create:
- (IBAction)switchView:(id)sender
{
UISegmentedControl *seg = (UISegmentedControl *)sender;
NSInteger choice = [seg selectedSegmentIndex];
NSArray *array = [mainView subviews];
UIView *oldView = [array objectAtIndex:0];
[oldView removeFromSuperview];
if (choice == 0) {
tableController = [[EventsTableViewController alloc]
initWithNibName:#"EventsTableView" bundle:nil];
[mainView addSubview:tableController.view];
[tableController release];
}
if (choice == 1) {
calendarController = [[EventsCalendarViewController alloc]
initWithNibName:#"EventsCalendarView" bundle:nil];
[mainView addSubview:calendarController.view];
[calendarController release];
}
if (choice == 2) {
mapController = [[EventsMapViewController alloc]
initWithNibName:#"EventsMapView" bundle:nil];
[mainView addSubview:mapController.view];
[mapController release];
}
}
With it set up like this, when I come onto the view, the main portal of my view is filled with the EventsTableViewController's view, I can click to mapView and calendarView, but when I go BACK to tableView, I die because the table delegate methods are being called on a deallocated instance.
So I went and made all of these controllers into synthesized properties, so I can release them in [dealloc]. Which seems to work, but the real question is why adding these views as subviews doesn't retain them, passing ownership to the new view it's a member of, allowing me to release them right there?
Wow, guys. Thanks so much for all your responses--tragically I sent you all on a horrible goosechase.
My NavigationView navigates a NSArray of Event objects (local arts events). My table view drills down to a detail view.
My detail view has in it the following:
-(void)loadEvent:(Event *)event
{
thisEvent = event;
}
And I call that from my table view before pushing the detail view onto the nav stack. thisEvent is a synthesized property of type Event, and so since it's synthesized, I dutifully release'd it in [dealloc].
Many of you already see the problem. Backing up to the table view, when I scroll such that the one I just saw is displayed, it builds the custom table row, and so it goes to get the title property from the Event.... which I just released inside the detail controller. Boom.
I added a retain to that loadEvent: method above and the crashes, they are gone.
NONE of this was really about the views getting retained and released by the navcontroller. It was about accidentally over-releasing the data objects I'm navigating. Part of what had me discover this was, I NSLogged myself in the [dealloc] of each of these view controllers, and I can now see they're behaving exactly as they should.
Thanks! I SO love this site.
I'd guess the fault lies in EventsSearchViewController's init. Is it returning an autoreleased self by mistake ?
Looks like EventsSearchViewController is allocating an array and then over-releasing it, with one of the releases probably in its dealloc.
If you comment out the release, your EventsSearchViewController is never deallocated (it leaks). So, errors that occur as a result of its own dealloc will be masked since that method won't be called. Releasing the controller is the right thing, but you have another bug in the controller itself that only appears at dealloc time.
It could also be that dealloc is releasing an autoreleased array, so you may not have two explicit release calls in your code. But it looks very much like releasing something in dealloc that's causing the problem.