0

此处的代码是从 RootViewController 启动的模态视图,用于显示带有缩略图幻灯片的视频,然后将定时指令绑定到该电影。

一切正常,但是有一个内存泄漏/缺乏释放,我只是看不到寻找并花了三天时间试图修复它,是时候寻求帮助了......

如果我通过注释掉 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

你确实有比一个泄漏更多的问题。

第一个

就在你initWithNibName:bundle:身上,因为你在那里没有做任何有用的事情:完全摆脱它!(此外:不要发布传递给您的方法的参数!幸运的是,您已将这些发布放在无法访问的行中,即在 return 语句之后......)

下一个方法,下一个问题

  1. 为什么要发送release到类对象?不!这在很多层面上都是错误的。
  2. 您已决定为您的计时器创建属性。这本身没什么不好。但是为什么你要直接在这里使用 ivars 呢?我强烈建议您实施setTheTimer:setBackgroundTimer:正确处理失效和释放,只需在self.theTimer = nil; self.backgroundTimer = nil;此处执行即可。这也将解决处理这些事情的不对称性。(顺便说一句:theTimer 对 ivar 来说并不是一个好名字……尤其是当有另一个ivar 是计时器时!)

textInstructions:看起来毫无疑问,但...

viewDidLoad还有一些问题

  1. 它泄漏了一个 MPMoviePlayerController:
    @property保留它,所以你需要在alloc这里平衡。
  2. backgroundTimer有一个@property声明为保留的对应:您在这里违反了此 API 合同,因为您只将计时器分配给 ivar。改为使用self.backgroundTimer = ...
  3. 从您发布的所有代码中,在我看来,theTimer作为调用中的最后一个参数-[NSNotificationCenter addObserver:selector:name:object:]传递是作为该参数传递的一种奇特方式nil。这有点好,因为通常NSTimer不会发布太多MPMovieDurationAvailableNotifications。事实上,我看不到theTimer除了 in 之外的其他用途close::难道这只是您介绍backgroundTimerivar/@property 之前的无用残余吗?(好吧,该名称的变量又出现了一次,但它应该伴随着一个大胖编译器警告......)
  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 秒。[NSNumber numberWithInt:]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.事实上,您一开始就使用字符串文字(永远不会被释放)(即,当您初始化shoutTimes 时)完全节省了您的屁股,在这里。

接下来:removeObserver:forKeyPath:

真的应该改掉释放参数的坏习惯,这些参数传递给你的方法!

话虽如此,摆脱整个这种方法!

首先也是最重要removeObserver:forKeyPath:的是一种来自NSKeyValueObserving非正式协议的方法,它所扮演的角色与你在这里(ab-)使用它来完成的角色完全不同。其次,它是必须调用superif 的方法之一——无论如何——你真的需要重写它。(好吧,除了,当你也压倒一切时addObserver:forKeyPath:options:context:,它应该不说你不应该那样做,除非你真的知道你在做什么-if-you-ever-planned-on-using-KVO.)

movieDurationAvailable:

就像埃文说的,你在times这里漏水了。去听他的建议,或者——相反——提出来NSMutableArray *times = [NSMutableArray array];,你就完成了。

playerThumbnailImageRequestDidFinish:

你不拥有image,所以不要释放它!
就个人而言,我会在将视图添加到视图层次结构之前完成视图的设置(即添加识别器并执行类似的操作),但这完全是品味问题......

makeThumbnailImageViewFromImage:andTimeCode:

...泄漏一个NSNumber(使用[NSNumber numberWithFloat:(pos * timeslice)]而不是alloc/initWithFloat:-dance)并防止您由于myScrollView直接在它之前的无条件返回语句过度释放而崩溃(呸!)。当我们这样做时:将这个方法重命名为 any newThumbnailImageView...,这样当你在一年左右重新访问这段代码时,你会立即知道[imageView release];在底部playerThumbnailImageRequestDidFinish:真的是必要的,而不必查看这个方法的实现。
或者,您可以将其重命名为thumbnailImageView...并将返回语句更改为return [imageView autorelease];. playerThumbnailImageRequestDidFinish:奖励:随着[imageView release];那里变得过时,少一行。

dealloc

[[NSNotificationCenter defaultCenter] removeObserver:self];在最顶部添加。

其余的看起来还可以。(虽然我觉得很奇怪,landscapeView除了它的声明之外,从来没有/没有提到过 ivar。)

概括

再次阅读 Apple 的“内存管理编程指南”中的内存管理规则自动释放部分。它们是纯金的!

于 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 回答