iPhone question regarding memory management - iphone

Here's a hypothetical getter:
- (DetailViewController *)detailController
{
if (detailController == nil) {
DetailViewController *controller = [[DetailViewController alloc] initWithNibName:#"Detail" bundle:nil];
self.detailController = controller;
[controller release];
}
return detailController;
}
Then the code that calls it looks something such as:
- (void)loadControllerOrSomething
{
DetailViewcontroller *controller = self.detailController;
[navigationController doSomethingWith:controller];
}
My question regarding memory management is the following. If I let *controller go out of scope here, in loadControllerOrSomething, am I leaking memory? Should I be doing a controller = nil after working with navigationController?

No you won't leak anything.
There was no additional retain added to the controller before it was returned, and no retain added when it was received. If you need to guarantee it's existence outside the scope of your functions you should call retain on it, and release when done.
This is generally how memory management works in Cocoa. When an object is returned by a function you have no ownership rights. Unless you call 'retain' it will be deleted by when it reaches the end of its natural lifecycle which could be the next frame, the next minute or so on.
The exception are functions with names containing 'alloc' or 'copy' and that return a new object. You are responsible for calling release when they are no longer needed.

Related

Sending release to an NSArray of UIViewControllers?

If I have an NSArray of UIViewControllers and send release to the array, will that call viewDidUnload or dealloc for each of the UIViewControllers? or neither?
Here's what I'm doing:
- (void) viewDidLoad {
UIViewController* profileController = [[ProfileController alloc] init];
..........
//all the other controllers get allocated same way
self.viewControllers = [[NSMutableArray alloc] initWithObjects: profileController, dietController, exerciseController, progressController, friendsController, nil];
[profileController release];
//other controllers get released same way ....
}
- (void) dealloc {
[viewControllers release];
NSLog("DEALLOC!");
//I know dealloc is being called
//what happens to the view controllers?
}
I put a breakpoint in the viewDidUnload and dealloc methods for each of these view controllers, and they don't get called.
As pgb mentioned, you are confusing the two concepts of memory management and view controller life cycle. viewDidUnload will be called whenever the view controllers view is unloaded, which of course will only happen if the view is loaded. In the absence of displaying any of the view controllers you should not expect to see viewDidUnload called at all.
You very much should expect to see dealloc called though!
Your problem is with the initialisation of the viewControllers array:
self.viewControllers = [[NSMutableArray alloc]
initWithObjects: profileController,
dietController,
exerciseController,
progressController,
friendsController,
nil];
Assuming that viewControllers is a property that is using the retain attribute, this will leak the array, and thus all the array's contents. The problem is that you have alloc'd a mutable array (thus retain count = 1), then you are assigning it to the viewControllers property which will increment the retain count.
In your dealloc method you (correctly) release the array, but this will merely decrement the retain count to one.
My suggested fix is to add autorelease to the above code:
self.viewControllers = [[[NSMutableArray alloc]
initWithObjects: profileController,
dietController,
exerciseController,
progressController,
friendsController,
nil] autorelease];
After making this change you should expect to see dealloc being called on the view controllers. You should also expect to see viewDidUnload called as the views of these view controllers are unloaded (for example, as they are popped off of the stack in a navigation-controller based application).
I see two issues here, one for each of the methods not called:
viewDidUnload won't be called if the view associated with the UIViewController wasn't loaded. You are basically mixing two concepts: memory management (and object lifecycle) vs. view controller life cycle. While you can see some parallelism between the two, they are not necessarily related.
Are you, at any point, pushing the viewController so its view is visible? If you are not, then viewDidUnload will certainly not be called (as won't viewDidLoad).
As for dealloc, if it's not called after you release the object (and you think the object should be dealloced from memory) it's because you have a memory leak. In your code, I can easily see the memory leak on the initialization code:
UIViewController* profileController = [[ProfileController alloc] init];
..........
//all the other controllers get allocated same way
self.viewControllers = [[NSMutableArray alloc]
initWithObjects: profileController,
dietController,
exerciseController,
progressController,
friendsController,
nil];
profileController gets its retainCount bumped on the first line, when you alloc it. Later, when you add it to a NSMutableArray, the array will retain it, bumping its retainCount once again. To balance that, you would need to release twice, but you are only releaseing once, on your dealloc method. To solve this issue, I would change your initialization to:
UIViewController* profileController = [[[ProfileController alloc] init] autorelease];
..........
//all the other controllers get allocated same way
self.viewControllers = [[NSMutableArray alloc]
initWithObjects: profileController,
dietController,
exerciseController,
progressController,
friendsController,
nil];
which will add the extra release you are missing and balance the retain release calls on your view controllers.
When you release the array, that does not dealloc it, that gives it permission to be dealloced if there are no other retains.
Similarly, if the array is dealloced, each element inside is released, but the objects may be retained elsewhere and hence are not guaranteed to be dealloced immediately.
In general, an object like a view or view controller is retained by the UI logic while it's actively being presented, so releasing your retains on it, either directly or through the array, will not cause it to be dealloced if it's being displayed (or, eg, in the stack of a navigation controller).

application crashed after dismiss presentModalViewController

i have a problem when going to another controller with the following buttons
-(IBAction)art:(id)sender{
TestYourInfoViewController *test = [[TestYourInfoViewController alloc]
initWithNibName:#"TestYourInfoViewController" bundle:[NSBundle mainBundle]];
test.questionType = #"art";
testYourInfoViewC = test;
[testYourInfoViewC setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self.navigationController presentModalViewController:testYourInfoViewC animated:YES ];
[test release];
}
and when i go back with the following
-(IBAction)back:(id)sender{
[[self parentViewController] dismissModalViewControllerAnimated:YES];
}
it crash the application with no stacktrace.. what is the wrong with that please.
Is testYourInfoViewC defined in the header as a retained #property? If so, you should always be referring to it using self and dot notation.
- (IBAction)art:(id)sender
{
TestYourInfoViewController *test = [[TestYourInfoViewController alloc]
initWithNibName:#"TestYourInfoViewController" bundle:[NSBundle mainBundle]];
test.questionType = #"art";
self.testYourInfoViewC = test;
[self.testYourInfoViewC setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self.navigationController presentModalViewController:self.testYourInfoViewC animated:YES ];
[test release];
}
When you create a retained #property and #synthesize it, a setter is being created that handles the memory management involved in retaining the new object and releasing the old, but by assigning test to testYourInfoViewC you're bypassing that synthesized setter.
Let's step through this here. You've created test using alloc/init, thus setting its retainCount to 1. Next you've assigned testYourInfoViewC to test. No change in the retain count, testYourInfoViewC is now simply pointing to the same object as test instead of retaining a copy for itself.
Now when you call release on test that retain count returns to 0 and the object is deallocated. Your instance of TestYourInfoViewController is completely gone and testYourInfoViewC is now left flapping in the wind. When attempting to dismiss it, the parentViewController is going to attempt to send some messages to that object behind the scenes, like -viewWillDisappear:, -viewDidDisappear: etc.
Edit: Here's how I handle this kind of situation in my projects. I override the getter for the property and determine whether it needs to be created or not. This way I can call the property anywhere in my code and I can be assured that if its not created it will be allocated, initialized and setup just in time.
- (TestYourInfoViewController *)testYourInfoViewC
{
if (!testYourInfoViewC)
{
testYourInfoViewC = [[TestYourInfoViewController alloc] init]; // This will work because the .xib and class name are identical.
[testYourInfoViewC setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
}
return testYourInfoViewC;
}
After you've setup the getter to provide lazy instantiation, your -art: method will look like this...
- (IBAction)art:(id)sender
{
[self.navigationController presentModalViewController:self.testYourInfoViewC animated:YES];
}

Where is the leak?

The Instruments tells me that this piece of code has a leak. However, I am pretty sure I have released it later on. Can anyone tell me what's going on here?
- (void) addReminderEntry{
DataEntryController* item = [[DataEntryController alloc] initWithEntryType:REMINDER]; // it says that the leak was instantiated here
item.delegate = self;
[[self navigationController] pushViewController:item animated:YES];
[item setEditing:YES animated:YES];
[item release];// this is the place I release it
}
Thanks
More than likely it has to do with something not being released within the DataEntryController class. Make sure you are releasing all your properties/etc within that class.
Leaks tells you only where memory was allocated, what it cannot tell you is where to put the code that should have released it to start with!
So this is saying that you made a view controller, and it was still around in memory after you finished with it. Yes you release the VC in that code, but only after you present it - which means the navigation controller has retained it, and possibly other things. It only gets deallocated when the final release is called.
The main culprit for view controllers not being released is usually having the view controller set itself as a delegate for something it retains, and then not undoing that when the view controller goes offscreen. If your view controller is a delegate of something that retains it, it's never going to be deallocated.
It turns out that this is caused by this constructor:
- (DataEntryController*) initWithEntryType:(DataType) eType{
DataEntryController* item = [[DataEntryController alloc] init];//<- here
item.entryType = eType;
item.allowEdit = YES;
return item;
}
Apparently iOS adds retain 1 to each constructor with an initial 'init'.
It works fine after switching to:
DataEntryController* item = [super init];

Won't reassigning a UITableViewController's tableView property cause a leak?

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.

differing methods of alloc / init / retaining an object in objective-c

In several pieces of sample objective-c code I've seen people create new objects like this:
RootViewController *viewController = [[RootViewController alloc] init];
self.rootViewController = viewController; // self.rootViewController is a (nonatomic,retain) synthesized property
[viewController release];
[window addSubview: [self.rootViewController view]];
Is that any different "behind the scenes" than doing it like this instead?
self.rootViewController = [[RootViewController alloc] init];
[window addSubview: [self.rootViewController view]];
Edit: later I release rootViewController in my dealloc method:
-(void) dealloc {
[rootViewController release];
[super dealloc];
}
I'm just curious about the syntax between the two. One seems to create a temporary viewController object, and the other allocs/inits directly to self.rootViewController.
Seems a bit more straightforward/streamlined that way so I'm wondering why anyone would opt for the first method.
Thanks!
Answer:
So it looks like when using the second method it causes a memory leak because the rootViewController object would actually have a retain count of 2. (See my answer below with a link to a post that thoroughly explains it.) Thanks everyone!
This line:
self.rootViewController = viewController
is identical to this line:
[self setRootViewController:viewController];
The typical setX call will release a previously retained value of X and assign a new retained value of X.
id old = X;
X = [new retain];
[old release];
But it can do anything else as well.
If you know there is not a current value to release (in init) and the setter function does nothing but retain the new value (synthesized), you can replace:
RootViewController *viewController = [[RootViewController alloc] init];
self.rootViewController = viewController; // self.rootViewController is a (nonatomic,retain) synthesized property
[viewController release];
With:
rootViewController = [[RootViewController alloc] init];
which does not use self. and so directly assigns a value instead of calling a setter method. Using the setter method is generally preferred.
To consolidate the lines as you want, you can also switch to this:
self.rootViewController = [[[RootViewController alloc] init] autorelease];
Which uses the setter method, releases the allocated instance, and fits on one line.
Your second code snipppet doesn't release the object you created.
self.rootViewController is a property that retains the object. So you're creating an object using alloc, and then the setter method for self.rootViewController will retain it also. You should release all objects that you allocated. Always
What happens is:
You create an object of type RootViewController using alloc, so the retain count becomes 1
The object is assigned to a property which also retains the object. So the retain count becomes 2
When self is deallocated later on, the retained RootViewController object will be released, so its retain count becomes 1 again.
Result: you have a memory leak.
In the second code snippet there will be a memory leak because you omit the [self.rootViewController release] line.
In more detail:
When you call [[RootViewController alloc] init], the retain count of the created object will be 1.
Calling self.rootViewController = viewController will increase it to 2 because the self.rootViewController property is retaining.
Calling [viewController release] decreases the retain count to 1
So if you call self.rootViewController = nil later, then the retain count will be 0 (because the generated setter calls a release method), so the object will be deallocated.
In the second case, the retain count will be 1 when you call self.rootViewController = nil, so the object will never be released.
If you want a more compact solution, try this:
self.rootViewController = [[[RootViewController alloc] init] autorelease];
[window addSubview: [self.rootViewController view]];
Please read and understand the Cocoa Memory MAnagement Rules. You obtained the object with alloc, therefore you have ownership of it. By "you" I mean the executing code in the current scope. You need to either release or autorelease it to relinquish ownership. Assigning the object to something else (in this case a property) does not absolve you of your responsibility to relinquish ownership when done.
Your second example leaks. You can fix it thusly:
self.rootViewController = [[[RootViewController alloc] init] autorelease];
In Mac OS X, whether you use that or your first example is a just a matter of preferred style. With the iPhone, the first example is generally preferred because it doesn't involve adding an object to the autorelease pool. Having said that, since a view controller will probably need to stick around beyond the end of the current event, it makes little difference.
By the way, two answers have mentioned retain counts and they are both correct, but it is better to avoid thinking about retain counts at all and think only in terms of ownership. Retain counts are an implementation detail.
Thank you guys for your answers! I was looking for less "read the memory management guide" type of answers and more of a "dumbed down," "here's the background and difference between these two methods laid out for you" type of answer. I'm familiar with object ownership, retain counts, etc. but I didn't realize WHY using **self.**rootViewController was important, and WHY the second code snippet was leaking... and literally the "behind the scenes" difference between the two. So I came across this post which was I feel was the exact answer I was looking for... (I hope it's accurate!) :) but anyway I'm giving the ole check mark to Philippe because he answered first. I just didn't understand his answer until reading the following post...
http://www.iphonedevsdk.com/forum/iphone-sdk-tutorials/7295-getters-setters-properties-newbie.html
This part was key for me:
So what would happen if you wrote:
self.obj = [[SomeObject alloc] init];
In this case, you're holding onto an object with a retain count of two - the first count comes from the "alloc" and the second one is added by the setter.
To release this variable, you'd have to do something like this:
[obj release];
self.obj = newValue;
so that "release" gets called twice on the object. If you omit the extra "release", then when the pointer gets overwritten the object will still be floating around with a retain count of one, and thus doesn't get deallocated. Instant memory leak.
Thanks again!