Xcode - Perdita di memoria iOS che mi sta facendo impazzire, penso che sia NSnotificationCenter, ma sperando che gli occhi freschi possano vedere cosa non posso vedere cosa non posso

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

Domanda

Il codice qui è una vista modale lanciata da RootViewController ed è quella di visualizzare un video con una pellicola in miniatura sotto il film e quindi le istruzioni a tempo limitate al film.

Funziona tutto, ma c'è una perdita di memoria / mancanza di rilascio che non riesco a vedere per aver guardato e aver trascorso tre giorni cercando di risolverlo, è giunto il momento di chiedere aiuto ...

Se disabilito NSNotificationCenter commentandolo (evidenziato nel .m) non ho problemi relativi alla memoria e mantengo il testo a tempo. Ma non ho nemmeno miniature. Ho provato a inserire [[NSNotificationCenter alloc] removeObserver:self]; In numerosi punti per vedere se questo si sbarazzerà per me. Ma ahimè, senza risultati.

Ho anche provato a rilasciare il "sfondo", ma non è eccessivamente colpito quando provo a compilare e correre.

In sostanza, la prima volta che carico la vista modale, non ci sono problemi e sembrano grandi - tuttavia, se lo chiudo con il -(IBAction)close:(id)sender; Sembra che qualcosa non stia rilasciando poiché la prossima volta che lancio la stessa pagina l'utilizzo della memoria aumenta di circa il 30% (all'incirca l'importo utilizzato dalla generazione della miniatura) e aumenta di circa lo stesso importo ogni volta Vista modale.

Per favore, mi sente a mente che sono un principiante per questo e l'errore è probabilmente un sanguinoso stupido per quelli di voi a conoscenza. Ma nell'interesse di fare questo progetto, prenderò volentieri qualsiasi abuso che desideri lanciare contro di me.

Ecco il codice:

.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

Questa app è già là fuori usando UiWebView (che semplicemente semplicemente sux), quindi sto cercando di fare le cose bene e farlo correttamente.

È stato utile?

Soluzione

Hai un paio di problemi in più di uno perdere.

Il primo

è nel tuo initWithNibName:bundle: Dato che non stai facendo nulla di utile lì: liberartene, del tutto! (Inoltre: non rilasciare argomenti, che vengono passati ai tuoi metodi! Per fortuna, hai messo quelle versioni in linee irraggiungibili, cioè dopo la dichiarazione di ritorno ...)

