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.
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.
In iOS, I pop from current viewController into previous one, but it doesn't go into dealloc.
Is this because there is another pointer pointing towards the current viewController, either in a different viewController or in the current one?
This is where I pop to previous view:
- (IBAction)fileUploadCancelTouched:(UIButton *)sender {
[self.fileToUpload cancel];
[self.view hideToastActivity];
[self.greenprogressBar removeFromSuperview];
[self.subView removeFromSuperview];
self.fileUploadCancelButton.hidden = YES;
if (self.commandComeBackToFinalScreen == 1) {
[self.navigationController popViewControllerAnimated:YES];
}
}
This is my dealloc function:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.greenprogressBar = nil;
self.fileUploadCancelButton = nil;
self.fileToUpload = nil;
[buttonHome_ release];
[buttonTestMeAgain_ release];
[buttonMarkMyTest_ release];
[examId_ release];
[sender_ release];
self.ob = nil;
[_fileUploadCancelButton release];
[super dealloc];
}
Check to make sure that ARC is not enabled in your project. If it is not ARC enabled then dealloc should be called unless your code is retaining your view controller. You should check through the Instruments tool if your pop commands reduces memory or not.
There may be some other reasons as mentioned in another answer that I am posting below:
The obvious reason is that something is retaining your viewController. You will have to look closely at your code. Do you do anything that in your class that uses delegates, since they sometimes retain the delegate. NSURLConnection will retain your class, and so does NSTimer. You can scatter code in you class and log your class's retain count, and try to find out where. In the code you showed so far the retain could should just be 1, since the class is only retained by the navigation controller.
Also, before you pop your view, get a reference to it, pop it with NO animation, and then send it some message that has it report the retain count (this would be some new method you write). That new method could also log other things, like whether it has any timers going, NSURLConnections, etc.
First of all, get rid of [super dealloc]. I know that's intuitive, but the documentation says don't do it.
In my own case, I had an observer & timer in my dealloc method, but that wouldn't run since the timer had a strong pointer to the controller.
Created a dedicated clean up method which removed the observer & invalidated the timer. Once that ran, the controller was correctly deallocated.
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 :)
I've the class OTNetwork that is subclass of UIViewController.
When user pushes a button I use this code to call it:
OTNetwork *net = [[OTNetwork alloc] initWithNibName:#"OTNetwork" bundle:nil];
[self presentModalViewController:net animated:YES];
[net release];
When user wants to exit, pushes a button and the OTNetwork object sends a notification that makes the caller ViewController dismiss the view controller. This is the code:
[self dismissModalViewControllerAnimated:YES];
My problem is that the OTNetwork object dealloc method is never called. And here is the invalidate call to a timer that never is stopped. An aditional problem is the memory leak.
In the caller View Controller this object only is created and dismissed by these lines of code.
Any help please?
Thanks in advance!.
Autorelease never guarantees when the dealloc will be called and you shouldn't rely on that.
And autorelease pools should be used for threads or when you have large memory allocations in a closed loop. It shouldn't be used on the main thread which already runs in a separate pool.
You should probably move the invalidate timer call to viewDidUnload or viewWillDisappear in OTNetwork class.
Hope that helps.
[Update: Mar 02, 2012]
If you'd like to ensure that dealloc is called, try the following
1) Store a reference to OTNetwork controller
OTNetwork *net = [[OTNetwork alloc] initWithNibName: #"OTNetwork" bundle: nil];
net.delegate = self;
self.modalV = net; // #property (nonatomic, strong) OTNetwork *modalV;
[net release];
[self presentModalViewController: modalV animated: YES];
2) Define a protocol / delegate in OTNetwork to report back when it's closed
// .h
#protocol OTNetworkDelegate;
- (void) netViewClosed;
#end
// .m
- (void) viewDidUnload
{
[self.delegate netViewClosed];
}
3) In mainViewController, implement the protocol
- (void) netViewClosed
{
if(modalV)
{
[modalV release], modalV = nil;
}
}
when you pass your OTNetwork object to the self which i'm assuming is a navigationController then your OTNetwork object is in the release pool and you don't need to worry about it being deallocated also cause your code is good on memory management.
So the short answer, its in the autorelease pool
you can try this for dealloc to be called , by using your own autorelease pool.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
try{
//your code for allocating that object
OTNetwork *net = [[[OTNetwork alloc] initWithNibName:#"OTNetwork" bundle:nil] autorelease]; [self presentModalViewController:net animated:YES];
}
finally{
[pool drain];
}
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.