UIView и NSTimer не освобождают память - PullRequest
1 голос
/ 17 июля 2009

У меня есть строка текста UILabel в 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 ]

5 голосов
/ 17 июля 2009

Проблема в том, что вы никогда не удаляете UILabel из Status 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 в Status увеличивает количество сохраняемых заголовков до 2. В следующем выпуске счетчик окончательных сохранений равен 1. Поскольку объект никогда не удаляется из своего суперпредставления, объект никогда не освобождается.

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

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

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

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

Редактировать [1]:

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

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

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

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

3 голосов
/ 17 июля 2009

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

Задача 1

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

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

Задача 2

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

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

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

[1] Временное бессмысленное отображение представления, когда оно не отображается ни в каком окне, бессмысленно.

2 голосов
/ 17 июля 2009

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

...