Xcode - утечка памяти iOS, которая сводит меня с ума, я думаю, что это nsnotificationCenter, но надеясь, что свежие глаза смогут увидеть, что я не могу
-
27-10-2019 - |
Вопрос
Код здесь представляет собой модальный вид, запущенный из 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:
Поскольку вы не делаете ничего полезного: избавьтесь от этого, полностью! (Кроме того: не выпускайте аргументы, которые передаются вашим методам! К счастью, вы разместили эти релизы в строках, которые недоступны, т.е. после операции возврата ...)
Следующий метод, следующие проблемы
- Почему вы отправляете
release
к объекту класса? Не! Это неправильно на много уровни - Вы решили создать свойства для своих таймеров. Это ничего плохого само по себе. Но почему же вы используете Ivars прямо здесь? Я настоятельно рекомендую вам реализовать
setTheTimer:
а такжеsetBackgroundTimer:
Чтобы справиться с аннулированием и правильно выпустить и просто сделатьself.theTimer = nil; self.backgroundTimer = nil;
здесь. Это также исправят асимметрию в обращении с этими вещами. (Кстати: Thetimer не такой отличное имя для ivar ... особенно когда есть еще один Ивар, это таймер!)
textInstructions:
Выглядит непреодолимо, но ...
viewDidLoad
Есть еще несколько проблем
- Это утечка MpmoviePlayerController:
А@property
сохранит это, поэтому вам нужно сбалансироватьalloc
здесь. backgroundTimer
имеет соответствующий@property
Это объявляется, что удерживает: вы нарушаете этот контракт API здесь, поскольку вы только назначаете таймер ivar. Использоватьself.backgroundTimer = ...
вместо.- Из всего кода, который вы опубликовали, мне кажется, что прохождение
theTimer
как последний аргумент в вашем призыве к-[NSNotificationCenter addObserver:selector:name:object:]
это причудливый способ проходитьnil
как этот параметр. Что довольно хорошо, потому что обычноNSTimer
не публикует слишком многоMPMovieDurationAvailableNotification
с На самом деле, как я не вижуtheTimer
Используется, кроме как вclose:
: Может быть, это всего лишь бесполезный остаток, прежде чем вы представилиbackgroundTimer
ivar/@собственность? (Ну, есть еще одно появление переменной этого имени, но она должна сопровождаться предупреждением о большом жирном компиляторе ...) - Вы в любом случае реализуете
viewDidUnload
? Если так, делает это:self.player = nil;
?[shoutOutTexts release], shoutOutTexts = nil;
?[shoutOutTimes release], shoutOutTimes = nil;
?self.keyframeTimes = nil;
?[[NSNotificationCenter defaultCenter] removeObserver:self name: MPMovieDurationAvailableNotification object:nil];
?self.backgroundTimer = nil;
? (При условии,setBackgroundTimer:
выпуски и недействительно старое значение)
- Обновлять Я пропустил это на первом ходе: ты протекаешь 15
NSNumber
S здесь. Использовать[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];