Calling UIViewController and never is deallocated - ios

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];
}

Related

iOS - viewController dealloc method not called after popping to previous viewController

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.

Why a UIViewController would stay in memory

Assuming that a view controller is created like this:
#property (nonatomic, strong) SomeViewController *someViewController;
...
self.someViewController = [[SomeViewController alloc] initWithView:imgView];
[self addChildViewController:self.someViewController];
self.someViewController.view.frame = self.view.bounds;
[self.mainView addSubview:self.someViewController.view];
Why would it not get released by the following?
__weak MainViewController *weakSelf = self;
self.someViewController.didCloseBlock = ^{
[weakSelf.someViewController.view removeFromSuperview];
[weakSelf.someViewController willMoveToParentViewController:nil];
[weakSelf.someViewController removeFromParentViewController];
weakSelf.someViewController = nil;
};
I can tell it's not getting released because if I keep opening and closing the view controller (creating a new instance each time I open one), it causes low memory warnings (and then a crash on iOS5), and in SomeViewController didReceiveMemoryWarning, I see a log for the number of times I've created a new SomeViewController. For example, when I get the memory warning after opening 9 new SomeViewControllers, I will get 9 didReceiveMemoryWarning logs, indicating that I have 9 SomeViewController instances in memory, even though I'm nilling each one out in the code above.
You're retaining your view once in your property with the strong annotation and again with self.someViewController = [[SomeViewController alloc] initWithView:imgView];
Using the synthesized variable should get rid of this:
_someViewController = [[SomeViewController alloc] initWithView:imgView];
If you're not using ARC, you can use self.someViewController = [[[SomeViewController alloc] initWithView:imgView] autorelease];
I'd probably go for the first option, ARC or not though.
You are just setting the block didCloseBlock, nothing else actually. Do you execute it?

Under ARC, keep getting EXC_BAD_ACCESS after using ARC, because of using Block?

Issue:
I keep getting EXC_BAD_ACCESS. And after I open NSZombieEnabled, I saw this [FeatureCommentListViewController respondsToSelector:]: message sent to deallocated instance 0x7c1dc30
Before I changed my project to ARC, there is no such error, but after I changed to ARC, this error appeared.
I declare a ViewController in a Block and push it into navigation Controller. Will this reason case it's lifetime shorter?
UIBlockButton is from this post
UIBlockButton *lbGood3 = [[UIBlockButton alloc] initWithFrame:CGRectMake(0, 0, First_Button_Width, [self getGoodRow2Height:productDetail]) ];
[lbGood3 handleControlEvent:UIControlEventTouchUpInside withBlock:^ {
NSLog(#"%#", Label.text);
ProductDetail *productDetail = [productDetailDict objectForKey:#"product"];
NSString *dp_id = [NSString stringWithFormat:#"%#-%#",productDetail.url_crc,productDetail.site_id];
FeatureCommentListViewController *cmtListController = [[FeatureCommentListViewController alloc] initWithNibName:#"FeatureCommentListViewController" bundle:nil];
cmtListController.title = Label.text;
cmtListController.isReviewed=isReviewed;
cmtListController.productDetail=productDetail;
cmtListController.dp_id=dp_id;
cmtListController.feature_name = #"&feature_good_id=2";
[self.navigationController pushViewController:cmtListController animated:YES];
}];
Should I declare the controller as a member of this viewController or just declare out of the block?
I solved this by alloc the FeatureCommentListViewController in the viewDidLoad function and use it in block.
1st. Im wondering why do you push a view controller in a block but not in the main thread? Isn't it important to give a quick response to the touch action?
2nd.[self.navigationController pushViewController:cmtListController animated:YES]; is in your block. Whenever you left the current navigationController what will the self.navigationController represent?
3rd. If you declare the viewController out of the block you can add __block in front of it as mentioned by Hermann Klecker.

Memory leaks in pushViewController and (I think) coming back from a GCD queue

I'm having some trouble with memory leaks on one of my view controllers. From my main view controller, I'm pushing my settings view controller like so:
-(IBAction)launchSettings {
SettingsViewController *svc = [[SettingsViewController alloc] init];
svc.title = #"title of app";
//this actually adds a back button for the next vc pushed
self.navigationItem.backBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:nil action:nil] autorelease];
[[self navigationController] pushViewController:svc animated:YES]; // <--Instruments says leak is here
[svc release];
if (AdsAreEnabled) {
ADBannerView *adBanner = SharedAdBannerView;
adBanner.delegate = nil;
}
}
So, when I initially push the view controller, I have no leaks. The view controller uses a GCD queue to load up my In-App Purchase store, and when I hit the "back" button I've created above to pop it off the stack, that's when a crapload of leaks show up in Instruments. A bunch are showing up in the line of code where I push the view controller, which makes no sense to me since I immediately release it.
A couple other leaks are only leaking in main, either NSCFstrings, SKProduct and SKProductInternal, all of which I think are only brought up in the GCD queue. Here's where instruments is telling me the problem is:
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil); // <-- Instruments says leak is here
[pool release];
return retVal;
}
Here's my code where I call GCD, in its own method that gets called during viewDidLoad of the SettingsViewController:
if ([iapManager canMakePurchases]) {
// Display a store to the user.
iapTableView.sectionFooterHeight = 0;
iapTableView.rowHeight = 50;
iapTableView.scrollEnabled = NO;
//init sectionNames here to avoid leakage.
sectionNames = [[NSArray alloc] initWithObjects:#"Get Rid of Ads!", nil];
[spinner startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(reloadStore) name:kInAppPurchaseManagerProductsFetchedNotification object:iapManager];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(reloadStore) name:kTransactionCompleted object:iapManager];
//Run this in seperate thread to avoid UI lockup
dispatch_queue_t store_check = dispatch_queue_create("see if store is available", NULL);
dispatch_async(store_check, ^ {
[iapManager loadStore];
});
dispatch_release(store_check);
}
I'm a little stumped as to what I've done wrong here - I use exactly the same technique to load up a different view controller and it doesn't leak, and I can't figure out how to tell if/where my GCD stuff is leaking - everything's been analyzed repeatedly and comes out clean. I remove my observer from the Notification Center in SVC's dealloc so it's not that. I made sure to remove the transaction observer in my IAP manager's dealloc, so it's not that.
Any suggestions? Anything else you'd need to know to help me figure out where I've gone so terribly terribly wrong here?
Edited to add: I release sectionNames in SVC's dealloc method, so that's not it either.
Edit 2: I tried auto-releasing svc when I alloc it (and getting rid of the corresponding release) but I'm still getting the same leaks.
Well, I finally figured it out with some troubleshooting help from a friend - for some reason I forgot you still have to release an IBOutlet ivar even if you haven't set it up as a property (for some reason I thought an IBOutlet autoreleased if it wasn't a property, which is not true), and once I had the proper releases in dealloc, all my leaks magically went away.
Duh. Another thing to add to my Idiot Checklist, I suppose. :)
Thanks for the suggestions, guys!
First of all, in "launchSettings". You have initialized the UIBarButtonItem by "alloc and init" which makes its retain count = 1 but you haven't released it.
You should realize that those properties are retaining the values passed to them. So you could have autoreleased it.
I should say that this action isn't needed as the back button is already put for you.
Second, in the last code snippet, you did the same sectionsName.

Autorelease scope

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.

Resources