title: UITableView crashes after (apparently) failing to ask delegate for numberOfRowsInSection
body: <p>There are a lot of questions here about <code>UITableViewController</code>s crashing when the number of rows returned by<code>numberOfRowsInSection</code> isn't consistent with the datasource.</p>
  
  <p>My app is crashing because of this problem -- but I can't see why. To add to the confusion, it crashes on the real iPhone and iPhone Simulator but works perfectly on a (real) iPad.</p>
  
  <p>This is the error:</p>
  
  <pre><code>2013-04-05 14:38:15.644 JobTime[28484:c07] *** Terminating app due to uncaught
  exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]:
  index 3 beyond bounds [0 .. 2]'
  </code></pre>
  
  <p>It's right -- the NSArray only extends to 2.</p>
  
  <p>What's odd is that the TableView doesn't seem to be asking my controller (which is set as its delegate) for the number of rows in the section before it refreshes itself. This is my method:</p>
  
  <pre><code>- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  {
      NSLog(@"called numberOfRowsInSection");
      return [[[JobStore sharedStore] allJobs] count];
  }
  </code></pre>
  
  <p>Here's the sequence for the crash on the iPhone Simulator (but not iPad):</p>
  
  <ol>
  <li>TableView working properly, everything performing as expected, NSLog message above pops up regularly.</li>
  <li>Click 'add new item' button.</li>
  <li>Add new item to the JobStore (a singleton containing the NSArray of jobs).</li>
  <li>Open a modal navigationController (UIModalPresentationFormSheet) to edit that new item.</li>
  <li>Cancel the edit -- and remove the unwanted item from the JobStore.</li>
  </ol>
  
  <p>Then the crash:</p>
  
  <ol>
  <li>The TableView updates itself without calling numberOfRowsInSection (i.e. the NSLog message above doesn't appear), so it tries to retrieve an object from the JobStore that is outside the bounds of the array.</li>
  </ol>
  
  <p>I've added an NSLog to the dismissBlock that's executed on completion of the ViewController being dismissed:</p>
  
  <pre><code>[jobDetailViewController setDismissBlock:^{
      NSLog(@"dismissBlock");
      [[self tableView] reloadData];
      [self clearTableViewBackgroundColorAndResetHighlight];
  }];
  </code></pre>
  
  <p>That's executed when I choose 'done' and save the item from the modal view, but <b>not</b> when I choose 'cancel'. The line of code is the same in both methods, and I know that the correct methods are being called for each button:</p>
  
  <pre><code>[[self presentingViewController] dismissViewControllerAnimated:YES completion:dismissBlock];
  </code></pre>
  
  <p>The same is true if I simplify the dismissBlock to just an NSLog statement and no other calls: it doesn't get called in the 'cancel' case.</p>
  
  <p>The only solution I've found is the rather inelegant one of re-checking the bounds of the array in cellForRowAtIndexPath:</p>
  
  <pre><code>- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  {
      UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
  
      if (!cell) {
          cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
                                    reuseIdentifier:@"UITableViewCell"];
      }
  
      // Check array bounds before trying to retrieve object from array
  
      if ([indexPath row] &lt; [self tableView:tableView numberOfRowsInSection:0]) {
          Job *j = [[[JobStore sharedStore] allJobs] objectAtIndex:[indexPath row]];
          [[cell textLabel] setText:[j displayDescription]];
          [[cell detailTextLabel] setText:[j timeDescription]];
  
          if ([j selected]) {
              [cell setBackgroundColor:[UIColor lightGrayColor]];
          }
      } else {
          [[cell textLabel] setText:@""];
          NSLog(@"gone past array bounds");
      }
      return cell;
  }
  </code></pre>
  
  <p>That works, but it's clumsy. Any idea why the TableView doesn't seem to ask for the updated number of rows before it refreshes itself? Or why it works on an iPad, but not an iPhone?</p>
  
  <p>I'm beginning to suspect some quirk of the different modal views but would be grateful for any help. Thanks in advance.</p>
  
  <p>James</p>