I have a simple question. I was referring "Your first iOS application" document by apple.I found that the class has a property called myViewController:
#interface applicationClass
{
MyViewController *myViewController
}
Now to assign a memory to this pointer, the code shown is:
MyViewController *aViewController = [[MyViewController alloc]
initWithNibName:#"MyViewController" bundle:[NSBundle mainBundle]];
[self setMyViewController:aViewController];
[aViewController release];
My doubt here is, what is wrong if this is done as follows:
self.myViewController = [[MyViewController alloc]
initWithNibName:#"MyViewController" bundle:[NSBundle mainBundle]];
I cannot find this kind of instantiation where a property is assigned directly in many of the documents. Instead, a temporary memory is allocated and then it is retained by the property. Can anyone guide me if I am wrong ?
If you call methods which have alloc or copy in their names you get objects with a retain count of +1 and thus you are responsible for releasing it after use.
Now, if you assign to a property that is defined as #property(retain,...) ... then the #synthesize'd method takes care that retain and release are called correctly. So if you do self.foo = bar then the retain count of bar is increased by one.
Here, you got an object with retain count 1 from your alloc/init. Then you assign it to your property, and the retain count climbs to 2, which is too high (you only have one reference to it, not two). Two solutions: either the first code block you've cited, it stores the object in a variable and can then call release to immediately "fix" the retain count to 1 again. Or, you can do this:
self.myViewController = [[[MyViewController alloc] initWithNibName:#"MyViewController" bundle:[NSBundle mainBundle]] autorelease];
The autorelease will make sure that at a later time release is being called on the object thus again "fixing" the retain count. You cannot replace autorelease withrelease` here as the retain count would drop to 0 before it gets assigned to the property, thus it would get deallocated before it gets passed to the property.
With this you are retaining your object twice: in self. (if you have set retain in your property of couse) and in [MyViewController alloc]. You only want to retain the object once ....
Related
Usually when we push viewcontroller, we will create object for view controller, after that line we will push and release it.
But it gets crashed when we run in iOS5. So I retained the object through propery and declare it in interface as global. Now it is working fine. Will retaining viewcontroller occupy much memory? What is the difference between following two approches?
One:
MyViewCOntroller *obj = [[MyViewCOntroller alloc] init];
[self.navigationController pushViewController:obj Animated:YES];
[obj Release]
Two:
self.obj = [[MyViewCOntroller alloc] init];
[self.navigationController pushViewController:self.obj Animated:YES];
[self.obj Release]
The first one should be right and please detect for the crash reason again. It can't be crashed when you use the first one to push a new view controller.
As for the difference: in the second one, if you declare the obj as a var of self class and you don't use ARC, you take care of obj like the other instance vars. You just need to do release in the dealloc.
Generally speaking, you should not release property(self.obj) in methods except for dealloc.
The second code snippet should be replaced like this:
self.obj = [[MyViewCOntroller alloc] init];
[self.navigationController pushViewController:self.obj Animated:YES];
And add below one to your dealloc method:
self.obj = nil; // Property will release itself and set the point to nil
The first code snippet is OK, you alloced local instance and released it after used.
why aren't you init your view controller with nib?
SearchView *secondViewController = [[SearchView alloc] initWithNibName:#"SearchView" bundle:nil];
[self.navigationController pushViewController:secondViewController animated:YES];
[secondViewController release];
be careful with retaining any objects. You must be completely sure that you init it only onse and then release it. If you do so, you may not care about memory. The difference between your inits is: in 1st case you creating ner object. It is NOT retain, but, may be leak, I'm not shure. I think you should add autorelease. In 2nd case you have an object's property (probebly, retain?) in header. you must release it in dealloc method
Are you passing the View Controller to the new Object?
If yes, Are you releasing this View Controller property in the new View Controller's dealloc method? That would be a double release.
Example no. 2 would solve this problem because of the (retain) type property it may have in the old View Controller, would set its retain count to 2.
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).
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];
}
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.
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!