【问题标题】:Possible memory leak in UIViewController with UITableView带有 UITableView 的 UIViewController 中可能存在内存泄漏
【发布时间】:2012-06-27 21:49:26
【问题描述】:

我有一个以模态方式呈现的 UIViewController。当我查看内存分配 Instrument 时,显示视图时内存使用量会增加,但退出时内存不会释放。 如果我不断打开和关闭视图,内存只会越来越高。 Instruments 不会报告内存泄漏! 这可能是什么原因造成的?视图控制器代码如下(我跳过了 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

编辑 ------------- 似乎有些不对劲。我已将 NSLog 添加到单元分配中,并且从未调用它,即使已创建单元!

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

编辑 7 月 1 日 ------ 我添加了一个导航控制器,现在使用推送而不是模式,这个问题仍然存在。 我通过在视图之间来回移动几次来使用 Instruments 进行大量拍摄,似乎单元格仍然在附近徘徊,因为此屏幕截图显示手势识别器仍然在之前加载视图中。

【问题讨论】:

  • Inside viewWillAppear 你使用 performOnMainThread。这不是必需的,viewWillAppear 发生在主线程上。
  • 我只使用了这种方法,所以我可以设置 waitUntilDone:YES 所以我现在在绘制表格之前已经填充了数组。
  • 试试这个:[self workOutFullUseToDisplay]。你确实意识到 Objective-C 是顺序的,对吗?
  • 我最初有这个,但不确定它是否会在调用下一个方法之前完成 workOutSingleUseToDisplay 方法。如果不需要,我会删除它。谢谢
  • 我已修复它:-) 如果我将 tableView 的 IBOutlet 更改为强而不是弱,则在关闭时释放其内存。谁能解释为什么会这样?我认为弱引用会少 1 个引用计数。

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


【解决方案1】:

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

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

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

【讨论】:

  • 是的,我自己在上面说过。解释为什么会很好。我前段时间读到使用 IBOutlets 的弱链接。我确实让 XCode 生成 IBOutlets,但在弹出窗口中你可以选择强或弱。另外,我认为强者比弱者更有可能留下一些东西!我认为删除视图时会删除弱引用对象。
  • 这实际上取决于。您应该对 FileOwner 拥有的 IBOutlets 使用强链接,对子视图使用的 IBOutlets 使用弱链接。 developer.apple.com/library/ios/documentation/Cocoa/Conceptual/… 请注意与iOS(而非OS X)相关的部分
  • 我相信,在您的具体情况下,UITableView 单元格被分配了一个强链接,并引用了 tableview(superview)。而在另一端,UITableview(很弱)也在引用单元格 - 这不允许它们中的任何一个解除分配
【解决方案2】:

看起来您已经找到了一些解决方法,但以防万一:

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

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 没有被发布(尽管我认为您说的是)。

【讨论】:

  • 它帮助了我! @ChrisH 感谢您发布此信息。实际上,您的第一个提示帮助我追踪了一些莫名其妙的行为。像这样的帖子让 Stack Overflow 变得更好!
  • 你能分享你的问题吗?我想我的应用中可能有类似的东西。
【解决方案3】:

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

你正在使用弱属性:

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

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

在你dealloc中,你有

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

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

但如果你声明你的属性像:

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

您的财产将被释放。

【讨论】:

  • 我实际上只添加了 self.tableView=nil 和 self.starCountLbl=nil 到 dealloc,同时试图找到什么没有释放。实际上并不需要它,因为它们无论如何都是弱引用,并在视图控制器释放时释放。
【解决方案4】:

[编辑]

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

[原创] 好吧,在你的 viewDidLoad 中,你执行了一个分配。在您的 dealloc 中,我没有看到 [页脚释放]。这可能是你的泄漏!!!我也没有看到释放你的 _singleUseArray 或 _fullUseArray 数组

【讨论】:

  • 我使用的是 ARC,所以无法手动释放。我试过添加 _singleUseArray = nil; _fullUseArray = 无;页脚 = 无;到 dealloc 但没有变化。
  • 您应该在最初的帖子中介绍 ARC,因为我认为它决定了答案。
  • 很抱歉。我会把它添加到问题中。我已将 2 个数组分配器移至 viewDidLoad,因此它们肯定只会被调用一次,但内存模式仍然相同。每次关闭时都会调用视图控制器 dealloc。还有其他想法吗?
【解决方案5】:

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

这是你的代码:

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

【讨论】:

  • 我确实使用了泄漏工具,但这里没有显示泄漏,即使每次加载视图时内存都会增加。
  • 完全取出数组init还是有问题:-(
  • 我出现 1 个内存泄漏,但我认为这无关。它说责任框架=[NSURL(NSURL) 路径],当我单击它时,它看起来与情节提要加载有关。
  • 你得到了多少内存泄漏?至少现在你有东西要看。
  • 请在我的问题下方查看我的最终评论。我通过将 IBOutlet 更改为强而不是弱来解决了这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-24
  • 1970-01-01
  • 2011-07-18
  • 1970-01-01
  • 1970-01-01
  • 2010-10-11
  • 2011-01-03
相关资源
最近更新 更多