【问题标题】:UIView and NSTimer not releasing memoryUIView 和 NSTimer 不释放内存
【发布时间】:2009-07-17 03:24:40
【问题描述】:

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

文件:状态.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

【问题讨论】:

    标签: iphone objective-c cocoa uiview nstimer


    【解决方案1】:

    问题是您永远不会从状态 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 添加到 Status 视图会将标题的保留计数增加到 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。

    【讨论】:

    • 我尝试将自动释放添加到标题,但程序崩溃了。你会建议我如何发布它?
    • 既然您现在已将 [[UILabel alloc]init] 移至 init 方法,您需要对标题执行的唯一释放是在 Status 的 dealloc 中。只需将 [title release] 放在 [super dealloc] 之前即可。您可以删除标题上的所有其他发布/自动释放调用。这里发生的情况是您在 init 中分配一次 UILabel,这意味着保留计数为 1。由于您希望在程序的整个生命周期中使用该单个 UILabel,因此您需要释放 UILabel 的唯一位置是当整个视图处于解除分配。
    【解决方案2】:

    您的代码有 2 个问题。

    问题 1

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

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

    问题 2

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

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

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

    [1] 当视图没有显示在任何窗口中时,让视图周期性地使其自身失效是没有意义的。

    【讨论】:

    • 谢谢吉姆。我意识到问题 1。我相信我现在已经解决了这两个问题。对于问题 2,我是否需要偶尔终止计时器?
    • 如果这种计时器方法确实正确,我已经用打破保留周期的解决方案更新了答案。
    【解决方案3】:

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

    【讨论】:

    • 非常感谢。这似乎成功了。我将 title=[[UILabel alloc].... 行移至 init 部分。
    猜你喜欢
    • 2023-03-21
    • 2011-10-04
    • 1970-01-01
    • 2018-08-27
    • 1970-01-01
    • 2017-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多