call a method after the object is released? - objective-c

i was reading this code, where setRegions is called after RootViewController is released : i find it a bit strange : does it mean RootViewController is still accessible, even if it was released and self.navigationController "owns" it ?
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Create the navigation and view controllers
RootViewController *rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.navigationController = aNavigationController;
[aNavigationController release];
[rootViewController release];
[rootViewController setRegions:[Region knownRegions]];
// Configure and display the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
Thanks

This is bad code.
An object should retain another object for as long as it cares about it. And in this case that rule is broken. The rootViewController is released, and then as you note, a method is called on it. This can be dangerous.
In this case, it works. This is because rootViewController is passed to another object, which retains it. So when we release it, it still has a positive retain count and is not deallocated. So our reference to it still works, and methods called on it work fine.
But lets say some implementation changed and initWithRootViewController: now no longer retained it's argument for some reason (an assumption you can't really make all the time). Suddenly this all crashes because rootViewController gets deallocated.
To fix this funk, you just need to move [rootViewController release]; to after the last useful reference of that object in this function. Your code then becomes more robust and more correct.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Create the navigation and view controllers
RootViewController *rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
[rootViewController setRegions:[Region knownRegions]];
UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.navigationController = aNavigationController;
// Release temporary objects since we've now sent them to other other objects
// which may or may not retain them (we don't really care which here)
[aNavigationController release];
[rootViewController release];
// Configure and display the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
Last thing to note: release and dealloc are very different things. release does not necessarily destroy objects. It simply decrements the retain count by one. And if that retain count ever gets to zero, only then is the object is deallocated. So this code works because a release happens but without triggering a dealloc.

The above is very dangerous code. It might happen to work, but it's just getting lucky. You should never access a variable after you have released it. In fact, it is best practice to immediately set variables to nil after releasing them if they don't immediately go out of scope. Some people only do this in Release mode, and so create a macro like:
#ifdef DEBUG
#define RELEASE(x) [x release];
#else
#define RELEASE(x) [x release]; x = nil;
#endif
The reason for this is to help catch bugs in debug mode (by having a crash rather than just a silent nil pointer), while being a bit safer in release mode.
But in any case, you should never access a variable after you've released it.

RootViewController *rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
(objectA created, retain count is 1, rootViewController points to it)
UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
(objectB created, retain count is 1, aNavigationController points to it)
(objectA retain count is 2 now, both rootViewController and some property in self.aNavigationController point to it)
self.navigationController = aNavigationController;
(objectB retain count is 2 now, both aNavigationController and self.navigationController point to it; assuming self.navigationController is a retain property)
[aNavigationController release];
(objectB retain count is 1 now, however, both aNavigationController and self.navigationController point to it)
[rootViewController release];
(objectA retain count is 1 now, however, both rootViewController and some property in self.aNavigationController point to it)
[rootViewController setRegions:[Region knownRegions]];
(use rootViewController to access objectA)
(This is not good)
Following is my recommended way:
RootViewController *rootViewController = [[[RootViewController alloc] initWithStyle:UITableViewStylePlain] autorelease];
[rootViewController setRegions:[Region knownRegions]];
UINavigationController *aNavigationController = [[[UINavigationController alloc] initWithRootViewController:rootViewController] autorelease];
self.navigationController = aNavigationController;

Related

Releasing Main View Controller (iOS)

I've searched for this on Apple's site and can only seem to find documentation using Storyboards or Navigation Controllers, neither of which I'm using. It's an incredibly straightforward question about memory management.
I created a completely blank application. In my AppDelegate's didFinishLaunchingWithOptions function I'm creating an instance of a View Controller which I've built. My design (which itself could be a problem) is to have a 1:1 relationship between View Controllers and Views.
So the main menu of my application, which is a launching pad for everything is in MenuViewController.h/m.
In .h:
MenuViewController *m;
In .m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
m = (MenuViewController *)[[MenuViewController alloc] init];
m.window = self.window;
[m doStuff]; // handful of functions, not actually called this
//[m release]; // doesn't make sense to me
return YES;
}
This is where I'm confused. I want this to exist for basically the entirety of the application life cycle. But I'm also under the impression that you should (in the scope of the current function) release anything you allocate. If you need it beyond that, you should retain it elsewhere first. Is this not true?
My fundamental question is...where should I be releasing this View Controller? Is there anything else I've said that seems out of whack?
The initialization is wrong. You don't assign a window to controller, you assign a controller to window:
// window creation code here
...
m = [[MenuViewController alloc] init];
[window setRootViewController:m]; // window does retain for m
[m release]; // so we release it here
[self.window makeKeyAndVisible];
return YES
}
You're right. Generally you should release anything you create in a scope. But in this case you want ownership of the view controller. In this case you need to release the object in the dealloc method of your app delegate:
- (void)dealloc {
[m release];
[super dealloc];
}
Alternatively you could define a #property for your view controller with retain flag and then do this:
MenuViewController *viewController = [[MenuViewController alloc] init];
self.m = viewController;
[viewController release];
Btw, you don't need to cast to MenuViewController in either case.
EDIT: I completely missed that you don't add your view controller to your window. Good point #Eimantas.

