使用UITableView的UIViewController中可能存在内存泄漏

时间:2012-06-26 14:31:21

标签: objective-c xcode cocoa-touch uitableview uiviewcontroller

我有一个以模态方式呈现的UIViewController。当我观察内存分配Instrument时,显示视图时内存使用量会增加,但是当它退出时,内存不会被释放。 如果我继续打开和关闭视图,内存会不断增加。 仪器不会报告内存泄漏! 可能是什么导致了这个? View Controller代码如下(我跳过了didSelectRow代码)。 总是叫Dealloc。

编辑 - 我正在使用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

编辑------------- 似乎有点不对劲。我已经向单元格alloc添加了一个NSLog,即使创建了单元格,它也从未被调用过!

PowerUpCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (cell == nil) {
        NSLog(@"new cell");
        cell = [[PowerUpCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
    }

7月1日编辑------ 我添加了一个导航控制器,现在使用push而不是modal,这个问题仍然存在。 我通过在视图之间前后移动几次来使用乐器拍摄镜头,看起来可能是单元格仍然悬空,因为此屏幕截图显示手势识别器仍然在前一次加载视图时。 screen shot

5 个答案:

答案 0 :(得分:3)

这是因为您将IBOutlets用作weak,而不是使用strong

我实际上认为这是XCode环境中的一个缺陷,因为它应该警告你这种行为。

作为一种最佳实践,我建议让XCode通过将视图拖动到Interface Builder中的代码来生成IBOutlets,以避免这种烦人的陷阱。

答案 1 :(得分:2)

看起来您已经找到了解决此问题的方法,但以防这有助于:

1)确保在调试时没有打开僵尸,因为这会导致对象在您认为应该被解除分配后挂起(编辑方案 - >运行 - &gt;诊断)。

2)你正在使用ARC,所以我在故事板/ NIB中假设故事板或至少是原型UITableView单元?如果是这样,那么下面的NSLog()从未被调用的原因是因为dequeueReusableCellWithIdentifier调用知道通过定义的cellIdentifier从这些原型单元创建单元格。非常方便。

PowerUpCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (cell == nil) {
        NSLog(@"new cell");
        cell = [[PowerUpCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
    }

您必须依赖UITableView来管理UITableViewCells的这个缓存,并适当地释放它们。所以它们可能只是闲逛,因为你的UITableView没有发布(虽然我认为你说的是​​)。

答案 2 :(得分:0)

<强> [编辑]

在viewWillAppear方法中,您是否已打印出来以查看通过else子句的频率。对我而言,似乎你调用了你的workOutSingleUseToDisplay和workOutFullUseToDisplay方法。每次调用它们时,都要分配_singleUseArray和_fullUseArray。仅仅因为你移入和移出视图,并不意味着它调用dealloc,或者它会自动释放你当前的数组。我认为您看到的是,当您离开视图时,它不会释放这两个数组,但会尝试重新分配它们。

<强> [原稿] 好吧,在viewDidLoad中,执行一个alloc。在你的dealloc中,我没有看到[页脚释放]。这可能是你的泄密!我也没有看到你的_singleUseArray或_fullUseArray数组的发布

答案 3 :(得分:0)

我不确定我是否得到了答案,但你的代码中有一些奇怪的东西:

你正在使用弱属性:

@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet OutlineTextUILabel *starCountLbl;

但根据the doc(搜索“弱”),weak propoerty与assign非常相似。

在你dealloc中,你有

self.tableView = nil;
self.starCountLbl = nil;

我很确定生成的这些属性的setter根本不会释放它们!

但是,如果您宣布您的属性如下:

@property (nonatomic, retain) IBOutlet UITableView *tableView;
@property (nonatomic, retain) IBOutlet OutlineTextUILabel *starCountLbl;

生成的setter就像

(void)setTableView(UITableView *)newTableView {
    [tableView release];
    if(newTableView != nil)
        tableView = [newTableView retain];
}

您的财产将被释放。

答案 4 :(得分:0)

至少使用Leaks仪器监控内存泄漏。分配工具实际上不会显示内存泄漏。如果运行Analyze,您将看到可能导致泄漏的行。

这是你的代码:

 PowerUpCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
 if (cell == nil) {
    NSLog(@"new cell");
    cell = [[PowerUpCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
 }

您看,cell不会是nil ...这已在dequeueReusableCellWithIdentifier:的API文档中说明:

  

返回值

  具有关联标识符的UITableViewCell对象,如果可重用单元队列中不存在此类对象,则为nil。

无论如何,如果有泄漏,可能是由以下原因造成的:

_singleUseArray = [[NSMutableArray alloc] init];

_fullUseArray = [[NSMutableArray alloc] init];

宣布

NSMutableArray *_singleUseArray;
NSMutableArray *_fullUseArray;

我认为,默认情况下,两者都分配了__strong限定符。我不太确定,但这可能是问题的真正原因。如何宣布这个呢?

NSMutableArray * __weak _singleUseArray;
NSMutableArray * __weak _fullUseArray;

此外,在宣布之前

_singleUseArray = [[NSMutableArray alloc] init];

_fullUseArray = [[NSMutableArray alloc] init];

如何首先将其分配给nil以删除之前的引用?

_singleUseArray = nil;
_singleUseArray = [[NSMutableArray alloc] init];

_fulUseArray = nil;
_fullUseArray = [[NSMutableArray alloc] init];