Next Method, Next Problems

  1. Perché stai inviando release a un oggetto di classe? Non! È sbagliato molti livelli.
  2. Hai deciso di creare proprietà per i tuoi timer. Non è niente di male di per sé. Ma perché allora, stai usando gli Ivars direttamente qui? Ti incoraggio fortemente ad implementare setTheTimer: e setBackgroundTimer: per gestire l'invalidazione e rilasciare correttamente e semplicemente farlo self.theTimer = nil; self.backgroundTimer = nil; qui. Ciò correggerebbe anche l'asimmetria nella gestione di quelle cose. (A proposito: Thetimer non lo è tale un grande nome per un ivar ... specialmente quando c'è altro Ivar che è un timer!)

textInstructions: Sembra onorevole ma ...

viewDidLoad ha altri problemi

  1. Flega un mpmovieplayercontroller:
    Il @property lo manterrà, quindi devi bilanciare il alloc qui.
  2. backgroundTimer ha un corrispondente @property Ciò è dichiarato che stia trattenendo: stai violando questo contratto API qui, poiché assegni solo il timer all'IVAR. Uso self.backgroundTimer = ... invece.
  3. Da tutto il codice che hai pubblicato, mi sembra che passino theTimer Come ultimo argomento nella tua chiamata a -[NSNotificationCenter addObserver:selector:name:object:] è un modo elegante di trasmettere nil come quel parametro. Che è un po 'buono, perché di solito NSTimer non ne pubblica troppi MPMovieDurationAvailableNotificationS. In effetti, come non riesco a vedere theTimer essere usato tranne in close:: Potrebbe essere che questo sia solo un reMint inutile da prima di introdurre il backgroundTimer Ivar/@proprietà? (Beh, c'è un'altra occorrenza di una variabile di quel nome, ma dovrebbe essere accompagnato da un grande avvertimento del compilatore a grasso ...)
  4. In ogni modo, implementi viewDidUnload? In tal caso, lo fa:
    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;? (Supponendo, setBackgroundTimer: rilasci e invalida il vecchio valore)
  5. Aggiornare Mi sono perso questo al primo giro: stai perdendo 15 NSNumbers qui. Uso [NSNumber numberWithInt:] invece di alloc/init nella configurazione di shoutOutTimes.

Una piccola osservazione su movieURL, che puoi trasformare nel seguente one-liner:

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

E poi questo

NSTimeInterval lastCheckAt = 0.0; All'interno dell'ambito globale. Dal tuo utilizzo: Ivar Plz?!? Uno?

Più problemi più tardi. Devo prendermi qualcosa da mangiare per primo.


Seconda parte

Ora entriamo in timerAction:

Il primo problema non è troppo grave, - specialmente in questo particolare contesto - ma dovresti esserne consapevole -[NSArray count] Restituisce un NSUInteger e quello il U non è un errore di battitura, ma una designazione che questo valore non è firmato. Certamente non ti imbatterai in problemi con la firma in questa app e raramente lo faccerai in altre occasioni, ma quando Lo fai, compensano bug davvero funky e dovresti essere consapevole delle implicazioni ...
Il vero problema con questo metodo è, tuttavia, che lo sei che perde uno CommentView per iterazione mentre - allo stesso tempo - in modo eccessivo NSString. Il fatto che stavi usando i letterali di stringa (che non verranno mai trattati) in primo luogo, (cioè quando stavi inizializzando ShoutoutTimes) salva totalmente il culo, qui.

Prossimo: removeObserver:forKeyPath:

Voi veramente Dovrebbe sbarazzarsi di quella cattiva abitudine di rilasciare parametri, che vengono passati ai tuoi metodi!

Detto questo, sbarazzati dell'intero metodo!

Innanzitutto removeObserver:forKeyPath: è un metodo del NSKeyValueObserving Protocollo informale e svolge un ruolo totalmente diverso da quello che stai (ab-) che lo usa per realizzare qui. In secondo luogo, è uno di quei metodi in cui è essenziale chiamare super if - in ogni caso - tu veramente bisogno di prevalerlo. (Beh, tranne, quando eri prevalente addObserver:forKeyPath:options:context: e non vorrebbe e-senza che-senza dire che non sia da fare per non essere senza che-sei-sai-cosa-cosa-sei-in-fare-if-you-ever-planned-- on-use-kvo.)

movieDurationAvailable:

Come ha detto Evan, stai perdendo times qui. Scegli il suo suggerimento o - invece - fallo NSMutableArray *times = [NSMutableArray array]; E hai finito qui.

playerThumbnailImageRequestDidFinish:

Non possiedi image, quindi non rilasciarlo!
Personalmente, finirei di impostare la vista (cioè aggiungere il riconoscimento e fare cose del genere) prima di aggiungerlo alla gerarchia della vista, ma è completamente una questione di gusti ...

makeThumbnailImageViewFromImage:andTimeCode:

... perdite an NSNumber (uso [NSNumber numberWithFloat:(pos * timeslice)] invece di alloc/initWithFloat:-dance) e ti impedisce di crash a causa del prevalente myScrollView dalla dichiarazione di ritorno incondizionata che lo precede direttamente (hew!). Mentre ci siamo: rinominare questo metodo a entrambi newThumbnailImageView... in modo che quando rivisiterai questo codice tra circa un anno, lo sai immediatamente [imageView release]; in fondo a playerThumbnailImageRequestDidFinish: È davvero necessario, senza dover esaminare l'implementazione di questo metodo.
In alternativa, potresti rinominarlo a thumbnailImageView... e modificare la dichiarazione di ritorno in return [imageView autorelease];. Bonus: una linea in meno in playerThumbnailImageRequestDidFinish: come la [imageView release]; allora diventa obsoleto.

dealloc

Aggiungere [[NSNotificationCenter defaultCenter] removeObserver:self]; in cima.

Il resto sembra a posto. (Anche se lo trovo strano, che l'Ivar landscapeView non è mai/nessun posto menzionato a parte la sua dichiarazione.)

Riepilogo

Leggi le sezioni Regole di gestione della memoria e Autorelease Dalla "Guida alla programmazione della gestione della memoria di Apple" ancora una volta. Sono oro puro!

Altri suggerimenti

Non rilasci mai times in movieDurationAvailable::

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

Dovresti usare autorelease Quando lo passi al metodo:

[self.player requestThumbnailImagesAtTimes:[times autorelease] timeOption: MPMovieTimeOptionExact];
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top