2009-07-17 3 views
1

У меня есть строка текста UILabel в UIView, которая регулярно обновляется через 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 

Файл: 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

У вас есть 2 проблемы с кодом.

Задача 1

В -drawRect добавить подвид в иерархии представлений каждый раз, когда представление обращается. Это неправильно по 2 причинам:

  • Каждый раз, когда вид рисуется, число подвидов увеличивается на 1
  • Вы изменяете иерархию вида во время жеребьевки - это неправильно.

Задача 2

Таймеры сохраняют свои цели. В инициализаторе для объекта Status вы создаете таймер, который нацелен на себя. Пока таймер недействителен, между таймером и представлением сохраняется цикл сохранения, поэтому представление не будет отменено.

Если подход использования таймера для аннулирования представления действительно является правильным решением вашей проблемы, вам нужно предпринять явные шаги, чтобы прервать цикл сохранения.

Один из способов сделать это - запланировать таймер в -viewDidMoveToWindow: когда представление помещается в окно [1] и отменяет таймер при удалении представления из окна.

[1] Наличие представления недействительным себя периодически, когда оно не отображается в любом окне, в противном случае бессмысленно.

+0

Спасибо Джим. Я понял проблему 1. Я считаю, что исправил оба вопроса сейчас. Для проблемы 2 мне нужно время от времени убивать таймер? –

+0

Я обновил ответ решением для нарушения цикла сохранения, если этот подход по таймеру действительно правильный. –

2

Вместо вызова -setNeedsDisplay с NSTimer в контроллере представления, почему бы не создать метод, который вызывает "title.text = [NSString stringWithFormat:@"Tick %d", [model n]] ;"? Таким образом, вместо повторного создания метки каждый раз, когда срабатывает таймер, вы можете просто обновить отображаемое значение.

+0

спасибо. Кажется, это трюк. Я переместил заголовок = [[UILabel alloc] .... строка в часть init. –

5

Проблема в том, что вы никогда не удаляете UILabel из статуса UIView. Давайте посмотрим на ваши сохраняющие подсчетов в DrawRect:

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

Здесь вы создали UILabel с Alloc, который создает объект с сохранения графа 1.

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

Добавление UILabel к Просмотр состояния увеличивает счетчик удержания заголовка до 2. Следующий выпуск приводит к окончательному счету сохранения 1. Поскольку объект никогда не удаляется из своего супервизора, объект никогда не освобождается.

По существу, вы добавляете один UILabel поверх другого, каждый раз при запуске таймера, пока не закончится память.

Как показано ниже, вы должны, вероятно, создать UILabel один раз при загрузке представления и просто обновить текст UILabel с помощью [model n].

Как примечание по хозяйству, вы также можете убедиться, что вы должным образом освобождаете все объекты, оставшиеся до объекта, в своих методах dealloc. 'model' и 'title' должны быть выпущены в Status 'dealloc, так же как и' model 'должна быть в SbarLeakAppDelegate.

Надеюсь, это поможет.

Edit [1]:

Похоже, у вас есть проблема памяти довольно хорошо обрабатывается в данный момент. Я просто хотел предложить другую альтернативу двум таймерам, которые вы используете.

Таймер, запущенный в объекте Status, срабатывает каждые 2 секунды. Таймер, который на самом деле увеличивает значение «model», n, срабатывает только один раз в секунду. Хотя я считаю, что вы делаете это, чтобы обеспечить более регулярную «частоту обновления» в представлении «Статус», вы можете перерисовать представление 4 или 5 раз в секунду без изменения данных. Хотя это может быть не заметно, потому что представление довольно простое, вы можете рассмотреть что-то вроде NSNotification.

С помощью NSNotification вы можете иметь объект статуса «наблюдать» определенный вид уведомления, которое будет уволено моделью при изменении значения «n». (в данном случае приблизительно 1 в секунду).

Вы также можете указать метод обратного вызова для обработки уведомления при его получении. Таким образом, вы будете вызывать только -setNeedsDisplay, когда данные модели были фактически изменены.

+0

Я попытался добавить авторекламу к заголовку, и программа разбилась. Как бы вы посоветовали мне освободить его? –

+0

Поскольку теперь вы перешли [[UILabel alloc] init] к методу init, единственный выпуск, который вам нужно сделать для заголовка, находится в dealloc статуса. Просто поставьте [title release] перед [super dealloc]. Вы можете удалить все другие вызовы release/автоответчика на заголовок. Что здесь происходит, так это то, что вы выделяете UILabel один раз в init, а это означает, что счетчик остается равным 1. Поскольку вы хотите, чтобы этот единственный UILabel использовался для всего жизненного цикла программы, единственное место, которое вам нужно для выпуска UILabel, - это когда весь вид перераспределена. – thauburger