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.
Related
I'm converting a library to ARC atm. where I have an NSViewController descendant that loads a xib the usual way:
- (instancetype)initWithModule: ...
{
self = [super initWithNibName: #"mynib" bundle: [NSBundle bundleForClass: [self class]]];
if (self != nil) {
[self view];
}
return self;
}
When I executed this without ARC the retain count of that controller is 2 after the call to view (which loads the nib and connects the outlets, as you know). However with ARC enabled this increases the retain count to 3, which later causes this controller to leak, because the count never goes back to 0.
I changed all outlets to use weak references (except for NSTextView instances, but they never appear as top level objects). But this doesn't seem to help.
Update: It seems to affect every view controller I have, at least all those I checked. So this seems to be a fundamental problem, not related to the xib content.
How can I find out what causes the additional retain on load?
The absolute retain count is meaningless. You need to find all the spots where retain is called (or called by implication, in the case of ARC).
To do that, use the Allocations instrument and turn on reference count tracking. That'll give you access to the backtrace of every single retain and you can find the extra one.
More likely than not it'll be a strong reference to self in a block held by something in self. Or it'll be a cycle of strong references; self -> other -> self kinda thing.
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 have always found this confusing. Can someone please explain it for me?
In a view controller class, I have, for example, a scroll view and I want to add a number of views to it. So I create, within the view controller, a helper method that creates the view and returns it to the caller. The caller in turn adds this new view to the scroll view.
So here I call the method to get a view and pass it directly to addSubView:
[scrollView addSubView:[self getView]];
And here is the method that creates the view:
-(UIView *)getView {
UIView *v = [[UIView alloc] init];
// do all the guff I need to configure the view
return v;
}
The question is where or when does v get released? Should I be assigning it as autorelease when it is created in getView or do I release it after i have called addSubView:? Thanks.
Since -[UIView addSubview:] obtains ownership of the passed-in view by sending it a -retain message, you should be sending the return value of getView an -autorelease message:
return [v autorelease];
Jacob's conclusion is correct, but his reason is wrong, or at least unclear (and I tried but failed to squeeze this into a comment).
In getSubview:, you have created an object. You are now the owner*. When the method ends, you must release the object, relinquishing your ownership, or it will leak. The fact that you are returning the object from the method makes no difference to this rule.
In this case, since you want the code which called this method to be able to do something with the object, you use autorelease, saying "I relinquish my ownership, but don't actually get rid of this object until the calling code has a chance to see whether it wants to make a claim". What happens on the other end (retain, copy, immediate release, anything) is of no concern for the code in getSubview:. All that method needs to do is worry about the memory that it has ownership of. Doing otherwise, i.e, relying on the fact that the calling code will do something specific with the memory, will lead to bugs.
In slightly more technical terms, the object pointed to by v must be either released or autoreleased because v is about to go out of scope at the end of the method. Once a name goes out of scope, you must no longer use that name to refer to an object, and if that name was the only reference you had to the object, you have leaked.
*Note that an object can have multiple owners -- any other object which retains it becomes a part-owner.
I want to ask a stupid question about the iPhone application. I am the green of the iPhone app. I read the following code in the Apple website.
MyViewController *aViewController = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:[NSBundle mainBundle]];
[self setMyViewController:aViewController];
[aViewController release];
And I have a question, how it means of the 'release' in the line 3?
Does it presents the memory clear? or the program take control of that object? or other meanings. Thank you very much.
When you alloc something, the object you get will have a retain count of 1 - this means that this object is currently being used by someone, so it should not be removed from memory. If you call retain on an object it will increase the retain count, meaning the object is being used by 2 things. If the retain count reaches 0, it implies that the object is no longer being used by anything and it can be removed from memory. You can decrease an object's retain count by calling release on the object.
In your example, aViewController is alloc'd and after line 1 has a retain count of +1.
It is then set as the view controller in line 2. This method is therefor taking ownership of the object, so should retain it for its own use.
Line 3, we don't want anything more to do with the view controller, so we release our hold of it. The retain count decreases by one - and it is now up to the new owner to release it when it is finished with it.
You might find it helpful to read through the memory management section of this tutrial
Whenever you call alloc, you own a reference to the object that comes back, and you must call release to indicate that you no longer intend to use that reference.
In the above case, you have allocated a new view controller and assigned it to a property of your class. Assuming the property is declared with the retain option, the property will acquire its own reference to the view controller by called retain on it. So there are now two active references to it. The property will eventually release its reference (either when it is assigned a different view controller, or when your class is finalised). But if you don't call release yourself, one reference will remain, and the view controller will never be freed.
In short, you must match every alloc with a release, otherwise things will leak.
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.