Xcode - iOS fuite de mémoire qui me rend fou, je pense qu'il est NSNotificationCenter, mais les yeux dans l'espoir frais peut voir ce que je ne peux pas

StackOverflow https://stackoverflow.com/questions/5843665

Question

Le code est ici une vue modal lancée du RootViewController et est d'afficher une vidéo avec une bande de film miniature ci-dessous le film puis chronométré instructions lié au film.

Tout fonctionne, mais il y a une fuite de mémoire / manque de libération que je ne peux pas voir pour regarder et avoir passé trois jours à essayer de le réparer, le temps est venu demander de l'aide ...

Si je désactive le NSNotificationCenter en commentant dehors (mis en évidence dans le .m) Je n'ai pas de problèmes en ce qui concerne la mémoire et garder le texte chronométré. Mais je n'ai pas non plus des vignettes. J'ai essayé encartage [[NSNotificationCenter alloc] removeObserver:self]; à de nombreux points pour voir si cela va se débarrasser de lui pour moi. Mais hélas, en vain.

J'ai aussi essayé libérer le « backgroundTimer », mais il est pas trop impressionné lorsque je tente de compiler et exécuter.

En substance, la première fois que je charge la vue modale, il n'y a aucun problème que ce soit et tout semble grand - Cependant, si je ferme avec le -(IBAction)close:(id)sender; il semble quelque chose ne libère pas que la prochaine fois que je lance la même page les augmentations d'utilisation de la mémoire d'environ 30% (à peu près la quantité de qui est utilisé par la génération de vignettes) et augmente d'environ la même quantité chaque fois que la relance du point de vue modal.

S'il vous plaît nu à l'esprit que je suis un débutant à cela et l'erreur est susceptible d'une connerie à ceux d'entre vous dans le savoir. Mais dans l'intérêt d'obtenir ce projet terminé, je vais prendre un plaisir tout abus vous avez envie de jeter à moi.

Voici le code:

.h

#import <UIKit/UIKit.h>
#import <MediaPlayer/MPMoviePlayerController.h>
#import "ImageViewWithTime.h"
#import "CommentView.h"

@interface SirloinVideoViewController_iPad : UIViewController {
    UIView *landscapeView;
    UIView *viewForMovie;
    MPMoviePlayerController *player;
    UILabel *onScreenDisplayLabel;
    UIScrollView *myScrollView;
    NSMutableArray *keyframeTimes;
    NSArray *shoutOutTexts;
    NSArray *shoutOutTimes;
    NSTimer *backgroundTimer;
    UIView *instructions;
}

-(IBAction)close:(id)sender;
-(IBAction)textInstructions:(id)sender;

@property (nonatomic, retain) IBOutlet UIView *instructions;
@property (nonatomic, retain) NSTimer *theTimer;
@property (nonatomic, retain) NSTimer *backgroundTimer;
@property (nonatomic, retain) IBOutlet UIView *viewForMovie;
@property (nonatomic, retain) MPMoviePlayerController *player;
@property (nonatomic, retain) IBOutlet UILabel *onScreenDisplayLabel;
@property (nonatomic, retain) IBOutlet UIScrollView *myScrollView;
@property (nonatomic, retain) NSMutableArray *keyframeTimes;

-(NSURL *)movieURL;
- (void) playerThumbnailImageRequestDidFinish:(NSNotification*)notification;
- (ImageViewWithTime *)makeThumbnailImageViewFromImage:(UIImage *)image andTimeCode:(NSNumber *)timecode;
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer;
@end

.m

#import "SirloinVideoViewController_iPad.h"
#import "SirloinTextViewController.h"

@implementation SirloinVideoViewController_iPad
@synthesize theTimer, backgroundTimer, viewForMovie, player,
   onScreenDisplayLabel, myScrollView, keyframeTimes, instructions; 

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {

    }
    return self;
    [nibNameOrNil release];
    [nibBundleOrNil release];
}

