I have a very simple code to show a modal controller (nextController is a class member):
nextController = [[InstructionsScreen alloc] initWithNibName:#"InstructionsScreen" bundle:nil];
[self presentModalViewController:nextController animated:YES];
[nextController release];
And then when the controller should hide:
[self dismissModalViewControllerAnimated:YES];
nextController = nil;
All works good as expected, but when I run instrument Object Allocations it shows that after dismissing the modal controller the memory it allocated is not freed. This becomes a problem because when I show several controllers the memory is over ...
Can anybody give me some clues ? Clang doesn't see any problems, so I'm stuck hitting the memory limit, because the memory of the dismissed controllers won't get released.
EDIT: What I discovered up to now is that it seems to be a leak somewhere in Apple's stuff. Way to reproduce: XCode -> create new project with the template "Utility application". Don't write any code yourself. Just create a new utility application and run it with "Object allocations", choose to see "Created & Still living". Now flip the modal controller few times - you'll see the allocated memory only grows and grows every time the modal controller is appearing and when it's disappearing too ...
There is no leak in the code you show as far as I can see. There could be a leak in InstructionsScreen that would prevent it being deallocated.
I think it's worth running the Static Analyser to see if it finds a leak.
The leak in the Apple template code is interesting. It could be that there is a leak. It seems unlikely but obviously it's not impossible. I would say that it's more likely that it's a false-positive in Instruments, which is why I'd suggest using the Static Analyser.
(You might want to raise a bug report about the leak.)
Modal views are not subviews of the calling view but are instead subview of the apps window and are retained by the window itself. You generally you do not retain a reference to them in the controller that calls them. Instead, evoke the modal view and then have it communicate with the controller by defining the controller as the modal view's delegate.
I think that if you use synthesize to create the accessor for a nextController property defined with retain, then the accessor will retain any object assigned to the property. Simply setting the value to nil will not release the object unless the accessor is set up to do that and I don't think the autogenerated ones do.
You will expressly have to call release before setting to nil.
If this doesn't work, post the code for your definition of the nextController property.
Related
I've got a relatively simple problem that's been evading solution for some time. I have a view controller and an associated XIB. The view controller is called FooterViewController. FooterViewController's view is set as the footer view of a tableview.
FooterViewController's view includes a label for showing feedback to the user. I would like this label to persist until its value is changed by my application. Under normal circumstances, it does. However, I've just begun testing with memory warnings, and I've found that after the view is unloaded in response to a memory warning, the label is cleared.
Here's what I've tried so far to solve the problem: in FooterViewController's viewWillUnload method, I store the label's text in an instance variable called statusString:
- (void)viewWillUnload
{
statusString = [statusLabel text];
testInt = 5;
NSLog(#"View will unload; status string = %#; testInt = %d",
statusString, testInt);
[super viewWillUnload];
}
Note that I've also set another instance variable, declared as NSInteger testInt, to 5.
Then, in FooterViewController's viewDidLoad method, I try to set the label's text to statusString:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Just before setting label, statusString: %#; testInt: %d",
statusString, testInt);
[statusLabel setText:statusString];
NSLog(#"View did load.");
}
However, this does not work. Further, in the log after simulating a memory warning, I see:
View will unload; status string = Invalid IP address Error code: 113; testInt = 5
(Note that "Invalid IP address Error code: 113" is the correct value for statusString)
Then, after navigating to FooterViewController again, I see:
Just before setting label, statusString: (null); testInt: 0
This indicates to me that for some reason, the instance variables of FooterViewController are being reinitialized when the view loads again. A final note: the method initWithNibName:bundle: is being called each time the view must reload, though I expect this; after all, the view must be reloaded from the NIB.
So, my questions are these:
Why do these instance variables appear to be nullified or zeroed in the process of unloading and reloading the view?
If I'm doing something incorrectly that's causing this nullification, what is it?
If I'm not doing anything incorrectly, and this is normal behavior, how should I handle maintaining state between loads of the view?
Thanks,
Riley
The statusString looks like a weak reference, not a strong property. It can not retain the labels's text, which gets deallocated with the label when the view is unloaded. That's why you get first a correct value (before the label is deallocated), and null later (after the label has been deallocated, and the weak ref nullified). Turn your statusString into a strong property, and that ARC magic won't bite you any longer.
It looks like you need to be using didRecieveMemoryWarning instead of viewDidUnload, since viewDidUnload is not guaranteed to be called in the event of a memory warning. If the crash is exiting the app completely then you need to be writing the data to disk using something like coreData. Save your data here and then call the super so the view will still be released. Hope that helps.
I figured out what was going on, finally. The issue was that I called the allocation and initialization methods for FooterViewController in its parent view controller's viewDidLoad method. When the views were dumped and subsequently reloaded, my view controller was re-initialized! This destroyed the original FooterViewController, which maintained the instance variables I needed, and replaced it with a brand-new VC.
The solution was to move [[FooterViewController alloc] init] to the init method of FooterViewController's parent VC, so that the initialization was only performed once per run cycle.
I've learned my lesson: don't reinitialize your view controllers unless you really mean to do so. As such, be very careful where you put your calls to the initializer in parent view controllers.
Thanks for the help I got from the two answerers.
I'm familiar with the delegate pattern and nilling my delegates, especially when doing asynchronous calls which are still in progress when my view controllers disappear. I nil the delegate, and the callback successfully returns on a nil object.
I'm now experimenting with using completion blocks to make my code a little easier to read.
I call a network service from my view controller, and pass a block which updates my UITableView. Under normal circumstances it works fine. However, if I leave the view before it completes, the completion handler block is executed - but the UITableView is now a zombie.
Whats the usual pattern for handling this?
UPDATE WITH CODE SAMPLE
This is an iPad app, I have two view controllers on screen at once, like a split view. One is the detail, and the other is a grid of images. I click an image and it tell the detail to load the info. However, if i click the images too fast before they have chance to do the network call - I have the problems. On changing images the code below is called which counts the favourites of a image....
So here is my dilemma, if I use the code below - it works fine but it leaks in instruments if you switch images before the network responds.
If I remove the __block and pass in self, then it crashes with zombies.
I can't win... I'm sure i'm missing something fundamental about using blocks.
__block UITableView *theTable = [self.table retain];
__block IndexedDictionary *tableData = [self.descriptionKeyValues retain];
FavouritesController *favourites = [Container controllerWithClass:FavouritesController.class];
[favourites countFavouritesForPhoto:self.photo
completion:^(int favesCount) {
[tableData insertObject:[NSString stringWithFormat:#"%i", favesCount]
forKey:#"Favourites:" atIndex:1];
[theTable reloadData];
[tableData release];
[theTable release];
}];
Any tips? Thanks
SECOND UPDATE
I changed the way I loaded the favourites. Instead of the favourites being a singleton, I create an instance on each photo change. By replacing this and killing the old one - the block has nowhere to callback (i guess it doesn't even exist) and my code now just looks like the below, and it appear to be working:
[self.favourites countFavouritesForPhoto:self.photo
completion:^(int favesCount) {
[self.descriptionKeyValues insertObject:[NSString stringWithFormat:#"%i", favesCount]
forKey:#"Favourites:" atIndex:1];
[self.table reloadData];
}];
It doesn't leak, and doesn't appear to be crashing either.
I recommend you test that the tableview is not nil at the start of the block. It sounds like the tableview is properly discarded when its parent view goes off-screen, so after that point, no tableview operations are valid.
Retaining the UITableView within the block is a bad idea, because datasource/tableview updates can result in implicit method calls and notifications that will not be relevant if the tableview is not on-screen.
Block will retain any object that it references, except for those annotated with __block. If you want not to execute completion blocks at all, just make some property like isCancelled and check whether it is YES before calling completion block.
So you have a background operation which has to call back another object after it finishes and the object can be destroyed in the meantime. The crashes you describe happen when you have non retained references. The problem as you see is that the referred object goes away and the pointer is invalid. Usually, what you do is unregister the delegate inside the dealloc method so that the background task continues, and whenever it is ready to communicate the results back it says "Shoot, my callback object is nil", and at least it doesn't crash.
Still, handling manually weak references is tedious and error prone. You can forget to nil a delegate inside a dealloc method and it may go without notice for months before you encounter a situation where the code crashes.
If you are targeting iOS 5.0 I would read up upon ARC and the weak references it provides. If you don't want to use ARC, or need to target pre 5.x devices, I would recommend using zeroing weak reference libraries like MAZeroingWeakRef which work also for 3.x devices.
With either ARC's weak references or MAZeroingWeakRef, you would implement the background task with one of these fancy weak reference objects pointing back to your table. Now if the pointed object goes away, the weak pointer will nil itself and your background task won't crash.
I am working on an iPad (only) app and I stumbled across a weird problem. The app gets terminated after a memory warning on iPad 1 but works fine on iPad 2.
I am using ARC and targeting iOS 5. I use nibs and most of my assets are displayed using UIImageViews. I also have a few hundred buttons and plenty of gesture recognizers... I re-watched the WWDC11 videos (sessions 323 and 322) on ARC and I don't seem to be doing anything special.
The app is UIImage intensive, I am doing lots of animations using UIImage. I am however using the initWithContentsOfFile constructor rather than the imageNamed call. I'm trying to prevent the images from being cached by the system.
I'm also using GCD to schedule sound effects and to animate views. I'm always doing this on the main thread.
The app uses a UINavigationController that never has more than 1 UIViewController on it's stack. I can confirm that this is true because the didReceiveMemoryWarning only gets called on the current view controller (I'm logging the call).
The thing I don't understand is why Instruments is reporting high numbers (as if the view controllers don't get deallocated) in both the Allocations and VM Tracker instruments. The Allocations instrument shows a small drop when I navigate from one view controller to another (which is what I expect) but the VM Tracker Instrument shows that the Dirty Size is not dropping when I do the same thing. Eventually the app uses too much memory and gets terminated (on iPad 1). When I get memory warnings on the iPad 2 the app does NOT get terminated though...
It feels as if my images, sounds or views don't get destroyed and the memory does not get reclaimed... My object hierarchy is very basic and there should not be any retain cycles of any sort. I don't even have simple delegates...
Do you have any suggestions? I really don't want to release this app only for the iPad 2 or newer... It's an app for kids and it would be a pitty... I'd be so much happier to learn that I'm doing something wrong, as I really want to make sure this app is the best it can be...
Cheers,
Nick
There are ways to say, 'optimise' your objects by setting their properties to nil when certain things aren't needed -- so while you can't write a dealloc method anymore, you can do self.object = nil (when pertinent) which ends up doing something like this in a non-ARC world for an 'retain' (i.e., strong) property:
- (void)setObject:(id)newObject
{
[object release]; // send release message to current object
object = newObject; // set reference to newObject
[object retain]; // send retain message to newObject
}
Now while in ARC you don't/can't write retain/release yourself in your code, the compiler inserts these calls for you, meaning that in practise, setting a property to nil would do this in the above example:
[object release]; // send release message to current object
object = nil; // set reference to nil
[object retain]; // send retain message to nil (no effect)
Moreover, this is just the tip of the iceberg -- you should make sure that there are no retain cycles in your code, which could be resulting in objects leaking without recourse to their destruction. This means, that there may be places where you're using strong references to a property (i.e., an object), when you should be using a weak property. The difference being, that strong references are retained, and weak references are assigned, the former having its retainCount incremented and the latter resulting in a property assignment that looks like this if handwritten:
- (void)setObject:(id)newObject
{
object = newObject;
}
I don't like answering my own question but I figured it could be helpful to future googlers. I implemented my own UIImage based animation and I no longer use the animationImages property. Turns out my memory issues are now gone as I no longer need to store all the images in memory and I load them as they are required using a timer.
I actually think that rolling out a custom animation is beneficial since it allows for callbacks and more powerful customisation options.
Once I'm happy with it and I feel like it's ready to be shared I will post the class(es) on GitHub.
I'm getting an strange case of excessive retain counts for a view controller that I'm loading when a button is pushed.
This is the code:
-(IBAction)new
{
if (!viewSpace)
viewSpace = [[ViewSpace alloc] initWithNibName:#"ViewSpace" bundle:nil];
viewSpace.delegate = self;
viewSpace.view.frame = CGRectMake(0, 0, viewSpace.view.frame.size.width, viewSpace.view.frame.size.height);
[self presentModalViewController:viewSpace animated:YES];
NSLog(#"Count Retain: %d",[viewSpace retainCount]);
}
-(void)viewSpaceWasDissmissed:(id)sender
{
[self dismissModalViewControllerAnimated:YES];
[viewSpace release];
NSLog(#"Count Retain: %d",[viewSpace retainCount]);
}
When the IBAction New is executed first time, the retain count is 5 when just is created. (It must be 1).
When the ViewSpace object must be unload calls viewSpaceWasDismissed function in order to remove the modal view and release the previous object.
The problem is that never the retain count reach 0 and the dealloc method of ViewSpace never is called causing memory leaks.
My question is how is possible that a recently created ViewController have 5 retains? I made sure that is never created before.
Thanks.
Cocoa is probably retaining the view controller 4 times internally for reasons of its own. This isn't a problem.
More generally, the -retainCount method is useless for reasons like this, and you should never call it. It will not help you, and it will confuse you.
To debug your leak, I suggest using the leaks Instrument, inspecting the object, and analyzing where each retain and release is coming from to determine whether any are incorrect.
Check the documentation for -retainCount. I believe it says that you should not be calling it yourself - you just need to take care of any retains that you cause, and don't worry about the 'actual' retain count.
You're doing two things wrong here:
The Current view controller retains the modally presented view controller and releaseds it when it is dismissed. So you should release viewSpace after it is presented, and you don't need the release message in the dismissModalViewController method. As an aside ViewSpace is a poor name for a view controller. I had to read to the line where you are presenting it as a view controller before I knew it was a view controller. I think ViewSpaceController is a more descriptive name.
You are using retainCount which is always a bad idea. All that matters is that in your new method you created an owned object (with the alloc) and you balanced that ownership with a release (or at least you will do when you put in the correction I suggested in point 1) That's it. You took ownership of an object and you released it. The retainCount method tells you absolutely nothing that can be of any use to you. Don't do it. Just balance ownerships with release, and that is all that matters.
I'm not 100% sure of every count but here are some:
Instantiation - 1
NIB - 1+
Strong Properties (1+)
Additionally any properties that list it as a strong property (in ARC).
I noticed that when you launch a nib and you use components of the controller in the nib design, it will increase reference counts (in a strong manner) on the controller instance.
I'm trying to implement my own version of NSViewController (for backwards compatibility), and I've hit a problem with bindings: Since bindings retain their target, I have a retain circle whenever I bind through File's owner.
So I thought I'd just explicitly remove my view from its superview and release the top level objects, and that would take care of the bindings, because my controller isn't holding on to the views anymore, so they release me and I can go away. But for some reason, my view controller still doesn't get released. Here's a sample app exhibiting the problem:
http://dl.dropbox.com/u/34351/BindingsLeak.zip
Build it, launch it, and hit Cmd-K ("Create Nib" in "Edit" menu) to load a NIB into the empty window. Hit Cmd-K again to release the first view controller (TestNibOwner) and load a new one. The old view controller never gets dealloced, though.
Remove the "value" binding on the checkbox, and it gets released just fine.
If you set breakpoints at the release/retain/autorelease overrides, you see that _NSBindingInfo retains the TestNibOwner, but never releases it in the leaking case.
Anyone know how to fix this?
Doing a little investigation with class-dump and friends, it looks like Apple has a private class called NSAutounbinder that takes care of this dirty work for classes such as NSViewController and NSWindowController. Can't really tell how it works or how to replicate it though.
So, I can't really answer your question on how to prevent the retain cycle from happening for arbitrary bindings in a loaded nib, but perhaps it's some consolation to know that Apple is cheating, and you're not missing anything obvious. :-)
One thing I've done for the same problem is to create a proxy NSObjectController inside my nib. My NSViewController-like class has a pointer to this proxy and all bindings are bound through it. When I want to cleanup the view controller, I then do [selfProxy setContent:nil] on the object controller and release the view controller. In this instance the NSObjectController proxy acts as the auto-unbinder in this case.
It's more manual and you can't just release the view by itself, but it does solve the retain problem.
I'd suggest you do this:
-(void) releaseTopLevelObjects
{
// Unbind the object controller's content by setting it to nil.
[selfProxy setContent:nil];
NSLog( #"topLevelObjects = %#", topLevelObjects );
[topLevelObjects release];
topLevelObjects = nil;
}
In your nib, bindings would happen through a path like:
selfProxy.content.representedObject.fooValue
When you remove your view from its superview, are you also sending it another -release message? It was created by unarchiving from the nib, right?