Xcode - iOS 内存泄漏让我发疯,我认为它是 NSNotificationCenter,但希望新的眼睛能看到我看不到的东西

发布于 2024-11-03 23:41:21 字数 10839 浏览 6 评论 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];
}

-(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),所以我正在尝试正确的事情并正确执行。

The code here is a modal view launched from the RootViewController and is to display a video with a thumbnail filmstrip below the movie and then timed instructions bound to the movie.

It all works, but there is a memory leak / lack of release that I just can't see for looking and having spent three days trying to fix it, the time has come to ask for help...

If I disable the NSNotificationCenter by commenting it out (highlighted in the .m) I don't have any issues regarding memory and keep the timed text. But I also don't have any thumbnails. I have tried inserting [[NSNotificationCenter alloc] removeObserver:self]; at numerous points to see if that will get rid of it for me. But alas, to no avail.

I have also tried releasing the 'backgroundTimer' but it's not overly impressed when I try to compile and run.

In essence, the first time I load the modal view, there are no issues whatsoever and all seems great - However, if I close it with the -(IBAction)close:(id)sender; it seems something isn't releasing as the next time I launch the same page the memory usage increases by about 30% (roughly the amount that is used by the thumbnail generation) and increases by roughly the same amount each time I re-launch the modal view.

Please bare in mind I am a newbie to this and the error is likely to a bloody stupid one to those of you in the know. But in the interest of getting this project done, I'll gladly take any abuse you fancy throwing at me.

Here's the code:

.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

This app is already out there heavily using UIWebView (which just plain sux) so I am trying to things right and do it properly.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

好倦 2024-11-10 23:41:21

您确实遇到了比一个泄漏更多的问题。

第一个