- (IBAction)close:(id)sender{
    [self.parentViewController dismissModalViewControllerAnimated:YES];
    [player stop];
    [player release];
    [theTimer invalidate];
    [theTimer release];
    [backgroundTimer invalidate];
    [SirloinVideoViewController_iPad release];
}

-

-(IBAction)textInstructions:(id)sender {

    SirloinTextViewController *vController = [[SirloinTextViewController alloc] initWithNibName:nil bundle:nil];
    [self presentModalViewController:vController animated:YES];
    [vController release];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    keyframeTimes = [[NSMutableArray alloc] init];
    shoutOutTexts = [[NSArray 
                      arrayWithObjects:
                      @"1. XXXXXXXXXXXX",
                      @"2. XXXXXXXXXXXX",
                      @"3. XXXXXXXXXXXX",
                      @"4. XXXXXXXXXXXX",
                      @"5. XXXXXXXXXXXX",
                      @"6. XXXXXXXXXXXX"
                      @"7. XXXXXXXXXXXX",
                      @"8. XXXXXXXXXXXX",                     
                      @"9. XXXXXXXXXXXX",                    
                      @"10. XXXXXXXXXXXX",                      
                      @"11. XXXXXXXXXXXX",                      
                      @"12. XXXXXXXXXXXX",                      
                      @"13. XXXXXXXXXXXX",
                      @"14. XXXXXXXXXXXX",                     
                      @"15. XXXXXXXXXXXX",
                      nil] retain];

    shoutOutTimes = [[NSArray 
                      arrayWithObjects:
                      [[NSNumber alloc] initWithInt: 1], 
                      [[NSNumber alloc] initWithInt: 73],
                      [[NSNumber alloc] initWithInt: 109],
                      [[NSNumber alloc] initWithInt: 131],
                      [[NSNumber alloc] initWithInt: 205],
                      [[NSNumber alloc] initWithInt: 250],
                      [[NSNumber alloc] initWithInt: 337],
                      [[NSNumber alloc] initWithInt: 378],
                      [[NSNumber alloc] initWithInt: 402],
                      [[NSNumber alloc] initWithInt: 420],
                      [[NSNumber alloc] initWithInt: 448],
                      [[NSNumber alloc] initWithInt: 507],
                      [[NSNumber alloc] initWithInt: 531],
                      [[NSNumber alloc] initWithInt: 574],
                      nil] retain];

    self.player = [[MPMoviePlayerController alloc] init];
    self.player.contentURL = [self movieURL];

    self.player.view.frame = self.viewForMovie.bounds;
    self.player.view.autoresizingMask = 
    UIViewAutoresizingFlexibleWidth |
    UIViewAutoresizingFlexibleHeight;

    [self.viewForMovie addSubview:player.view];

    backgroundTimer = [NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];

    [self.view addSubview:self.myScrollView];

    //I am pretty sure that this is the culprit - Just not sure why...

    [[NSNotificationCenter defaultCenter] 
     addObserver:self
     selector:@selector(movieDurationAvailable:)
     name:MPMovieDurationAvailableNotification
     object:theTimer];

    //Could be wrong, but when commented out I don't have the memory issues
}

-

- (NSInteger)positionFromPlaybackTime:(NSTimeInterval)playbackTime
{
    NSInteger position = 0;
    for (NSNumber *startsAt in shoutOutTimes)
    {
        if (playbackTime > [startsAt floatValue])
        {
            ++position;
        }
    }
    return position;
}

-(NSURL *)movieURL
{
    NSBundle *bundle = [NSBundle mainBundle];
    NSString *moviePath = 
    [bundle 
     pathForResource:@"sirloin" 
     ofType:@"m4v"];

    if (moviePath) {
        return [NSURL fileURLWithPath:moviePath];
    } else {
        return nil;
    }
}

NSTimeInterval lastCheckAt = 0.0;

