Xcode -私を狂わせているiOSメモリリーク私はそれがnsnotificationcenterだと思いますが、新鮮な目が私ができないものを見ることができることを願っています
-
27-10-2019 - |
質問
ここのコードは、rootViewControllerから開始されたモーダルビューであり、映画の下にサムネイルフィルムストリップが付いたビデオを表示し、映画に縛られた指示をタイミングします。
それはすべてうまくいきますが、それを修正しようとして3日間過ごしたことがわからない記憶の漏れ /リリースの欠如があり、助けを求める時が来ました...
nsnotificationCenterをコメントしてnsnotificationCenterを無効にした場合(.mで強調表示されています)、メモリに関して問題はありません。しかし、私にはサムネイルもありません。私は挿入を試みました [[NSNotificationCenter alloc] removeObserver:self];
それが私のためにそれを取り除くかどうかを確認するために多くのポイントで。しかし、悲しいかな、役に立たない。
また、「BackgroundTimer」をリリースしようとしましたが、コンパイルして実行しようとすると、あまり感動しません。
本質的に、モーダルビューを初めてロードするとき、問題はまったくありません。 -(IBAction)close:(id)sender;
次回同じページを起動したときに、メモリの使用量が約30%増加し(サムネイルの発生が使用する量)、メモリの使用量が約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)dealloc {
[player release];
[viewForMovie release];
[onScreenDisplayLabel release];
[keyframeTimes release];
[instructions release];
[shoutOutTexts release];
[shoutOutTimes release];
[super dealloc];
}
@end
このアプリはすでにuiwebview(これは単なるSUX)を使用しているので、私は正しいことをしようとしています。
解決
より多くの問題があります 1 リーク。
最初の1つ
あなたの中にあります initWithNibName:bundle:
あなたはそこで役に立つことをしていないので、それを完全に取り除きましょう! (さらに:あなたの方法に渡される議論をリリースしないでください!幸いなことに、あなたはそれらのリリースを到達不可能な行、つまり戻り声明の後に配置しました...)
次の方法、次の問題
- なぜあなたは送っているのですか
release
クラスオブジェクトに?しないで!それは間違っています たくさんの レベル。 - タイマーのプロパティを作成することにしました。それ自体は悪いことではありません。しかし、なぜ、ここでivarsを直接使用しているのですか?実装することを強くお勧めします
setTheTimer:
とsetBackgroundTimer:
無効化を処理し、適切に解放し、単に実行するself.theTimer = nil; self.backgroundTimer = nil;
ここ。それは、これらのものを処理する際にも非対称性を修正します。 (ちなみに:Thetimerはそうではありません そのような ivarの素晴らしい名前...特にあるとき 別 タイマーであるivar!)
textInstructions:
疑いの余地がないが...
viewDidLoad
さらにいくつかの問題があります
- mpmovieplayercontrollerを漏らします:
@property
それを保持するので、バランスをとる必要がありますalloc
ここ。 backgroundTimer
対応するものがあります@property
それは保持されていると宣言されています:あなたはIVARにタイマーを割り当てるだけなので、ここでこのAPI契約に違反しています。使用するself.backgroundTimer = ...
代わりは。- あなたが投稿したすべてのコードから、私には合格しているように思えます
theTimer
あなたの呼びかけの最後の議論として-[NSNotificationCenter addObserver:selector:name:object:]
通り過ぎる派手な方法ですnil
そのパラメーターとして。これは、通常は良いことですNSTimer
あまり投稿しませんMPMovieDurationAvailableNotification
s。実際、私が見ることができないように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
, 、これを次の1ライナーに変えることができます。
-(NSURL*)movieURL {
return [[NSBundle mainBundle] URLForResource:@"sirloin" withExtension:@"m4v"];
}
そして、これ
NSTimeInterval lastCheckAt = 0.0;
グローバル範囲内。あなたの使用から:ivar plz?!?one?
後でより多くの問題。私は最初に何か食べるものを自分自身に手に入れなければなりません。
パート2
さあ、入りましょう timerAction:
最初の問題は、特にこの特定の文脈において、あまり重大ではありませんが、あなたはそれに注意する必要があります -[NSArray count]
andを返します NSUInteger
そしてそれ u タイプミスではなく、この値が署名されていないという指定です。あなたは確かにこのアプリの署名に問題が発生することはなく、他の機会にはめったに行われませんが いつ あなたはそうする、彼らは本当にファンキーなバグを補い、あなたはその意味に注意する必要があります...
ただし、この方法の本当の問題は、あなたが 漏れている CommentView
反復ごと 間 - 同時に - 1つを過剰に解放します NSString
. そもそも文字列リテラル(決して扱われない)を使用していたという事実(つまり、Soutouttimesを初期化していたとき)は完全にあなたの尻を保存します。
次に: removeObserver:forKeyPath:
君は 本当 あなたの方法に渡されるパラメーターをリリースするという本当に悪い習慣を取り除くべきです!
そうは言っても、この方法全体を取り除いてください!
何よりもまず removeObserver:forKeyPath:
からの方法です NSKeyValueObserving
非公式のプロトコルと、ここで達成するためにそれを使用している(ab-)とはまったく異なる役割を果たします。第二に、それは、 super
もしか - 何でも - あなた 本当 オーバーライドする必要があります。 (まあ、あなたがオーバーライドしていたときを除いて addObserver:forKeyPath:options:context:
同様に、そしてそれは、あなたがしていないので、それはそれをしていないでしょう、それはあなたがすることではなく、あなたは何をしているのか、あなたは何をしていないのですか? in-using-kvo。)
movieDurationAvailable:
エヴァンが言ったように、あなたは漏れています times
ここ。彼の提案に行くか、 - 代わりに - それを作る NSMutableArray *times = [NSMutableArray array];
そして、あなたはここで終わります。
playerThumbnailImageRequestDidFinish:
あなたは所有していません image
, 、それをリリースしないでください!
個人的には、ビュー階層に追加する前にビューのセットアップを終了します(つまり、そのようなものを追加します)が、それは完全に味の問題です...
makeThumbnailImageViewFromImage:andTimeCode:
...漏れます NSNumber
(使用する [NSNumber numberWithFloat:(pos * timeslice)]
の代わりに alloc/initWithFloat:
-dance)および過剰なリリースのためにクラッシュするのを防ぎます myScrollView
直前の無条件のリターンステートメントによって(phew!)。私たちがそれになっている間:この方法をどちらに変更しますか newThumbnailImageView...
そのため、1年ほどでこのコードを再訪すると、すぐにそれを知っています [imageView release];
の一番下に playerThumbnailImageRequestDidFinish:
この方法の実装を見ることなく、本当に必要です。
または、に変更することもできます thumbnailImageView...
returnステートメントを変更します return [imageView autorelease];
. 。ボーナス:1回のラインが少ない 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];