UIView和NSTimer没有释放内存

时间:2009-07-17 03:24:40

标签: iphone objective-c cocoa uiview nstimer

我在UIView中有一行UILabel文本,它通过NSTimer定期更新。这段代码应该经常在屏幕底部附近写一个状态项。数据来自其控制之外。

我的应用程序内存耗尽非常快,因为看起来UILabel似乎没有被释放。看来dealloc从未被调用过。

这是我的代码的一个非常压缩的版本(为清楚起见,删除了错误检查等。):

文件:SbarLeakAppDelegate.h

#import <UIKit/UIKit.h>
#import "Status.h"

@interface SbarLeakAppDelegate : NSObject 
{
    UIWindow *window;
Model *model;
}
@end

文件:SbarLeakAppDelegate.m

#import "SbarLeakAppDelegate.h"

@implementation SbarLeakAppDelegate
- (void)applicationDidFinishLaunching:(UIApplication *)application 
{       
    model=[Model sharedModel];

    Status * st=[[Status alloc] initWithFrame:CGRectMake(0.0, 420.0, 320.0, 12.0)];
    [window addSubview:st];
    [st release];

    [window makeKeyAndVisible];
}

- (void)dealloc 
{
    [window release];
    [super dealloc];
}
@end

文件:Status.h

#import <UIKit/UIKit.h>
#import "Model.h"

@interface Status : UIView 
{
    Model *model;
    UILabel * title;
}
@end

文件:Status.m 这就是问题所在。 UILabel似乎没有被释放,并且很可能也是字符串。

#import "Status.h"

@implementation Status

- (id)initWithFrame:(CGRect)frame 
{
self=[super initWithFrame:frame];
model=[Model sharedModel];
[NSTimer scheduledTimerWithTimeInterval:.200 target:self  selector:@selector(setNeedsDisplay) userInfo:nil repeats:YES];
return self;
}

- (void)drawRect:(CGRect)rect 
{
title =[[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 12.0f)];
title.text = [NSString stringWithFormat:@"Tick  %d", [model n]] ;
[self addSubview:title];
[title release];
}

- (void)dealloc 
{
    [super dealloc];
}
@end

文件:Model.h(这个和下一个是数据源,因此仅包括完整性。)它所做的就是每秒更新一个计数器。

#import <Foundation/Foundation.h>
@interface Model : NSObject 
{
int n;
}

@property int n;
+(Model *) sharedModel;
-(void) inc;
@end

档案:Model.m

#import "Model.h"


@implementation Model

static Model * sharedModel = nil;

+ (Model *) sharedModel
{
if (sharedModel == nil)
    sharedModel = [[self alloc] init];
return sharedModel; 
}

@synthesize n;
-(id) init
{
self=[super init];
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(inc) userInfo:nil repeats:YES];
return self;
}

-(void) inc
{
n++;
}
@end

3 个答案:

答案 0 :(得分:5)

问题是你永远不会从状态UIView中删除UILabel。我们来看看drawRect中的保留计数:

(void)drawRect:(CGRect)rect {
   title =[[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 12.0f)];

在这里,您创建了一个带有alloc的UILabel,它创建了一个保留计数为1的对象。

[self addSubview:title];
[title release];

将UILabel添加到状态视图会将标题的保留计数增加到2.以下版本导致最终保留计数为1.由于永远不会从超级视图中删除该对象,因此永远不会释放该对象。

基本上,每次定时器被触发时,你都会在另一个上面添加一个UILabel,直到内存耗尽。

如下所示,您应该在视图加载时创建一次UILabel,并使用[model n]更新UILabel的文本。

作为管家备忘录,您可能还需要确保在dealloc方法中正确释放任何遗留对象。 'model'和'title'应该在Status'dealloc中发布,就像'model'应该在SbarLeakAppDelegate中一样。

希望这有帮助。

编辑[1]:

听起来你在这一点上已经很好地处理了内存问题。我只想建议您使用的两个计时器的另一种替代方案。

您在Status对象中运行的计时器每隔0.2秒触发一次。实际递增“模型”值n的计时器每秒仅触发一次。虽然我相信您这样做是为了确保状态视图更加经常“刷新率”,但您可能会在不更改数据的情况下每秒重新绘制视图4或5次。虽然这可能不太明显,因为视图相当简单,但您可能需要考虑类似NSNotification的内容。

使用NSNotification,您可以让Status对象“观察”当值“n”发生更改时模型将触发的特定类型的通知。 (在这种情况下,大约每秒1次)。

您还可以指定回调方法,以便在收到通知时处理通知。这样,只有在模型数据实际更改时才会调用-setNeedsDisplay。

答案 1 :(得分:3)

您的代码有两个问题。

问题1

在-drawRect中,每次绘制视图时都会向视图层次结构添加子视图。这有两个原因:

  • 每次绘制视图时,子视图的数量都会增加1
  • 您正在绘制时修改视图层次结构 - 这是不正确的。

问题2

计时器保留目标。在Status对象的初始值设定项中,创建一个以self为目标的计时器。在定时器无效之前,定时器和视图之间存在保留周期,因此视图不会被释放。

如果使用计时器使视图无效的方法确实是解决问题的正确方法,则需要采取明确的步骤来打破保留周期。

执行此操作的一种方法是在-viewDidMoveToWindow中安排计时器:将视图放入窗口[1]时,并在从窗口中删除视图时使计时器无效。

[1]当视图没有在任何窗口中显示时,定期使视图无效是没有意义的。

答案 2 :(得分:2)

为什么不创建一个调用“title.text = [NSString stringWithFormat:@"Tick %d", [model n]] ;”的方法,而不是在视图控制器中使用NSTimer调用-setNeedsDisplay?这样,每次计时器触发时,您都可以更新显示的值,而不是重新创建标签。