- (void)timerAction: theTimer 
{
    int count = [shoutOutTimes count];

    NSInteger position = [self positionFromPlaybackTime:self.player.currentPlaybackTime];

   NSLog(@"position is at %d", position);
    if (position > 0)
    {
        --position;
    }
    if (position < count) 
    {
        NSNumber *timeObj = [shoutOutTimes objectAtIndex:position];
        int time = [timeObj intValue];

        NSLog(@"shout scheduled for %d", time);
        NSLog(@"last check was at %g", lastCheckAt);
        NSLog(@"current playback time is %g", self.player.currentPlaybackTime);

        if (lastCheckAt < time && self.player.currentPlaybackTime >= time)
        {
            NSString *shoutString = [shoutOutTexts objectAtIndex:position];

            NSLog(@"shouting: %@", shoutString);

            CommentView *cview = [[CommentView alloc] initWithText:shoutString];
            [self.instructions addSubview:cview];
            [shoutString release];
        }
    }
    lastCheckAt = self.player.currentPlaybackTime;
}

// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return YES;
}

-(void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
    [[NSNotificationCenter defaultCenter] removeObserver:MPMovieDurationAvailableNotification];
    [[NSNotificationCenter defaultCenter] removeObserver:MPMoviePlayerThumbnailImageRequestDidFinishNotification];
    [keyPath release];
}

- (void) movieDurationAvailable:(NSNotification*)notification {
    float duration = [self.player duration];

    [[NSNotificationCenter defaultCenter] 
     addObserver:self 
     selector:@selector(playerThumbnailImageRequestDidFinish:)
     name:MPMoviePlayerThumbnailImageRequestDidFinishNotification
     object:nil];

    NSMutableArray *times = [[NSMutableArray alloc] init];
    for(int i = 0; i < 20; i++) {
        float playbackTime = i * duration/20;
        [times addObject:[NSNumber numberWithInt:playbackTime]];
    }
    [self.player 
     requestThumbnailImagesAtTimes:times 
     timeOption: MPMovieTimeOptionExact];
}

- (void) playerThumbnailImageRequestDidFinish:(NSNotification*)notification {
    NSDictionary *userInfo = [notification userInfo];
    NSNumber *timecode = 
    [userInfo objectForKey: MPMoviePlayerThumbnailTimeKey]; 
    UIImage *image = 
    [userInfo objectForKey: MPMoviePlayerThumbnailImageKey];
    ImageViewWithTime *imageView = 
    [self makeThumbnailImageViewFromImage:image andTimeCode:timecode];

    [myScrollView addSubview:imageView];

    UITapGestureRecognizer *tapRecognizer = 
    [[UITapGestureRecognizer alloc] 
     initWithTarget:self action:@selector(handleTapFrom:)];
    [tapRecognizer setNumberOfTapsRequired:1];

    [imageView addGestureRecognizer:tapRecognizer];

    [tapRecognizer release];
    [image release];
    [imageView release];
}

- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer {
    ImageViewWithTime *imageView = (ImageViewWithTime *) recognizer.view;
    self.player.currentPlaybackTime = [imageView.time floatValue];
}

- (ImageViewWithTime *)makeThumbnailImageViewFromImage:(UIImage *)image andTimeCode:(NSNumber *)timecode {
    float timeslice = self.player.duration / 3.0;
    int pos = [timecode intValue] / (int)timeslice;

    float width = 75 * 
    ((float)image.size.width / (float)image.size.height);

    self.myScrollView.contentSize = 
    CGSizeMake((width + 2) * 13, 75);

    ImageViewWithTime *imageView = 
    [[ImageViewWithTime alloc] initWithImage:image];
    [imageView setUserInteractionEnabled:YES];

    [imageView setFrame:CGRectMake(pos * width + 2, 0, width, 75.0f)];

    imageView.time = [[NSNumber alloc] initWithFloat:(pos * timeslice)];
    return imageView;

    [myScrollView release];
}

- (void)dealloc {
    [player release];
    [viewForMovie release];
    [onScreenDisplayLabel release];
    [keyframeTimes release];
    [instructions release];
    [shoutOutTexts release];
    [shoutOutTimes release];
    [super dealloc];
}

