1

我在 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

File: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
4

3 回答 3

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。

于 2009-07-17T04:35:32.900 回答
3

您的代码有两个问题。

问题 1

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

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

问题 2

计时器保留其目标。在您的 Status 对象的初始化程序中,您创建一个以 self 为目标的计时器。直到定时器失效,定时器和视图之间存在一个保留周期,所以视图不会被释放。

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

一种方法是在 -viewDidMoveToWindow 中安排计时器:当视图被放入窗口 [1] 时,并在视图从窗口中移除时使计时器无效。

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

于 2009-07-17T13:40:38.937 回答
2

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

于 2009-07-17T03:43:40.483 回答