位于您的 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 不会发布太多 MPMovieDurationAvailableNotification。事实上,除了在 close: 中之外,我看不到 theTimer 被使用:这可能只是您引入 backgroundTimer 之前的无用残留物吗? ivar/@property? (嗯,还有另一个同名变量出现,但它应该伴随着一个巨大的编译器警告......)
  4. 您是否以任何方式实现了viewDidUnload?如果是这样,是否:
    1. self.player = nil;
    2. [shoutOutTexts 发布],shoutOutTexts = nil;
    3. [shoutOutTimes 发布],shoutOutTimes = nil;
    4. self.keyframeTimes = nil;
    5. [[NSNotificationCenter defaultCenter]removeObserver:self 名称:MPMovieDurationAvailableNotification 对象:nil];?
    6. self.backgroundTimer = nil;? (假设,setBackgroundTimer:释放并使旧值无效
  5. 更新我第一次就错过了这个:你正在泄漏 15 < code>NSNumber 在这里。在shoutOutTimes的设置中使用[NSNumber numberWithInt:]而不是alloc/init。

关于 movieURL 的一点小评论,您可以将其变成以下一行:

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

然后

在全局范围内使用此 NSTimeInterval lastCheckAt = 0.0;。从你的用法来看:ivar PLZ?!?one?

更多问题稍后再说。我得先给自己弄点吃的。


第二部分

现在让我们进入 timerAction:

第一个问题并不是太严重,尤其是在这个特定的上下文中,但是您应该知道 -[NSArray count] 返回一个NSUInteger 并且 U 不是拼写错误,而是表明该值是无符号的。您当然不会在此应用程序中遇到签名问题,并且在其他情况下也很少遇到这种情况,但您这样做时,它们会弥补非常奇怪的错误,您应该意识到其中的含义。 .
然而,这种方法的真正问题是,您每次迭代都会泄漏一个 CommentView,同时 - 过度释放一个 NSString

接下来:removeObserver:forKeyPath:

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

话虽这么说,摆脱整个这个方法!

首先也是最重要的 removeObserver:forKeyPath:NSKeyValueObserving 非正式协议中的一个方法,它所扮演的角色与您在这里使用它来完成的任务完全不同。
其次,它是那些必须调用 super 的方法之一(无论以何种方式),如果您确实需要重写它。 (嗯,除了,当你覆盖 addObserver:forKeyPath:options:context: 以及-it-should-go-without-saying-that-you-shouldn't-do-that-除非你真的知道你在做什么,如果你曾经计划使用 KVO。)

movieDurationAvailable:

就像 Evan 所说,你正在泄漏在这里。采纳他的建议,或者将其设为 NSMutableArray *times = [NSMutableArray array]; ,这样就完成了。

playerThumbnailImageRequestDidFinish:

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

makeThumbnailImageViewFromImage:andTimeCode:

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

dealloc

在最顶部添加 [[NSNotificationCenterdefaultCenter]removeObserver:self];

其余部分看起来还不错。 (尽管我觉得很奇怪,除了它的声明之外,ivar landscapeView 从未被提及过。)

摘要

阅读这些部分内存管理规则Autorelease 再次来自 Apple 的“内存管理编程指南”。它们是纯金的!

You do have a couple more issues than one leak.

The first one

is in your initWithNibName:bundle: as you aren't doing anything useful there: get rid of it, entirely! (Besides: don't release arguments, that are passed to your methods! Luckily, you've placed those releases in lines that are unreachable, i.e. after the return statement...)

Next method, next problems

  1. Why are you sending release to a class object? Don't! That's wrong on many levels.
  2. You have decided to create properties for your timers. That's nothing bad per se. But why then, are you using the ivars directly here? I'd strongly encourage you to implement setTheTimer: and setBackgroundTimer: to handle the invalidation and release properly and simply do self.theTimer = nil; self.backgroundTimer = nil; here. That would fix the asymmetry in handling those things, as well. (By the way: theTimer isn't such a great name for an ivar...especially when there is another ivar that is a timer!)

textInstructions: looks unsuspicious but...

viewDidLoad has some more issues

  1. It leaks an MPMoviePlayerController:
    The @property will retain it, so you need to balance the alloc here.
  2. backgroundTimer has a corresponding @property that is declared to be retaining: You are violating this API contract here, since you only assign the timer to the ivar. Use self.backgroundTimer = ... instead.
  3. From all the code you posted, it seems to me that passing theTimer as the last argument in your call to -[NSNotificationCenter addObserver:selector:name:object:] is a fancy way of passing in nil as that parameter. Which is kind of good, because usually NSTimer doesn't post too many MPMovieDurationAvailableNotifications. In fact, as I can't see theTimer being used except in close:: Could it be that this is just a useless remnant from before you introduced the backgroundTimer ivar/@property? (Well there is another occurrence of a variable of that name, but it should be accompanied by a big fat compiler warning...)
  4. Do you, in any way, implement viewDidUnload? If so, does it:
    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;? (Assuming, setBackgroundTimer: releases and invalidates the old value)
  5. Update I've missed this one on the first go: You are leaking 15 NSNumbers here. Use [NSNumber numberWithInt:] instead of alloc/init in the setup of shoutOutTimes.

A minor remark on movieURL, which you can turn into the following one-liner:

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

And then this

NSTimeInterval lastCheckAt = 0.0; within the global scope. From your usage of it: ivar PLZ?!?one?

More issues later. I gotta get myself something to eat first.


Part Two

Now let's get into timerAction:

The first issue is not too grave, — especially in this particular context — but you should be aware that -[NSArray count] returns an NSUInteger and that the U is not a typo, but a designation that this value is unsigned. You certainly won't run into problems with the signedness in this App and rarely do on other occasions, but when you do, they make up for really funky bugs and you should be aware of the implications...
The real issue with this method is, however, that you are leaking one CommentView per iteration while — at the same time — overreleasing one NSString. The fact, that you were using string literals (which will never be dealloced) in the first place, (i.e. when you were initializing shoutOutTimes) totally saves your butt, here.

Next up: removeObserver:forKeyPath:

You really should get rid of that really bad habit of releasing parameters, that are passed to your methods!

That being said, get rid of the entirety of this method!

First and foremost removeObserver:forKeyPath: is a method from the NSKeyValueObserving informal protocol and plays a totally different role than what you are (ab-)using it to accomplish here.
Secondly, it is one of those methods where it is essential to call through to super if — by any means — you really need to override it. (Well, except, when you were overriding addObserver:forKeyPath:options:context: as well and-it-should-go-without-saying-that-you-shouldn't-do-that-unless-you-really-know-what-you-are-doing-if-you-ever-planned-on-using-KVO.)

movieDurationAvailable:

Like Evan said, you're leaking times here. Go for his suggestion or — instead — make it NSMutableArray *times = [NSMutableArray array]; and you are done here.

playerThumbnailImageRequestDidFinish:

You don't own image, so don't release it!
Personally, I would finish setting up the view (i.e. add the recognizer and do stuff like that) before adding it into the view-hierarchy, but that's completely a matter of taste...

makeThumbnailImageViewFromImage:andTimeCode:

...leaks an NSNumber (use [NSNumber numberWithFloat:(pos * timeslice)] instead of the alloc/initWithFloat:-dance) and prevents you from crashing due to overreleasing myScrollView by the unconditional return statement that directly precedes it (phew!).
While we're at it: rename this method to either newThumbnailImageView... so that when you'll revisit this code in a year or so, you instantly know that [imageView release]; at the bottom of playerThumbnailImageRequestDidFinish: really is necessary, without having to look at the implementation of this method.
Alternatively, you could rename it to thumbnailImageView... and change the return statement to return [imageView autorelease];. Bonus: one fewer line in playerThumbnailImageRequestDidFinish: as the [imageView release]; there becomes obsolete then.

dealloc

Add [[NSNotificationCenter defaultCenter] removeObserver:self]; at the very top.

The rest of it looks okay. (Although I find it odd, that the ivar landscapeView is never/nowhere mentioned apart from its declaration.)

Summary

Read the sections Memory Management Rules and Autorelease from Apple's "Memory Management Programming Guide" once again. They're pure gold!

暖树树初阳… 2024-11-10 23:41:21

您永远不会在 movieDurationAvailable: 中释放 times

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

将其传递给方法时应该使用 autorelease

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

You never release times in movieDurationAvailable::

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

You should use autorelease when you pass it to the method:

[self.player requestThumbnailImagesAtTimes:[times autorelease] timeOption: MPMovieTimeOptionExact];
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文