0

ここでのコードは、RootViewController から起動されるモーダル ビューであり、ムービーの下にサムネイル フィルムストリップを含むビデオを表示し、次にムービーにバインドされた時間指定された指示を表示します。

それはすべて機能しますが、メモリリーク/リリースの欠如があり、それを見て、3日間かけて修正しようとしただけではわかりません。助けを求める時が来ました...

コメントアウトして NSNotificationCenter を無効にすると (.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];
}

—</p>

-(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
}

—</p>

- (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) を大量に使用しているため、正しく実行しようとしています。

4

2 に答える 2

8

1 つのリークよりもいくつかの問題があります。

最初の1つ

あなたinitWithNibName:bundle:はそこで何の役にも立たないので、あなたの中にあります:それを完全に取り除いてください!(さらに: メソッドに渡された引数を解放しないでください! 幸いなことに、これらの解放は到達できない行、つまり return ステートメントの後に配置されています...)

次の方法、次の問題

  1. releaseクラスオブジェクトに送信するのはなぜですか? しないでください!それは多くのレベルで間違っています。
  2. タイマーのプロパティを作成することにしました。それ自体は何も悪いことではありません。しかし、なぜここで ivar を直接使用しているのですか? 無効化とリリースを適切に実装setTheTimer:して処理し、ここで行うことを強くお勧めします。これにより、それらの処理の非対称性も修正されます。(ちなみに: theTimer は ivar の名前としてはあまり適切ではありません...特にタイマーである別のivar がある場合は!)setBackgroundTimer:self.theTimer = nil; self.backgroundTimer = nil;

textInstructions:不審に見えますが...

viewDidLoadさらにいくつかの問題があります

  1. MPMoviePlayerController がリークします。
    これ@propertyは保持されるため、ここでバランスを取る必要がありallocます。
  2. backgroundTimer保持すると宣言された対応する@propertyものがあります:タイマーを ivar に割り当てるだけなので、ここでこの API コントラクトに違反しています。self.backgroundTimer = ...代わりに使用してください。
  3. theTimerあなたが投稿したすべてのコードから、呼び出しの最後の引数として渡すことは、そのパラメーターとして-[NSNotificationCenter addObserver:selector:name:object:]渡すための派手な方法であるように思えます。通常、あまり多くの sを投稿しないnilため、これは良いことです。実際、次の場合以外は使用されていないように見えますが、これは、 ivar/@propertyを導入する前の役に立たない残り物ではないでしょうか? (まあ、その名前の変数の別の出現がありますが、大きな太ったコンパイラ警告が伴うはずです...)NSTimerMPMovieDurationAvailableNotificationtheTimerclose:backgroundTimer
  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. 更新NSNumber最初にこれを見逃しました:ここで 15 sをリークしています。[NSNumber numberWithInt:]のセットアップで alloc/init の代わりに使用しますshoutOutTimes

movieURL次のワンライナーに変えることができるに関するマイナーな発言:

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

そして、これ

NSTimeInterval lastCheckAt = 0.0;グローバルスコープ内。あなたの使い方から: ivar PLZ?!?one?

さらに問題は後で。私は最初に何か食べるものを手に入れなければなりません。


パート2

さあ、入りましょうtimerAction:

最初の問題は、特にこの特定のコンテキストではそれほど重大ではありませんが、 が-[NSArray count]返す値NSUIntegerUはタイプミスではなく、この値が符号なしであることを示していることに注意する必要があります。 あなたは確かにこのアプリのサインで問題に遭遇することはなく、他の機会にはめったに起こりませ.
ただし、反復ごとに1 つをリークCommentViewしていると同時に、1 つを過剰に解放しているということですNSString最初に文字列リテラル (解放されることはありません) を使用していたという事実 (つまり、shoutOutTimes を初期化していたとき) は、ここであなたのお尻を完全に節約します。

次は:removeObserver:forKeyPath:

メソッドに渡されたパラメーターを解放するという本当に悪い習慣を本当に取り除く必要があります。

そうは言っても、このメソッド全体を削除してください!

何よりもまずremoveObserver:forKeyPath:、非公式プロトコルのメソッドであり、NSKeyValueObservingここで達成するために (悪用) 使用しているものとはまったく異なる役割を果たします。第二に、これは、どうしてもオーバーライドする必要があるsuper場合に呼び出すことが不可欠なメソッドの 1 つです。(まあ、あなたがオーバーライドしていて、あなたが何をしているのかを本当に知っていない限り、あなたはそれをすべきではないと言わずに行くべきである場合を除いて-KVO を使用する予定がある場合)。addObserver:forKeyPath:options:context:

movieDurationAvailable:

エヴァンが言ったように、あなたはtimesここで漏れています。彼の提案に従うか、代わりにそれNSMutableArray *times = [NSMutableArray array];を実行すれば、ここで完了です。

playerThumbnailImageRequestDidFinish:

あなたは を所有imageしていないので、リリースしないでください!
個人的には、ビュー階層に追加する前にビューのセットアップを終了します (つまり、レコグナイザーを追加してそのようなことを行います) が、それは完全に好みの問題です...

makeThumbnailImageViewFromImage:andTimeCode:

...をリークしNSNumber( -dance[NSNumber numberWithFloat:(pos * timeslice)]の代わりに使用)、その直前にある無条件の return ステートメントによる過剰解放によるクラッシュを防ぎます (ふー!)。その間、このメソッドの名前を「どちらか」に変更して、1 年かそこらでこのコードを再確認するときに、このメソッドの実装を確認しなくても、「」が本当に必要であることがすぐにわかるようにします。または、名前をに変更し、return ステートメントを に変更 することもできます。おまけ:が時代遅れになるため、 1 行少なくなります。alloc/initWithFloat:myScrollViewnewThumbnailImageView...[imageView release];playerThumbnailImageRequestDidFinish:
thumbnailImageView...return [imageView autorelease];playerThumbnailImageRequestDidFinish:[imageView release];

dealloc

一番上に追加[[NSNotificationCenter defaultCenter] removeObserver:self];します。

残りは問題ないようです。(奇妙だと思いますが、ivarlandscapeViewがその宣言を除いてまったく/どこにも言及されていないことは確かです。)

概要

Apple の「Memory Management Programming Guide」の「Memory Management Rules 」と「 Autorelease 」のセクションをもう一度読んでください。純金です!

于 2011-05-01T09:38:43.767 に答える
0

あなたは決してリリースtimesしませんmovieDurationAvailable:

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

autoreleaseメソッドに渡すときに使用する必要があります。

[self.player requestThumbnailImagesAtTimes:[times autorelease] timeOption: MPMovieTimeOptionExact];
于 2011-04-30T19:08:36.400 に答える