title: Xcode - iOS Memory leak that is driving me crazy I think it's NSNotificationCenter, but hoping fresh eyes can see what I can't
body: <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  <code>[[NSNotificationCenter alloc] removeObserver:self];</code> 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 <code>-(IBAction)close:(id)sender;</code> 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>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)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.</p>