Reputation: 10398
I have a UIViewController thats presented modally. When I watch the memory allocations Instrument, the memory usage increases when the view is presented, but when it's exited the memory isn't released. If I keep opening and closing the view, the memory just keeps getting higher. Instruments doesn't report a memory leak! What could be causing this? The View Controller code is below (I've skipped the didSelectRow code). Dealloc is always called.
EDIT - I am using ARC
.h
#import <UIKit/UIKit.h>
@class OutlineTextUILabel;
@interface StoreViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
int starCount;
NSMutableArray *_singleUseArray;
NSMutableArray *_fullUseArray;
}
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet OutlineTextUILabel *starCountLbl;
- (IBAction)exitBtnPressed:(id)sender;
.m
#import "StoreViewController.h"
#import "NSUserDefaults+MPSecureUserDefaults.h"
#import "PowerUpCell.h"
#import "OutlineTextUILabel.h"
#import "PowerUpSingleton.h"
#import "PowerUp.h"
#define kPrefsNumberOfStars @"numberOfStars"
@interface StoreViewController ()
@end
@implementation StoreViewController
@synthesize tableView = _tableView;
@synthesize starCountLbl;
#pragma mark View Methods
- (void)viewDidLoad
{
[super viewDidLoad];
// Display star count
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
BOOL valid = NO;
starCount = [prefs secureIntegerForKey:kPrefsNumberOfStars valid:&valid];
if (!valid) {
NSLog(@"Stars Tampered With!");
self.starCountLbl.text = @"Err";
} else {
self.starCountLbl.text = [NSString stringWithFormat:@"%d",starCount];
}
// Tableview setup
CGRect frame2 = CGRectMake(0, 0, 320, 40);
UIView *footer = [[UIView alloc] initWithFrame:frame2];
footer.backgroundColor = [UIColor clearColor];
self.tableView.tableFooterView = footer;
self.tableView.opaque = NO;
self.tableView.backgroundView = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
if (![[PowerUpSingleton sharedList] refreshArray]) {
NSLog(@"Error, %s",__FUNCTION__);
} else {
[self performSelectorOnMainThread:@selector(workOutSingleUseToDisplay) withObject:nil waitUntilDone:YES];
[self performSelectorOnMainThread:@selector(workOutFullUseToDisplay) withObject:nil waitUntilDone:YES];
[self.tableView reloadData];
}
}
- (void)workOutSingleUseToDisplay
{
_singleUseArray = [[NSMutableArray alloc] init];
for (PowerUp *pu in [[PowerUpSingleton sharedList] sharedArray]) {
if (!pu.fullUnlock) {
[_singleUseArray addObject:pu];
}
}
}
- (void)workOutFullUseToDisplay
{
_fullUseArray = [[NSMutableArray alloc] init];
for (PowerUp *pu in [[PowerUpSingleton sharedList] sharedArray]) {
if (pu.prefFullName != nil) {
[_fullUseArray addObject:pu];
}
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown);
}
- (void)viewDidUnload {
[self setTableView:nil];
[self setStarCountLbl:nil];
[super viewDidUnload];
}
#pragma mark TableView Setup Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if (section == 0) {
return @"Single Use";
} else if (section == 1) {
return @"Use forever";
}
return nil;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {
return [_singleUseArray count];
} else if (section == 1) {
return [_fullUseArray count];
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier;
if (indexPath.section == 0) {
cellIdentifier = @"powerUpCellSingleUse";
} else if (indexPath.section == 1) {
cellIdentifier = @"powerUpCell";
}
PowerUpCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[PowerUpCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
if (indexPath.section == 0) {
PowerUp *tmpPU = [_singleUseArray objectAtIndex:indexPath.row];
cell.descriptionLbl.text = tmpPU.displayName;
int cost = tmpPU.costSingle;
cell.costLbl.text = [NSString stringWithFormat:@"%d",cost];
if (cost > starCount) {
cell.costLbl.textColor = [UIColor redColor];
} else {
cell.costLbl.textColor = [UIColor blueColor];
}
int howMany = tmpPU.numberOwned;
cell.howManyLbl.text = [NSString stringWithFormat:@"%d",howMany];
} else if (indexPath.section == 1) {
PowerUp *tmpPU = [_fullUseArray objectAtIndex:indexPath.row];
cell.descriptionLbl.text = tmpPU.displayName;
int cost = tmpPU.costFull;
cell.costLbl.text = [NSString stringWithFormat:@"%d",cost];
if (cost > starCount) {
cell.costLbl.textColor = [UIColor redColor];
} else {
cell.costLbl.textColor = [UIColor blueColor];
}
if (tmpPU.fullUnlock) {
cell.costLbl.textColor = [UIColor greenColor];
cell.costLbl.text = @"---";
}
}
return cell;
}
#pragma mark -
- (IBAction)exitBtnPressed:(id)sender
{
[self dismissModalViewControllerAnimated:YES];
}
- (void)dealloc
{
NSLog(@"%s",__FUNCTION__);
self.tableView = nil;
self.starCountLbl = nil;
}
@end
EDIT ------------- Something seems not to be right. I've added an NSLog to the cell alloc, and it's never called, even though the cells are created!
PowerUpCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
NSLog(@"new cell");
cell = [[PowerUpCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
EDIT July 1st ------ I've added a Navigation controller and now use push instead of modal and this problem is still here. I've taken heap shots with Instruments by moving back and forward between views a few times and it seems maybe the cells are still hanging around, as this screenshot shows gesture recogniser still around from a previous load of the view.
Upvotes: 5
Views: 5333
Reputation: 924
Looks like you've already found some ways around this, but just in case this helps:
1) Make sure you haven't got Zombies turned on while you're debugging, as this causes objects to hang around after you think they should be dealloc-ed (Edit scheme -> Run -> Diagnostics).
2) You're using ARC and so I assume storyboards or at least prototype UITableView cells in your storyboard/NIB? If so, then the reason your NSLog() below never gets called is because the dequeueReusableCellWithIdentifier call knows to create cells from these prototype cells via the defined cellIdentifier. Pretty handy.
PowerUpCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
NSLog(@"new cell");
cell = [[PowerUpCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
You have to rely on the UITableView to manage this cache of UITableViewCells, and release them appropriately. So it's possible they are just hanging around because your UITableView isn't being released (though I think you're saying it is).
Upvotes: 2
Reputation: 868
It is because you used your IBOutlets as weak
, instead of using strong
.
I actually believe this is a flaw in the XCode environment, as it should warn you of this kind of behavior.
As a best practice, I would suggest letting XCode generate the IBOutlets by dragging the views to the code in the Interface Builder, to avoid such annoying pitfalls.
Upvotes: 3
Reputation: 1962
At least, use the Leaks instrument to monitor memory leaks. The Allocations instrument won't actually show the memory leaks. If you run Analyze, you will see the lines that are potentially causing the leaks.
This is your code:
PowerUpCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
NSLog(@"new cell");
cell = [[PowerUpCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
You see, cell
is not going to be nil
... This is stated in the API doc for dequeueReusableCellWithIdentifier:
:
Return Value
A UITableViewCell object with the associated identifier or nil if no such object exists in the reusable-cell queue.
Anyway, if there are leaks, maybe they are very much caused by:
_singleUseArray = [[NSMutableArray alloc] init];
and
_fullUseArray = [[NSMutableArray alloc] init];
When you declared
NSMutableArray *_singleUseArray;
NSMutableArray *_fullUseArray;
I think, by default both were assigned with a __strong
qualifier. I'm not really sure but this could be the real cause of the problem. How about declaring this instead?
NSMutableArray * __weak _singleUseArray;
NSMutableArray * __weak _fullUseArray;
Also, before declaring
_singleUseArray = [[NSMutableArray alloc] init];
and
_fullUseArray = [[NSMutableArray alloc] init];
how about assigning it first to nil
to remove the previous reference?
_singleUseArray = nil;
_singleUseArray = [[NSMutableArray alloc] init];
and
_fulUseArray = nil;
_fullUseArray = [[NSMutableArray alloc] init];
Upvotes: 0
Reputation: 7065
[EDIT]
In your viewWillAppear method, have you printed out to see how often you move through your else clause. To me it seems that you call your workOutSingleUseToDisplay and workOutFullUseToDisplay methods. Each time you call those, you are allocating _singleUseArray and _fullUseArray. Just because you move in and out of a view, does not mean it calls the dealloc, or that it will auto-release your current arrays. What I think you are seeing is that when you move out of your view, it does not release those two arrays, but does try to reallocate them.
[ORIGINAL] Well, in your viewDidLoad, you perform an alloc. In your dealloc, I don't see a [footer release]. This may be your leak!!! Nor do I see releasing of your _singleUseArray or _fullUseArray arrays
Upvotes: 0
Reputation: 12215
I'm not sure if I got the anwer, but there's something strange in your code :
your are using weak properties :
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet OutlineTextUILabel *starCountLbl;
But according to the doc (search "weak"), weak
propoerty is quite similar to assign
.
In you dealloc, you have
self.tableView = nil;
self.starCountLbl = nil;
I'm pretty sure that the generated setter of these properties doesn't release them at all !
But if you declare your properties like :
@property (nonatomic, retain) IBOutlet UITableView *tableView;
@property (nonatomic, retain) IBOutlet OutlineTextUILabel *starCountLbl;
the generated setter would be like
(void)setTableView(UITableView *)newTableView {
[tableView release];
if(newTableView != nil)
tableView = [newTableView retain];
}
And your properties would be released.
Upvotes: 0