Xcode: una filtración de memoria de iOS que me está volviendo loco, creo que es nsnotificationcenter, pero esperar que los ojos frescos puedan ver lo que no puedo

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

Pregunta

El código aquí es una vista modal lanzada desde RootViewController y debe mostrar un video con una tira de película en miniatura debajo de la película y luego instrucciones cronometradas en la película.

Todo funciona, pero hay una fuga de memoria / falta de liberación que no puedo ver para mirar y haber pasado tres días tratando de solucionarlo, ha llegado el momento de pedir ayuda ...

Si deshabilito el NSNotificationCenter comentándolo (resaltado en el .m), no tengo ningún problema con respecto a la memoria y mantengo el texto cronometrado. Pero tampoco tengo miniaturas. He intentado insertar [[NSNotificationCenter alloc] removeObserver:self]; En numerosos puntos para ver si eso lo eliminará. Pero, por desgracia, en vano.

También he intentado lanzar el 'backgroundtimer', pero no está demasiado impresionado cuando intento compilar y ejecutar.

En esencia, la primera vez que carga la vista modal, no hay problemas en absoluto y todo parece genial, sin embargo, si lo cierro con el -(IBAction)close:(id)sender; Parece que algo no se está lanzando, ya que la próxima vez que lance la misma página, el uso de la memoria aumenta en aproximadamente un 30% (aproximadamente la cantidad que usa la generación de miniatura) y aumenta aproximadamente la misma cantidad cada vez que vuelvo a lanzar la Vista modal.

Por favor, tenga en cuenta que soy un novato en esto y es probable que el error sea un estúpido estúpido para aquellos de ustedes que lo conocen. Pero en el interés de hacer este proyecto, con mucho gusto recibiré cualquier abuso que me apetezca arrojarme.

Aquí está el código:

.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

.metro

#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

Esta aplicación ya está en gran medida usando UiWebview (que simplemente sux), así que estoy tratando de cosas bien y lo hago correctamente.

¿Fue útil?

Solución

Tienes un par de problemas más que una filtración.

El primero

esta en tu initWithNibName:bundle: Como no estás haciendo nada útil allí: ¡deshazte de él, por completo! (Además: ¡No publiques argumentos, que se transmiten a tus métodos! Afortunadamente, has colocado esos lanzamientos en líneas que no se pueden decir, es decir, después de la declaración de devolución ...)

Siguiente método, siguiente problemas

  1. ¿Por qué estás enviando? release a un objeto de clase? ¡No! Eso esta mal en muchos niveles.
  2. Has decidido crear propiedades para sus temporizadores. Eso no es nada malo per se. Pero, ¿por qué entonces, estás usando los Ivars directamente aquí? Te recomendaría encarecidamente que implementen setTheTimer: y setBackgroundTimer: para manejar la invalidación y liberar correctamente y simplemente hacer self.theTimer = nil; self.backgroundTimer = nil; aquí. Eso solucionaría la asimetría en el manejo de esas cosas también. (Por cierto: Thetimer no es tal un gran nombre para un ivar ... especialmente cuando hay otro Ivar que es un temporizador!)

textInstructions: se ve insospechoso pero ...

viewDidLoad tiene algunos problemas más

  1. Fugas un mpmovieplayerController:
    los @property lo retendrá, por lo que debe equilibrar el alloc aquí.
  2. backgroundTimer tiene un correspondiente @property Se declara que está reteniendo: está violando este contrato de API aquí, ya que solo asigna el temporizador al IVAR. Usar self.backgroundTimer = ... en cambio.
  3. De todo el código que publicaste, me parece que pasando theTimer Como el último argumento en su llamado a -[NSNotificationCenter addObserver:selector:name:object:] es una forma elegante de pasar nil como ese parámetro. Lo cual es un poco bueno, porque generalmente NSTimer no publica demasiados MPMovieDurationAvailableNotifications. De hecho, como no puedo ver theTimer siendo utilizado excepto en close:: ¿Podría ser que este es solo un remanente inútil antes de que introduzca el backgroundTimer Ivar/@Propiedad? (Bueno, hay otra aparición de una variable de ese nombre, pero debe ir acompañado de una gran advertencia de compilador de grasa ...)
  4. ¿De alguna manera implementa? viewDidUnload? Si es así, lo hace:
    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;? (Asumiendo, setBackgroundTimer: lanzamientos e invalidados el valor antiguo)
  5. Actualizar Me he perdido este en el primer intento: estás filtrando 15 NSNumberS aquí. Usar [NSNumber numberWithInt:] en lugar de asigna/init en la configuración de shoutOutTimes.

