2009-07-17 79 views
1

我在UIView中有一行UILabel文本,它通過NSTimer定期更新。這段代碼應該每隔一段時間在屏幕底部附近寫一個狀態項。數據來自其控制之外。UIView和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 

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

回答

3

您的代碼有兩個問題。

問題1

在-drawRect你每天的圖繪時間增加一個子視圖視圖層次。這是錯誤的原因有二:

  • 每次圖繪,子視圖增加1
  • 您正在修改在繪圖時視圖層次結構的數量 - 這是不正確。

問題2

計時器保留它們的目標。在你的Status對象的初始化器中,你創建一個定位器來定位自己。在定時器失效之前,定時器和視圖之間有一個保留週期,所以視圖不會被釋放。

如果使用計時器使視圖失效的方法確實是解決問題的正確方法,則需要採取明確的步驟來中斷保留週期。

執行此操作的一種方法是將計時器安排在-viewDidMoveToWindow中:將視圖放入窗口時[1],並在從窗口中刪除視圖時使計時器無效。

[1]在沒有任何窗口顯示的情況下,使視圖自身週期性失效是沒有意義的。

2

與其說-setNeedsDisplay你的NSTimer在視圖控制器,爲什麼不創建一個所謂的 「title.text = [NSString stringWithFormat:@"Tick %d", [model n]] ;」 的方法呢?通過這種方式,每當定時器啓動時,您不必重新創建標籤,只需更新顯示的值即可。

+0

非常感謝。這似乎已經成功了。我將title = [[UILabel alloc] ....行移至init部分。 – 2009-07-17 11:33:23

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對象中運行的計時器每隔2秒觸發一次。實際增加「模型」值n的計時器每秒僅觸發一次。雖然我相信你這樣做是爲了確保Status視圖更加規則的「刷新率」,但你可能會每秒重繪4到5次視圖而不改變數據。雖然這可能不明顯,因爲視圖非常簡單,但您可能需要考慮諸如NSNotification之類的內容。

通過NSNotification,您可以讓狀態對象「觀察」一種特定種類的通知,只要值「n」發生變化,該通知將由模型觸發。 (在這種情況下,大約每秒1次)。

您還可以指定一個回調方法來處理接收到的通知。這樣,只有在實際更改模型數據時纔會調用-setNeedsDisplay。

+0

我試圖添加autorelease標題和程序崩潰。 你會建議我怎麼發佈它? – 2009-07-17 11:29:44

+0

由於您現在已將[[UILabel alloc] init]移至init方法,因此您需要爲標題執行的唯一版本是Status的dealloc。只需[超級dealloc]之前[標題發佈]。您可以刪除標題上的所有其他釋放/自動釋放呼叫。這裏發生了什麼是你在init中分配一次UILabel,這意味着保留計數爲1.因爲你希望單個UILabel在程序的整個生命週期中,唯一需要釋放UILabel的地方是當整個視圖是釋放。 – thauburger 2009-07-17 16:35:13