Analyzer warning about incorrect decrement of reference count

I just installed Xcode 4 and opened an earlier version of my app. The analyzer is reporting for this line:
[self.myViewControllerObject release];
incorrect decrement of the reference count of an object that is not owned at this point by the caller
I didn't enable ARC for my project.
When I analyze v2.0 of my app in Xcode 3.2.5, it doesn't show any potential error.
Header:
#class MyViewController;
MyViewController *myViewControllerObject;
#property ( nonatomic , retain ) MyViewController *myViewControllerObject;
Implementation:
#import "MyViewController.h"
#synthesize myViewControllerObject;
When a button is clicked I have:
TRY 1:
self.myViewControllerObject = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
[self.myViewControllerObject release];
TRY 2:
MyViewController *temp = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
self.myViewControllerObject = temp;
[temp release];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
[self.myViewControllerObject release];
TRY 3:
self.myViewControllerObject = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
In the dealloc method, I release it:
[self.myViewControllerObject release];
The warning comes from you calling release on a property through the accessor: when you do [self.myViewControllerObject release] you are actually calling the accessor method myViewControllerObject and then release on the return value. Since the name of the method does not begin with new, copy, or mutableCopy, you do not own the object it returns, hence you are not “allowed” to release it.
The solution is to never call release on the return value of that accessor, so basically your try #2 was fine:
MyViewController *temp = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
self.myViewControllerObject = temp;
[self.navigationController pushViewController:temp animated:YES];
[temp release];
But in dealloc do not use the accessor, rather:
[myViewControllerObject release];
If you need to release myViewController other than in dealloc, assign nil through the setter:
self.myViewControllerObject = nil;
Edit: For more on the subject, see Apple's Advanced Memory Management Guide.
The navigation controller manages the lifecycle of view controllers you add to it. In this way, when you push the view controller, you are passing 'ownership'. However, there's no reason you can't also maintain a reference to the view controller. Try this order:
self.myViewControllerObject = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.myViewControllerObject release];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
Alloc'd objects start with a retain count of one. Assigning a value to your 'retain' property increments the retain count by one. You should only keep one retain for the object, so you correctly release. But since you're starting at two, it's fine to release it before you pass it to the navigation controller. The compiler warning you see is just that - a warning, not always evidence you are doing something strictly wrong. It shouldn't matter if you pass it then release, but the order above should avoid it.
As I used the XCode 4 Beta version, the problem occurred. But when I tried it in the XCode 4 version, the Analyzer warning didn't occur to me. I Thank you to all whoever participated to help me.Thank you for your time.

navigationController and leaking

I used the instruments tool and it tells me I have no memory leaks. But I am struggling with the logic behind it. Why wouldn't this be leaky? It appears that some magic is happening behind the scenes and my AppDelegate is using my allocated navController to set the property of the self.navigationController. Without my initialization here, my property is nil.
Shouldn't I be forced to make these instance variables of the delegate and then release them in the dealloc? Why isn't this a leak? Or am I using the instruments tool wrong?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// two alloc calls which would imply I need a release
UINavigationController *navController = [[UINavigationController alloc] init];
UIViewController *calcController = [[CalculatorViewController alloc] init];
[navController pushViewController:calcController animated:YES];
[window addSubview:navController.view];
[window makeKeyAndVisible];
// can not release here, if i do, no views show up
// [navController release];
// [calcController release];
return YES;
}
...
// NOTE: No dealloc for navController or calcController
- (void)dealloc {
[window release];
[super dealloc];
}
Both navController and calcController exist for the life of the program. When the program terminates, everything is purged, so it doesn't matter. You do have a leak, but an irrelevant one.
Since your app delegate exists for the duration of the application run, you would only see a memory leak right before the application quits. So while there is technically a "leak" it only occurs right before the application is purged from memory.

Memory Management

How is method removeFromSuperView: really works?
I got a problem of memory bad access when I want to reinit the view
- (id)init {
if (!(self = [super init]))
return nil;
_mainView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
NSLog(#"retainCount :%d", [_mainView retainCount]);
UIButton *reInitButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f,0.0f,90.0f,35.0f)];
[reInitButton addTarget:self action:#selector(buttonDidTapped:) forControlEvents:UIControlEventTouchUpInside];
[[self view] addSubView:_mainView];
NSLog(#"retainCount :%d", [_mainView retainCount]);
[_mainView release];
NSLog(#"retainCount :%d", [_mainView retainCount]);
return self;
}
- (void)buttonDidTapped:(id)sender {
[_mainView removeFromSuperView]; //crash during second times press the button
NSLog(#"retainCount :%d", [_mainView retainCount]);
_mainView = [[UIView alloc] initWithFrame[[UIScreen mainScreen] bounds]];
[[self view] addSubView:_mainView];
NSLog(#"retainCount :%d", [_mainView retainCount]);
[_mainView release];
NSLog(#"retainCount :%d", [_mainView retainCount]);
}
I have NSLog every times there are any retain or alloc or release keyword. And the result is very weird.
//init
retainCount : 1
retainCount : 2
retainCount : 1
//1st time pressed button
retainCount : 1 //remove super view didn't decrease
retainCount : 2
retainCount : 1
//2nd time pressed button
retainCount : 0 //crash. Memory bad access
The weird thing is why it didn't crash on 1st time pressed??
I think your problem is here:
[_mainView release];
You've dropped your reference to _mainView, and yet, by my reading, that's a member variable that you'll keep around and continue to invoke methods on. That's not valid. Once you've called -release, you've essentially told the system you're not going to use that object again, and you can't do anything useful with a stale pointer to that object, like you do when you call -removeFromSuperView on it later.
If you want to continue to keep _mainView around and call code on it, you need to keep a reference. Perhaps you should move the release to your object's -dealloc method. Alternatively you could -release it in the button method and re-create a new view the next time you need to.
As a helpful tip, a lot of programmers like to reset objects to NULL (or nil in objC-speak) after releasing them, as a reminder that you can't use that object again. If you -release something, you'd better mean it.
Lastly, I suggest you Google the term "reference counting" and read up on it; it's a more generic idiom than the specifics of NSObject, and it is likely to be useful to think of the basics and how you might implement this in another language, like say, C. This will help you reason better about reference counted objects.
NEVER USE RETAINCOUNT. Sorry for putting that in caps, but I can't figure out for the life of me why people still use it. It's a faulty reference for memory management. Use instruments or similar instead.
You shouldn't be accessing _mainView at that point. This may be hard to explain, so bear with me. We're going to count, but not absolute retain count, just your code's claims on the object.
You allocate memory for an object and point at it with _mainView:
_mainView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
You have 1 claim of ownership to that object. When you add it as the subview of another view, that view likewise makes a claim of ownership, but that's not yours, it's the view's. The fact that it makes the object in _mainView stick around is an accident, and you shouldn't rely on it. Then you release the object:
[_mainView release];
You have relinquished your ownership claim -- you now have 0 claims, and you should no longer try to access this object. You don't own it. Again, the fact that it still exists because another view is using it, and the fact that you still have a pointer to it, are accidents*, and you should not rely on them.
When it comes time to handle your button press, then, you are accessing an object over which you have no ownership:
[_mainView removeFromSuperView];
and this causes a crash, which may not be expected, but it is not unreasonable. By letting your claims of ownership go to 0, you told the system "I don't need this object anymore. I'm not going to access it after this point. If it disappears, I will not be affected." In fact, though, you do need it to stay around, and you do need to access it.
What you should do, then, is move the line:
[_mainView release];
to inside the button action, right after the call to removeFromSuperview.
*The second of which could be avoided by setting _mainView = nil; after you release it, in this case, but that won't solve the greater problem.

UINavigationController crashes due to a NSArray variable

I push a view controller into current navigation controller. It works fine, except when I am getting out of the current view controller, it crashes.
MyTableView *newPage = [[MyTableView alloc] initWithNibName:#"table2" bundle:nil];
[[self navigationController] pushViewController:newPage animated:YES];
//[newPage release];
I comment out the last line to prevent crash. I read another post about variables being over released. In the newPage, I only have one variable (arrCellText), and is initialized in the initWithNibName
NSArray *temp = [[NSArray alloc] initWithObjects:#"string1", #"string2", #"string3", nil];
[self setArrCellText: temp];
[temp release];
I put the release in the dealloc
[arrCellText release];
If I comment out setting and release of arrCellText, it works fine too.
I must not have complete understanding of memory management, and I would like to understand this better. TIA
Where does the crash happen exactly?
First you can release 'newPage' after pushing it onto the navigationController (because it's retained there).
You might try to access anything from newPage after coming back. 'newPage' was released in the mean time and thus has some garbage value (but not nil).