XCODE - утечка памяти в iOS, которая сводит меня с ума - PullRequest
0 голосов
/ 30 апреля 2011

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

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

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

Я также пытался выпустить 'backgroundTimer', но он не слишком впечатлен, когда я пытаюсь скомпилировать и запустить.

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

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

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

Вот код ........

.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)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

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

}

@end

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

Ответы [ 2 ]

8 голосов
/ 01 мая 2011

Так как уже завтра:
С Днем Рождения!

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

Первый

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

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

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

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 NSNumber утечек. Используйте [NSNumber numberWithInt:] вместо alloc / init в настройках shoutOutTimes.

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

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

А потом этот

NSTimeInterval lastCheckAt = 0.0; в глобальном масштабе. От вашего использования его: ивар PLZ?!? Один?

Больше вопросов позже. Сначала я должен что-нибудь съесть.


Часть вторая

Теперь перейдем к timerAction:

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

Далее: removeObserver:forKeyPath:

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

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

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

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 никогда / нигде не упоминается отдельно от его объявления.)

Резюме

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

0 голосов
/ 30 апреля 2011

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

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

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

[self.player requestThumbnailImagesAtTimes:[times autorelease] timeOption: MPMovieTimeOptionExact];
...