@end

Cette application est déjà là fortement l'utilisation UIWebView (qui vient de sux plaine), donc je suis en train de bonnes choses et de le faire correctement.

Était-ce utile?

La solution

Vous avez quelques questions que plus un fuite.

Le premier

est dans votre initWithNibName:bundle: que vous ne faites rien il utile: se débarrasser de lui, tout à fait! (D'ailleurs: ne libère pas des arguments, qui sont passés à vos méthodes Heureusement, vous avez placé ces rejets dans les lignes qui sont inatteignable, à savoir après la déclaration de retour ...)

méthode suivante, les problèmes suivants

  1. Pourquoi envoyez-vous release à un objet de classe? Ne pas! Ce qui ne va pas sur nombre niveaux.
  2. Vous avez décidé de créer des propriétés pour vos minuteries. C'est rien de mal en soi. Mais pourquoi alors, utilisez-vous les Ivars directement ici? Je vous encourage fortement à mettre en œuvre setTheTimer: et setBackgroundTimer: pour gérer l'infirmation et libérer correctement et simplement faire self.theTimer = nil; self.backgroundTimer = nil; ici. Cela fixerait l'asymétrie dans le traitement de ces choses, aussi bien. (Soit dit en passant: theTimer est pas comme un grand nom pour une Ivar ... surtout quand il est autre Ivar qui est une minuterie)

textInstructions: semble soupçonneux, mais ...

viewDidLoad a encore quelques problèmes

  1. Il fuit un MPMoviePlayerController:
    Le @property conservera, donc vous avez besoin d'équilibrer le alloc ici.
  2. backgroundTimer a un @property correspondant qui est déclaré retenir: Vous violez ce contrat API ici, puisque vous attribuez que la minuterie à l'Ivar. Utilisez self.backgroundTimer = ... à la place.
  3. De tout le code affiché, il me semble que le passage theTimer comme dernier argument dans votre appel à -[NSNotificationCenter addObserver:selector:name:object:] est un moyen de fantaisie de passage dans nil comme ce paramètre. Ce qui est une sorte de bien, parce que généralement NSTimer ne publie pas trop de MPMovieDurationAvailableNotifications. En fait, je ne vois pas theTimer utilisé sauf dans close:: Se pourrait-il que c'est juste un reste inutile devant vous a présenté les backgroundTimer Ivar / @ propriété? (Eh bien, il y a une autre occurrence d'une variable de ce nom, mais il doit être accompagné d'un grand avertissement du compilateur de graisse ...)
  4. Avez-vous, en aucune façon, mettre en œuvre viewDidUnload? Si oui, il le fait:
    1. self.player = nil;?
    2. [shoutOutTexts release], shoutOutTexts = nil;?
    3. [shoutOutTimes release], shoutOutTimes = nil;?
    4. self.keyframeTimes = nil;?
    5. [[NSNotificationCenter defaultCenter] removeObserver:self name: MPMovieDurationAvailableNotification object:nil];?
    6. self.backgroundTimer = nil;? (En supposant, les rejets de setBackgroundTimer: et Invalide l'ancienne valeur)
  5. Mise à jour Je l'ai raté celui-ci sur le premier rendez-vous: Vous NSNumbers ici 15 une fuite de. Utilisation [NSNumber numberWithInt:] au lieu de alloc / init dans la configuration de shoutOutTimes.

Une remarque mineure sur movieURL, que vous pouvez transformer en un revêtement suivant:

-(NSURL*)movieURL {
    return [[NSBundle mainBundle] URLForResource:@"sirloin" withExtension:@"m4v"];
}

Et puis cette

NSTimeInterval lastCheckAt = 0.0; dans la portée globale. De votre utilisation de celui-ci:!? Ivar PLZ un

Plus de questions plus tard. Je dois me trouver quelque chose à manger d'abord.


Deuxième partie

Maintenant, nous allons entrer dans timerAction:

La première question n'est pas trop grave, - en particulier dans ce contexte particulier - mais vous devez savoir que le rendement des -[NSArray count] un de NSUInteger et que le U est pas une faute de frappe, mais une désignation que cette valeur est non signé. Vous ne serez certainement pas rencontrer des problèmes avec le signedness dans cette application et font rarement à d'autres occasions, mais quand vous faites, ils représentent pour les bugs vraiment géniaux et vous devriez être au courant des conséquences .. .
Le vrai problème avec cette méthode est, cependant, que vous êtes fuite d'une CommentView par itération tout - en même temps -. overreleasing un NSString Le fait que vous utilisiez littéraux de chaîne (qui ne sera jamais dealloced) en premier lieu, (par exemple lorsque vous were initialisation shoutOutTimes) sauve totalement vos fesses, ici.

Ensuite: removeObserver:forKeyPath:

vraiment devrait se débarrasser de cette habitude vraiment mauvais de libérer des paramètres, qui sont passés à vos méthodes!

Cela étant dit, se débarrasser de l'ensemble de cette méthode!

D'abord et avant tout removeObserver:forKeyPath: est une méthode du protocole informel NSKeyValueObserving et joue un rôle tout à fait différent de ce que vous êtes (ab-) l'utiliser pour accomplir ici. En second lieu, il est l'une de ces méthodes où il est essentiel d'appeler jusqu'à super si - par tout moyen - vous vraiment nécessité de la remplacer. (Eh bien, sauf quand vous surchargez addObserver:forKeyPath:options:context: ainsi et il-devrait-aller-sans-dire qui-vous-shouldn't-do-ce que, à moins-vous-vraiment-savoir-what-you-êtes, faire-si-vous jamais planifié-sur-utilisation de KVO.)

movieDurationAvailable:

Comme Evan dit, vous êtes une fuite times ici. Allez-y pour sa suggestion ou - au lieu -. NSMutableArray *times = [NSMutableArray array]; faire et vous fait ici

playerThumbnailImageRequestDidFinish:

Vous ne image pas propre, donc ne libère pas!
Personnellement, je finirais la mise en place de la vue (par exemple ajouter le et faire des choses reconnaisseur comme ça) avant de l'ajouter dans la vue hiérarchie, mais qui est complètement une question de goût ...

makeThumbnailImageViewFromImage:andTimeCode:

... fuites un NSNumber (utilisation [NSNumber numberWithFloat:(pos * timeslice)] au lieu de la alloc/initWithFloat: danse) et vous empêche de tomber en panne en raison de overreleasing myScrollView par la déclaration de retour inconditionnel qui précède directement ce (ouf!). Pendant que nous y sommes: renommer cette méthode soit newThumbnailImageView... de sorte que lorsque vous revisitez ce code dans un an, vous savez instantanément que [imageView release]; au fond de playerThumbnailImageRequestDidFinish: est vraiment nécessaire, sans avoir à regarder à la mise en œuvre de cette méthode.
Sinon, vous pouvez le renommer à thumbnailImageView... et modifier l'instruction de retour à return [imageView autorelease];. Bonus: une ligne moins playerThumbnailImageRequestDidFinish: comme [imageView release]; il devient obsolète alors

.

dealloc

Ajouter [[NSNotificationCenter defaultCenter] removeObserver:self]; au sommet.

Le reste semble correct. (Bien que je trouve étrange, que le landscapeView IVAR est jamais / mentionné nulle part en dehors de sa déclaration.)

Résumé

Lisez les sections mémoire Règles de gestion et Autorelease à partir d'Apple "Guide de programmation gestion de la mémoire" encore une fois. Ils sont l'or pur!

Autres conseils

Vous ne diffusons jamais times dans movieDurationAvailable::

NSMutableArray *times = [[NSMutableArray alloc] init];

Vous devez utiliser autorelease lorsque vous passez à la méthode:

[self.player requestThumbnailImagesAtTimes:[times autorelease] timeOption: MPMovieTimeOptionExact];
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top