Xcode - утечка памяти iOS, которая сводит меня с ума, я думаю, что это nsnotificationCenter, но надеясь, что свежие глаза смогут увидеть, что я не могу

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

Вопрос

Код здесь представляет собой модальный вид, запущенный из RootViewController, и предназначен для демонстрации видео с миниатюрной фильмом под фильмом, а затем инструкциями по времени, связанным с фильмом.

Все работает, но есть утечка памяти / отсутствие выпуска, которое я просто не могу видеть за то, чтобы искать и провести три дня, пытаясь ее исправить, пришло время попросить о помощи ...

Если я отключаю NSNotificationCenter, комментируя его (выделено в .m), у меня нет никаких проблем, касающихся памяти, и сохранить временный текст. Но у меня также нет миниатюр. Я пытался вставить [[NSNotificationCenter alloc] removeObserver:self]; В многочисленные моменты, чтобы посмотреть, избавится ли это от этого для меня. Но, увы, безрезультатно.

Я также попытался выпустить «фонотимомер», но это не слишком впечатлено, когда я пытаюсь компилировать и бежать.

По сути, в первый раз, когда я загружаю модальный вид, нет никаких проблем, и все кажется отличным - однако, если я закрою его с помощью -(IBAction)close:(id)sender; Похоже, что-то не выпускается, так как в следующий раз, когда я запускаю ту же страницу, использование памяти увеличивается примерно на 30% (примерно на сумму, которое используется генерацией миниатюры) и увеличивается примерно на одинаковую сумму каждый раз, когда я заново запускаю Модальный вид.

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

Вот код:

.час

#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

Это приложение уже сильно использует UIWebView (который просто SUX), поэтому я пытаюсь правильно и делаю это правильно.

Это было полезно?

Решение

У вас есть на пару проблем, чем один утечка.

Первый

находится в вашем initWithNibName:bundle: Поскольку вы не делаете ничего полезного: избавьтесь от этого, полностью! (Кроме того: не выпускайте аргументы, которые передаются вашим методам! К счастью, вы разместили эти релизы в строках, которые недоступны, т.е. после операции возврата ...)

Следующий метод, следующие проблемы

  1. Почему вы отправляете release к объекту класса? Не! Это неправильно на много уровни
  2. Вы решили создать свойства для своих таймеров. Это ничего плохого само по себе. Но почему же вы используете Ivars прямо здесь? Я настоятельно рекомендую вам реализовать setTheTimer: а также setBackgroundTimer: Чтобы справиться с аннулированием и правильно выпустить и просто сделать self.theTimer = nil; self.backgroundTimer = nil; здесь. Это также исправят асимметрию в обращении с этими вещами. (Кстати: Thetimer не такой отличное имя для ivar ... особенно когда есть еще один Ивар, это таймер!)

textInstructions: Выглядит непреодолимо, но ...

viewDidLoad Есть еще несколько проблем

  1. Это утечка MpmoviePlayerController:
    А @property сохранит это, поэтому вам нужно сбалансировать alloc здесь.
  2. backgroundTimer имеет соответствующий @property Это объявляется, что удерживает: вы нарушаете этот контракт API здесь, поскольку вы только назначаете таймер ivar. Использовать self.backgroundTimer = ... вместо.
  3. Из всего кода, который вы опубликовали, мне кажется, что прохождение theTimer как последний аргумент в вашем призыве к -[NSNotificationCenter addObserver:selector:name:object:] это причудливый способ проходить nil как этот параметр. Что довольно хорошо, потому что обычно NSTimer не публикует слишком много MPMovieDurationAvailableNotificationс На самом деле, как я не вижу theTimer Используется, кроме как в close:: Может быть, это всего лишь бесполезный остаток, прежде чем вы представили backgroundTimer ivar/@собственность? (Ну, есть еще одно появление переменной этого имени, но она должна сопровождаться предупреждением о большом жирном компиляторе ...)
  4. Вы в любом случае реализуете viewDidUnload? Если так, делает это:
    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;? (При условии, setBackgroundTimer: выпуски и недействительно старое значение)
  5. Обновлять Я пропустил это на первом ходе: ты протекаешь 15 NSNumberS здесь. Использовать [NSNumber numberWithInt:] вместо Alloc/init в настройке shoutOutTimes.

