我在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
答案 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中,每次绘制视图时都会向视图层次结构添加子视图。这有两个原因:
问题2
计时器保留目标。在Status对象的初始值设定项中,创建一个以self为目标的计时器。在定时器无效之前,定时器和视图之间存在保留周期,因此视图不会被释放。
如果使用计时器使视图无效的方法确实是解决问题的正确方法,则需要采取明确的步骤来打破保留周期。
执行此操作的一种方法是在-viewDidMoveToWindow中安排计时器:将视图放入窗口[1]时,并在从窗口中删除视图时使计时器无效。
[1]当视图没有在任何窗口中显示时,定期使视图无效是没有意义的。
答案 2 :(得分:2)
为什么不创建一个调用“title.text = [NSString stringWithFormat:@"Tick %d", [model n]] ;
”的方法,而不是在视图控制器中使用NSTimer调用-setNeedsDisplay?这样,每次计时器触发时,您都可以更新显示的值,而不是重新创建标签。