Note that there are some explanatory texts on larger screens.

plurals
  1. POXCODE - iOS Memory leak that is driving me crazy I think it's NSNotificationCenter, but hoping fresh eyes can see what I can't
    text
    copied!<p>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.</p> <p>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...</p> <p>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.</p> <p>I have also tried releasing the 'backgroundTimer' but it's not overly impressed when I try to compile and run.</p> <p>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.</p> <p>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. </p> <p>Plus, it's my birthday tomorrow and if I can get this sorted, it would (rather sadly) be the best gift I could have!</p> <p>Here's the code........</p> <p>.h</p> <pre><code>#import &lt;UIKit/UIKit.h&gt; #import &lt;MediaPlayer/MPMoviePlayerController.h&gt; #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 </code></pre> <p>.m</p> <pre><code>#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]; } </code></pre> <p>—</p> <pre><code>-(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 } </code></pre> <p>—</p> <pre><code>- (NSInteger)positionFromPlaybackTime:(NSTimeInterval)playbackTime { NSInteger position = 0; for (NSNumber *startsAt in shoutOutTimes) { if (playbackTime &gt; [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; </code></pre> <hr> <pre><code>- (void)timerAction: theTimer { int count = [shoutOutTimes count]; NSInteger position = [self positionFromPlaybackTime:self.player.currentPlaybackTime]; NSLog(@"position is at %d", position); if (position &gt; 0) { --position; } if (position &lt; 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 &lt; time &amp;&amp; self.player.currentPlaybackTime &gt;= 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; } </code></pre> <hr> <pre><code>// 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 &lt; 20; i++) { float playbackTime = i * duration/20; [times addObject:[NSNumber numberWithInt:playbackTime]]; } [self.player requestThumbnailImagesAtTimes:times timeOption: MPMovieTimeOptionExact]; } </code></pre> <hr> <pre><code>- (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]; } </code></pre> <hr> <pre><code>- (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)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } - (void)dealloc { [player release]; [viewForMovie release]; [onScreenDisplayLabel release]; [keyframeTimes release]; [instructions release]; [shoutOutTexts release]; [shoutOutTimes release]; [super dealloc]; } @end </code></pre> <p>This app is already out there heavily using UIWebView (which just plain sux) so I am trying to things right and do it properly.... Thanks in advance!</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload