I haven't done any iOS development since iOS 3, so my memory is a little hazy (though memory management was never anything I struggled with and my mind is quite clear on that).
I'm starting a new project and don't understand why the skeleton code is structured the way it is:
- (void)dealloc
{
[_window release];
[super dealloc];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc]
initWithFrame:[[UIScreen mainScreen] bounds]]
autorelease];
// ... snip ...
}
Why would the window object be autoreleased? I'm pretty sure it never used to be this way in older iOS versions.
Where does _window come from? Is this just another way to access [self window]?
I'd have written this as:
- (void)dealloc
{
[self.window release];
[super dealloc];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc]
initWithFrame:[[UIScreen mainScreen] bounds]];
// ... snip ...
}
It was always drummed into me never to release an autoreleased object, and in fact doing so usually results in a segmentation fault.
In you second example you are leaking the window object since, the alloc will give the object a retain count of 1, the you assign it _window via the property which will also retain the object assigned to it.
It's true you should not release an autorelease object, but in the dealloc you are releasing the iVar for the window property. You should always release any property that is declared as either retain or strong. (although not when using ARC).
The _window is now automatically generated as the iVar for the property window.
There some believes that you should not use self. properties in init or dealloc.
Thus the way I do it is:
[_window release], _window = nil;
This wil set the _window to nil after releasing it, making sure that if any other thread might want to use this it will be calling on nil. Which could prevent a crash but could alo create some weird behavior. This is totally up to you.
You should move to ARC, which is a complier option to add release/autolease at compiletime fro you. There is no need to added these your self if you set the property correctly when using ARC.
So I figured this out (mostly). The #property declaration has the strong attribute. Apparently this is new to ARC (which I'm not actually using) and is otherwise just an alias for retain, so self.window = [ ... ] retains the object, hence the autorelease.
Still not clear on the _window variable, but I assume it is just a shortcut.
Check what the window property memory descriptor is like. I assume it's strong/retain in which case when you set the window property, it's value is retained thus needs to be released in dealloc.
Following your code path.
[UIWindow alloc] = retainCount of 1.
autoreleasing = retainCount of 0
setting the self.window bumps the retainCount which now = 1 and is alive until...
in dealloc you release it, thus retainCount = 0 and the object is deleted
You probably missed out that in later iOS SDKs, auto synthesized properties automatically create instance variables with underscore prefixes. So you could also do self.window = nil in your dealloc.
1. Why would the window object be autoreleased? I'm pretty sure it never used to be this way in older iOS versions.
If window object is not autoreleased, then it results in leak. Since you are using
self.window = [[[UIWindow alloc] ....]autorelease];
self. syntax allows setter function to get called, which retains the object once. So the actual window object which we alloced using [UIWindow alloc] should be released, hence autorelease. The retain using self. syntax is conceptually released in dealloc. So for one alloc plus one retain we are releasing twice.
Where does _window come from? Is this just another way to access
[self window].
Well this question is already discussed here
Related
My application crashes when simulating Memory warning in simulator with error:
[UINavigationController retain]: message sent to deallocated instance
I'm using ARC.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
_window = window;
[self startWithFlash];
return YES;
}
- (void)startWithFlash
{
[self.window.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
__weak typeof (self) weakSelf = self;
WPRSplashViewController *splashViewController = [[WPRSplashViewController alloc] initWithNibName:#"WPRSplashView" bundle:nil doneCallback:^{
[weakSelf startApplication];
}];
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:splashViewController];
[self.window makeKeyAndVisible];
}
- (void)startApplication
{
WPRMainViewController *mainViewController = [[WPRMainViewController alloc] init];
UINavigationController * controller = [[UINavigationController alloc] initWithRootViewController:mainViewController];
self.menuController = [[PHMenuViewController alloc] initWithRootViewController:controller
atIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
self.window.rootViewController = self.menuController;
[self.window makeKeyAndVisible];
}
This happens when somewhere in the app I call:
[((WPRAppDelegate*) [UIApplication sharedApplication].delegate) startWithFlash];
And right after that simulating memory warning.
Running Profile Tools with NSZombie enabled I get the following trace:
This is not the only place with such crash. In every place where I use UINavigationController as wrapper for view controller and I present it as modal view, after simulating memory warning I get this crash.
I had very similar issue in other place for which I've posted here another question but did not find a reasonable solution: Similar issue
Finally after days of investigations I've found a reason to all these crashes including the one described in "iOS memory warning sent to deallocated UIViewController"
The problem came out from PHAirViewController project. I still did not find out a real reason, but simply commenting out - (void)dealloc function in PHAirViewController.m file did the magic.
The main headache I got when was running Instruments to detect NSZombies. All traces were pointing to system classes like UINavigationController, UIImagePickerViewController etc... Due to this I started disabling ARC for parent controllers. In some places it helped but in some it didn't.
After a lot of voodoo I found out that every class (including system classes) was implementing UIViewCOntroller(PHAirViewController) Category and though - (void)dealloc function was always called on dismissing them.
Now the only thing left is to understand why this function is generating NSZombies. The interesting thing is that just commenting its content (which have only one line: self.phSwipeHandler = nil) does not have the same effect.
Quickfix: insert assert([NSThread isMainThread]); to various places in your code where you access appDelegate.window.rootViewController. This should be done for write- and for read-accesses to the property! This will reveal the culprit. appDelegate.window.rootViewController must not be accessed from any other thread than the main thread.
Generally, there are these reasons why this may happen:
You are using __unsafe_unretained variables.
You are using an unsafe_unretained property.
You are using non-ARC
You are accessing the same variable from different threads at the same time
You are accessing the same nonatomic, non-weak property from different threads at the same time
The fix for 1 and 2 is simple: Just don't use unsafe_unretained anymore.
The fix for 3 is: use ARC instead.
The fix for 4 and 5: use atomic properties instead, or synchronize access to your iVars. (Note that you must not access iVars from atomic properties directly as this breaks the atomicity.) Alternatively, use the property only from one thread, e.g. only from the main thread.
In your example, I assume that issue #5 applies. The culprit should be some non-main-thread accessing rootViewController from UIWindow.
It is likely you are using an assign or __unsafe_unretained property somewhere in your code. Delegates should always be of type weak, so that the reference to the delegate object is nil'ed out on deallocation.
Also, calling:
[((WPRAppDelegate*) [UIApplication sharedApplication].delegate) startWithFlash];
... from within another class in your app is a bit of a smell. One that I've had many times. It means you have circular dependencies. Your app delegate is dependent on the class using this code (transitively, if not directly), and this class is dependent on your app delegate. Looking at your Instruments trace, it looks like you have adopted the delegate pattern else where, so you have some experience with decoupling. I would suggest bubbling that message up through a delegate chain, notification, or block.
The leak instruments warn me about a memory leak related to this part of code:
[self.contview addSubview:nav.view];
Here are how I manage the view:
[nav.view removeFromSuperview];
self.nav = [[[destinationClass alloc] initWithNibName:pagename bundle:nil] autorelease];
[self.contview addSubview:nav.view];
Is it normal that the self.nav has a retainCount of 2 just after been allocated?Could this be related to the memory leak?
I'm very new to the memory management can someone give me some help?
Many Thanks
Assuming nav is a strong (retain) property, it retains the view controller you are assigning here:
self.nav = [[[destinationClass alloc] initWithNibName:pagename bundle:nil] autorelease];
effectively, the retain count after this line of code is 1; +2 for alloc and retain and -1 for autorelease. Generally you should never use retainCount method to determine the actual retain count of the object, maybe this answer will give you more insight why.
Every alloc, retain or copy call should be matched with a release or autorelease call. You should add a matching release call in dealloc method of your class
-(void) dealloc {
[_nav release];
_nav = nil;
[super dealloc];
}
Don't use manual memory management, use ARC, it will make your life much easier :)
What I've found using the profiling tool in Xcode (Allocations) is that when you nil out a property it does not get deallocated until the parent class gets nilled out. Now let's say you want to make sure that you don't keep an expensive modal view controller in memory (assuming that it won't get used very often), if the expensive VC is a property, the allocated memory for that property will not get released when the property gets nilled out, which means that when the user wants to use the expensive VC again we'll allocate the same amount of memory each time. This is easy to spot in the profiler as the graph just keeps climbing.
However, if I only define the expensive VC as an instance variable and define my own setter&getter the profiler allocation graph actually decreases immediately when the variable gets nilled out and goes back up with the same amount upon each new allocation.
So my question is, why does a variable 'seem' to get deallocated when defined as an instance variable but not when defined as a property?
// What I call defining something as an instance variable:
#interface SomeViewController ()
{
UIPopoverController *somePopover;
}
// What I call defining something as a property
#property (nonatomic,strong) UIPopoverController *somePopover;
// Nilling out a property which does not get allocated unless it does not have a parent (root node memory graph wise)
self.somePopover = nil;
// Nilling out an instance variable which does make the memory graph in the profiler go down by the same amount it went up
somePopover = nil;
AFAIK, you cannot force an object to release all of its memory until its parent calls deallocate whereupon all of its children get cascade deallocated..
https://stackoverflow.com/a/7631831/2536815
I don't think your analysis is correct. If properly used, both a property and an instance variable have the same effect on reference counting: setting it to nil decreases the reference counter and if it goes to 0, the instance (be it a view controller or something else) is immediately deallocated.
If this is not the case in your app, then the cause must be something else than property vs. instance variable.
To further analyze your specific problem, we'll need more information. So please post your code, describe the set up for measuring the memory management effects, what the effective results are and what you would expect instead.
So it seems like I just confused hiding/showing an expensive view with allocating/nilling, the memory graph goes down when the view isn't visible and vice versa, really silly of me I know..
Just to verify that you cannot force arc to release a property/ivar I created a new xcode project where I just put the code below in the didFinishLaunching method. And unfortunately the memory allocated for the property and ivar are kept in memory no matter if I nil them out or not. In my case the code below generates 2.8 MB of memory and when this method finishes and the application launches the profiler memory graph stays at 2.8MB indefinitely..
#interface SSAppDelegate ()
{
NSMutableArray *ivVC;
}
#property (nonatomic, strong) NSMutableArray *propertyVC;
#end
#implementation SSAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
for (int k=0; k<10; k++) {
ivVC = [NSMutableArray array]; // Doesn't matter if it's alloc] init]
self.propertyVC = [NSMutableArray array];
for (int i=0; i<1000;i++) {
[ivVC addObject:#"..................................................................................."];
[_propertyVC addObject:#"..................................................................................."];
}
ivVC = nil;
self.propertyVC = nil;
}
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Memory management with delegates, it is my understanding that I don't retain delegates, I am a little unsure about what to do with the delegate if the view gets unloaded (via viewDidUnload) and later recreated (via viewDidLoad)?
#property(assign) SomeClass *someDelegate;
.
- (void)viewDidLoad {
[super viewDidLoad];
someDelegate = [[SomeClass alloc] init];
[someDelegate setDelegate:self];
}
-(void)viewDidUnload {
[super viewDidUnload];
[self setSomeDelegate:nil];
}
-(void)dealloc {
[super dealloc];
}
PS: I might be on the wrong track, I am just trying to get my head round this ...
cheers Gary
If you use assign for your property, you're not calling retain on the object.
This means that you should definitely NOT call release or autorelease on it!
Your line in your dealloc
[someDelegate release];
will cause a crash at some point down in the future - you should remove it. You don't need to care about assigned properties in the dealloc method.
Your line
[self setSomeDelegate:nil];
will not leak.
However, you seem to have [[someDelegate alloc] init] in your viewDidLoad method. This is unusual; it's normal for the delegate to be an external object, not one made by yourself. In your case, it's not really a delegate, it's just an object that does something for you - you should rename it and change the property to a retain (and remember to release it in dealloc).
Currently, if your property is set to (assign) and someone else sets it, you will leak your initial delegate. If you only use the delegate inside this class, perhaps it shouldn't be a property at all? If you just want to be able to read it from outside your class you might be able to use (readonly) instead of assign (and change [self setSomeDelegate:nil] to someDelegate=nil;)
Your line in viewDidUnload that sets the delegate to nil removes the issue you raise in your second comment - you're removing the delegate so by the time you get to viewDidLoad again, your delegate is already nil :)
This may shed some light to understand why
The reason that you avoid retaining delegates is that you need to
avoid a retain cycle:
A creates B A sets itself as B's delegate … A is released by its owner
If B had retained A, A wouldn't be released, as B owns A, thus A's
dealloc would never get called, causing both A and B to leak.
You shouldn't worry about A going away because it owns B and thus gets
rid of it in dealloc.
I was wondering how the autorelese works on the iPhone. I though that once you send an autorelease to an object it is guaranteed to be retained in till the end of the scope of the block the autorelease was sent. Is that correct?
I was initializing a view from a NIB in the applicationDidFinishLaunching like below:
(void)applicationDidFinishLaunching:(UIApplication *)application {
loginViewController = [[[LoginViewController alloc] initWithNibName:#"LoginView" bundle:nil] autorelease];
[window addSubview: [loginViewController view]];
[window makeKeyAndVisible];
}
and the view did not show at all, all there was on the screen was the UIWindow
Now once I removed the autorelease from the end of the controller initialization all went smooth from there on.
What is this about?
Cheers,
K.
When you call autorelease, you give ownership of the object to the current autorelease pool. The run loop creates a new autorelease pool before it dispatches an event (such as applicationDidFinishLaunching:) and destroys that pool when the event finishes.
When you give ownership of your LoginViewController to the autorelease pool, it gets released just after the applicationDidFinishLaunching: returns. When the view controller deallocates itself, it removes its view from the superview (your window in this case).
Your application delegate should keep ownership of the LoginViewController and release it in the app delegate's dealloc method (or when you're done with your login and have moved on to another view).
To expand on Don's answer, it may be somewhat confusing to say "you give ownership of the object to the current autorelease pool." This might be misunderstood to mean the object is guaranteed to be destroyed when the autorelease pool is drained. This is not correct (though it will happen in this case). Sending -autorelease requests that the autorelease pool send a -release message when it is drained. If that -release message makes retainCount = 0, then the object will be destroyed.
So, in order to do what Don is recommending, you need to create a ivar to keep track of this view controller. His explanation of why the view vanishes is exactly right; but you don't want to just leak the view controller. You want to hold onto it, and release it when you're done with it.
#interface ... {
LoginViewController *_loginViewController;
}
#property (readwrite, retain) LoginViewController *loginViewController;
#implementation ...
#synthesize loginViewController = _loginViewController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
self.loginViewController = [[[LoginViewController alloc] initWithNibName:#"LoginView" bundle:nil] autorelease];
[window addSubview: [loginViewController view]];
[window makeKeyAndVisible];
}
- (void)dealloc {
[_loginViewController release]; _loginViewController = nil;
[super dealloc];
}
The autoreleasepool is cleaned at the end of the runloop. This means as long as you invoke methods and do stuff, it's still there.
I don't see the error in your code, but the Window is retained properly in your example.
Since you're adding your LoginViewController to the autorelease pool it's being released at the end of the run loop. When that happens it also releases its' view and removes it from being displayed.