Question

J'ai une ligne de texte UILabel dans un UIView qui est régulièrement mis à jour via NSTimer. Ce code est censé écrire un élément d'état au bas de l'écran tous si souvent. Les données proviennent de l'extérieur de son contrôle.

Mon application à court de mémoire très rapide, car il semble que le UILabel n'est pas libéré. Il semble que dealloc est jamais appelé.

Voici une version très compressée de mon code (vérification d'erreurs etc pour plus de clarté.):

Fichier: SbarLeakAppDelegate.h

#import <UIKit/UIKit.h>
#import "Status.h"

@interface SbarLeakAppDelegate : NSObject 
{
    UIWindow *window;
Model *model;
}
@end

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

Fichier: Status.h

#import <UIKit/UIKit.h>
#import "Model.h"

@interface Status : UIView 
{
    Model *model;
    UILabel * title;
}
@end

Fichier: Status.m C'est là le problème. UILabel ne semble pas être libéré, et tout à fait possible la chaîne ainsi.

#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

Fichier: Model.h (ceci et la prochaine sont les sources de données, donc donné qu'à l'exhaustivité.) Tout ce qu'il fait est mise à jour un compteur à chaque seconde

.
#import <Foundation/Foundation.h>
@interface Model : NSObject 
{
int n;
}

@property int n;
+(Model *) sharedModel;
-(void) inc;
@end

Fichier: 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
Était-ce utile?

La solution

Il y a 2 problèmes avec votre code.

Problème 1

Dans -drawRect vous ajoutez un sous-vue à la hiérarchie de vue chaque fois que la vue est tracée. Ceci est faux pour 2 raisons:

  • Chaque fois que le point de vue est tracé, le nombre de sous-vues augmente de 1
  • Vous modifiez la hiérarchie des vues au moment du tirage - ceci est incorrect
  • .

Problème 2

Timers conservent leurs cibles. Dans le initialiseur pour votre objet d'état, vous créez une minuterie qui cible auto. Tant que la minuterie est invalidée, il y a un cycle retenir entre la minuterie et la vue, la vue ne sera pas désallouée.

Si l'approche de l'utilisation d'une minuterie pour invalider la vue est vraiment la bonne solution à votre problème, vous devez prendre des mesures explicites pour briser le cycle retenir.

Une approche pour le faire est de programmer la minuterie en -viewDidMoveToWindow: lorsque la vue est mis dans une fenêtre [1], et annuler la minuterie lorsque la vue est retirée d'une fenêtre

.

[1] Avoir la invalidate de vue elle-même périodiquement quand il est pas affiché dans une fenêtre est par ailleurs inutile.

Autres conseils

Le problème est que vous ne retirez la UILabel du UIView État. Jetons un coup d'oeil à votre compte conserver dans drawRect:

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

Ici, vous avez créé un UILabel avec alloc, ce qui crée un objet avec un nombre de retenir 1.

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

Ajout du UILabel à la vue de l'état augmente le nombre conserve de titre à 2. Les résultats de libération suivants dans un compte conserver final de 1. Puisque l'objet est jamais retiré de son superview, l'objet est jamais désallouées.

Pour l'essentiel, vous ajoutez une UILabel au-dessus d'une autre, chaque fois que la minuterie est déclenchée, jusqu'à ce que la mémoire est épuisée.

Comme suggéré ci-dessous, vous devriez probablement créer UILabel une fois au chargement de la vue, et simplement mettre à jour le texte du UILabel avec [modèle n].

Comme une note d'entretien, vous pouvez également vous assurer que vous êtes correctement désaffecter une gauche sur des objets dans vos méthodes de dealloc. « Modèle » et « titre » devraient être libérés dans l'état de la dealloc, tout comme « modèle » devrait être SbarLeakAppDelegate.

Hope this helps.

Modifier [1]:

Il semble que vous avez le problème de mémoire assez bien traité à ce stade. Je voulais juste suggérer une autre alternative aux deux minuteries que vous utilisez.

La minuterie vous avez en cours d'exécution dans votre objet Etat se déclenche à chaque .2 secondes. La minuterie qui incrémente en fait la valeur « modèle », n, incendies qu'une seule fois par seconde. Même si je crois que vous faites cela pour assurer un plus « taux de rafraîchissement » régulier de la vue d'état, vous êtes potentiellement redessiner la vue 4 ou 5 fois par seconde sans changement de données. Bien que cela peut ne pas être perceptible car la vue est assez simple, vous voudrez peut-être envisager quelque chose comme NSNotification.

Avec NSNotification, vous pouvez avoir l'objet d'état « observer » un type particulier de notification qui sera tirée par le modèle chaque fois que la valeur change « n ». (Dans ce cas, à environ 1 par seconde).

Vous pouvez également spécifier une méthode de rappel pour gérer la notification lors de la réception. De cette façon, vous n'appeler -setNeedsDisplay lorsque les données de modèle a été réellement changé.

Au lieu d'appeler -setNeedsDisplay avec votre NSTimer dans le contrôleur de vue, pourquoi ne pas créer une méthode qui appelle « title.text = [NSString stringWithFormat:@"Tick %d", [model n]] ; »? De cette façon, au lieu de recréer l'étiquette chaque fois que la minuterie se déclenche, vous pouvez simplement mettre à jour la valeur affichée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top