Незначительное замечание о movieURL, что вы можете превратить в следующую одну строчку:

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

А потом это

NSTimeInterval lastCheckAt = 0.0; В рамках глобальной области. От вашего использования: ivar plz?!? One?

Больше проблем позже. Я должен получить что -нибудь поесть первым.


Часть вторая

Теперь давайте перейдем к timerAction:

Первая проблема не слишком серьезная, особенно в этом конкретном контексте, но вы должны знать, что -[NSArray count] возвращает NSUInteger и что U это не опечатка, а назначение, что это значение не знаковое. Вы, конечно, не столкнетесь с проблемами с подписанностью в этом приложении и редко делаете в другие случаи, но когда Вы делаете, они восполняют действительно прикольные ошибки, и вы должны знать о последствиях ...
Реальная проблема с этим методом, однако, заключается в том, что вы утечка один CommentView за итерацию в то же время - в то же время - чрезмерная издания NSString. Тот факт, что вы использовали строковые литералы (которые никогда не будут следовать), в первую очередь, (т. Е. Когда вы инициализации Shoutouttime) полностью спасает ваш приклад, здесь.

Следующий: removeObserver:forKeyPath:

Ты В самом деле должен избавиться от этой действительно плохой привычки выпускать параметры, которые передаются вашим методам!

При этом, избавьтесь от всего этого метода!

В первую очередь removeObserver:forKeyPath: это метод из NSKeyValueObserving Неформальный протокол и играет совершенно иную роль, чем то, что вы (AB-) используете его для достижения здесь. Во -вторых, это один из тех методов, где важно вызвать super Если - каким -либо образом - вы В самом деле нужно переопределить это. (Ну, за исключением случаев, когда вы переоценили addObserver:forKeyPath:options:context: Также и-это-нельзя-то, что вы не имеете, это не так, как вы действительно знаете, что-то, что вы не планируете, запланирован Использование-кво.)

movieDurationAvailable:

Как сказал Эван, ты протекаешь times здесь. Пойти на его предложение или - вместо этого - сделай это NSMutableArray *times = [NSMutableArray array]; И вы закончили здесь.

playerThumbnailImageRequestDidFinish:

У тебя нет image, так что не выпускайте это!
Лично я закончил бы настроить вид (т.е. добавить распознавание и делать такие вещи), прежде чем добавить его в иерархию просмотра, но это совершенно вопрос вкуса ...

makeThumbnailImageViewFromImage:andTimeCode:

... утечка NSNumber (использовать [NSNumber numberWithFloat:(pos * timeslice)] вместо alloc/initWithFloat:-Dance) и не позволяет вам сбой из -за переизбывания myScrollView Благодаря безоговорочному заявлению о возврате, которое напрямую предшествует ему (Фу!). Пока мы в этом: переименовать этот метод на любой newThumbnailImageView... так что, когда вы вернетесь на этот код через год или около того, вы сразу же знаете, что [imageView release]; на дне playerThumbnailImageRequestDidFinish: Действительно необходимо, без необходимости смотреть на реализацию этого метода.
В качестве альтернативы, вы можете переименовать это в thumbnailImageView... и изменить оператор возврата на return [imageView autorelease];. Анкет Бонус: на одну меньше строки в playerThumbnailImageRequestDidFinish: как [imageView release]; тогда становится устаревшим.

dealloc

Добавлять [[NSNotificationCenter defaultCenter] removeObserver:self]; на самом верху.

Остальное выглядит нормально. (Хотя я нахожу это странным, что ivar landscapeView никогда не упоминается, кроме его объявления.)

Резюме

Прочитайте разделы Правила управления памятью а также Авторелиаз из «Руководства по программированию памяти» Apple еще раз. Они чистое золото!

Другие советы

Вы никогда не выпускаете times в movieDurationAvailable::

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

Вы должны использовать autorelease Когда вы передаете его методу:

[self.player requestThumbnailImagesAtTimes:[times autorelease] timeOption: MPMovieTimeOptionExact];
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top