Un comentario menor en movieURL, que puedes convertir en la siguiente línea:

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

Y entonces esto

NSTimeInterval lastCheckAt = 0.0; Dentro del alcance global. De su uso: ¿Ivar plz?!? Uno?

Más problemas más tarde. Tengo que conseguir algo para comer primero.


La segunda parte

Ahora vamos a entrar en timerAction:

El primer problema no es demasiado grave, especialmente en este contexto particular, pero debe tener en cuenta que -[NSArray count] Devuelve un NSUInteger y que el U no es un error tipográfico, sino una designación de que este valor no está firmado. Ciertamente no tendrá problemas con la firma en esta aplicación y rara vez lo hace en otras ocasiones, pero cuando Lo haces, compensan errores realmente funky y debes ser consciente de las implicaciones ...
El verdadero problema con este método es, sin embargo, que eres gotera uno CommentView por iteración mientras que al mismo tiempo - excesivo NSString. El hecho de que estuviera usando literales de cadena (que nunca se desearán) en primer lugar (es decir, cuando estaba inicializando ShoutOutTimes) salva totalmente su trasero, aquí.

A continuación: removeObserver:forKeyPath:

De Verdad ¡Debería deshacerse de ese mal hábito de liberar parámetros, que se pasan a sus métodos!

Dicho esto, ¡deshazte de la totalidad de este método!

Primero y ante todo removeObserver:forKeyPath: es un método de la NSKeyValueObserving Protocolo informal y desempeña un papel totalmente diferente al que lo está utilizando para lograr aquí. En segundo lugar, es uno de esos métodos en los que es esencial llamar a super Si, de alguna manera, tú De Verdad Necesito anularlo. (Bueno, excepto, cuando estabas anulando addObserver:forKeyPath:options:context: Además, y debo ir sin darle a Saying-That-que no debo hacer que no sea lo que sea. en using-kvo.)

movieDurationAvailable:

Como dijo Evan, estás filtrando times aquí. Ir por su sugerencia o, en cambio, hazlo NSMutableArray *times = [NSMutableArray array]; Y terminaste aquí.

playerThumbnailImageRequestDidFinish:

No tienes image, así que no lo liberes!
Personalmente, terminaría de configurar la vista (es decir, agregar el reconocimiento y hacer cosas así) antes de agregarlo a la jerarquía de vistas, pero eso es completamente una cuestión de gusto ...

makeThumbnailImageViewFromImage:andTimeCode:

... fugas un NSNumber (usar [NSNumber numberWithFloat:(pos * timeslice)] en vez de alloc/initWithFloat:-diance) y evita que se estrellen debido a la liberación excesiva myScrollView por la declaración de retorno incondicional que la precede directamente (¡uf!). Mientras lo hacemos: cambie el nombre de este método a cualquiera newThumbnailImageView... para que cuando vuelvas a visitar este código en un año más o menos, sabes instantáneamente que [imageView release]; en el fondo de playerThumbnailImageRequestDidFinish: Realmente es necesario, sin tener que mirar la implementación de este método.
Alternativamente, puede cambiar el nombre de thumbnailImageView... y cambiar la declaración de devolución a return [imageView autorelease];. Bonificación: una línea menos en playerThumbnailImageRequestDidFinish: como el [imageView release]; se vuelve obsoleto entonces.

dealloc

Agregar [[NSNotificationCenter defaultCenter] removeObserver:self]; en la cima.

El resto se ve bien. (Aunque me resulta extraño que el ivar landscapeView nunca/no se menciona en ninguna parte aparte de su declaración).

Resumen

Lea las secciones Reglas de gestión de memoria y Autorización automática de la "Guía de programación de gestión de memoria de Apple una vez más. ¡Son oro puro!

Otros consejos

Nunca liberas times en movieDurationAvailable::

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

Deberías usar autorelease Cuando lo pasas al método:

[self.player requestThumbnailImagesAtTimes:[times autorelease] timeOption: MPMovieTimeOptionExact];
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top