UITableView, Background thread and NSRangeException

Discussion in 'iOS Programming' started by Sam77, Sep 14, 2010.

  1. Sam77 macrumors newbie

    Joined:
    Aug 17, 2010
    #1
    I've got some inconsistencies with the app UIEvents (Scrolling, Reloading) and backgroundthread work. Occasional "NSRangeException" is kinda annoying, though not life threatning, nevertheless, for the sake of good code,

    Here is where the conflict is showing up. I occasionally keep getting "Index beyond bounds" error.

    This specially occurs when:
    I tap a button, that fills the tableView. I scroll down to the last row. While the tableView scrolls, I tap another button that reloads the tableView with Lesser data(rows) and thats where the error comes out! Sometimes it works, sometimes i get the following:

    Here is the IBAction:
    1- Sqlite querying via NSOperation.
    2- After getting data in an array the following method is called in MainThread in AppDelegate

    Code:
    [self.mainController performSelectorOnMainThread:@selector(cacheArray:) withObject:dataArray waitUntilDone:NO];
    

    Code:
    - (void) cacheArray:(NSArray *)CurentArray{
    	self.currMainArray = CurentArray;
            if(CurrentArray) [tableView reloadData];
    }

    Code:
     Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSMutableArray objectAtIndex:]: index 33 beyond bounds [0 .. 14]
    Code:
    (
    	0   CoreFoundation                      0x36440303 __exceptionPreprocess + 114
    	1   libobjc.A.dylib                     0x3523d4c4 objc_exception_throw + 40
    	2   CoreFoundation                      0x363c1751 -[__NSArrayM objectAtIndex:] + 184
    	3   MyAppN                             0x0000af2b -[MainViewController tableView:cellForRowAtIndexPath:] + 156
    	4   UIKit                               0x32405250 -[UITableView(UITableViewInternal) _createPreparedCellForGlobalRow:withIndexPath:] + 652
    	5   UIKit                               0x32404eb4 -[UITableView(UITableViewInternal) _createPreparedCellForGlobalRow:] + 52
    	6   UIKit                               0x323cb488 -[UITableView(_UITableViewPrivate) _updateVisibleCellsNow:] + 1308
    	7   UIKit                               0x323c8e48 -[UITableView layoutSubviews] + 208
    	8   UIKit                               0x32370ab8 -[UIView(CALayerDelegate) _layoutSublayersOfLayer:] + 40
    	9   CoreFoundation                      0x363c85c1 -[NSObject(NSObject) performSelector:withObject:] + 24
    	10  QuartzCore                          0x30c8c624 -[CALayer layoutSublayers] + 184
    	11  QuartzCore                          0x30c8c2ac CALayerLayoutIfNeeded + 200
    	12  QuartzCore                          0x30c8bbb8 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 264
    	13  QuartzCore                          0x30c8b7e0 _ZN2CA11Transaction6commitEv + 284
    	14  QuartzCore                          0x30c939e0 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 88
    	15  CoreFoundation                      0x3641424b __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 18
    	16  CoreFoundation                      0x36415da5 __CFRunLoopDoObservers + 500
    	17  CoreFoundation                      0x364172fd __CFRunLoopRun + 940
    	18  CoreFoundation                      0x363be0c3 CFRunLoopRunSpecific + 226
    	19  CoreFoundation                      0x363bdfd1 CFRunLoopRunInMode + 60
    	20  GraphicsServices                    0x33c4cf90 GSEventRunModal + 196
    	21  UIKit                               0x32363b48 -[UIApplication _run] + 572
    	22  UIKit                               0x32361fc0 UIApplicationMain + 972
    	23  MyAppN                             0x00002e39 main + 80
    	24  MyAppN                             0x00002db0 start + 52
    )
    
    Particularly "updateVisibleCells" gives me a clue to the problem, but im not sure what exactly it is.

    Code:
    	6   UIKit                               0x323cb488 -[UITableView(_UITableViewPrivate) _updateVisibleCellsNow:] + 1308
    Im not sure what do I do here. This error is eating my brain! hellppp
     
  2. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #2
    Do not ever update the UI from background thread: all updates must be done on the main thread.
     
  3. Sam77 thread starter macrumors newbie

    Joined:
    Aug 17, 2010
    #3
    Yes, I updated tableView in the main thread.


    Code:
    [self.mainController performSelectorOnMainThread:@selector(cacheArray:) withObject:dataArray waitUntilDone:NO];
    
    //in MainController
    - (void) cacheArray:(NSArray *)CurentArray{
    	self.currMainArray = CurentArray;
            if(CurrentArray) 	[tableView performSelectorOnMainThread:@selector(reloadData)  withObject:nil waitUntilDone:YES];
    }
    The same error continues. SIGABRT NSRangeException.
    with similar description above.

    Does the UIEvents that lead upto the error give clues??
    A tableView with More than 30 rows of data when scrolled down, while the tableView is scrolling (in motion), tapping another button with LESS data (translating to less Rows) causes this error.
     
  4. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #4
    Well for whatever reason you are trying to access an object at index 33 of an array that only contains 15 objects.

    As you have only posted tiny snippets of code we can't really say much more than that. The crash is happening in tableView:cellForRowAtIndexPath: which you have not posted. I would suggest checking that method and making sure you are not using a stale array reference.
     
  5. Sam77 thread starter macrumors newbie

    Joined:
    Aug 17, 2010
    #5
    Also, this error only occurs when tableView is scrolling.

    Before error, the TableView has 35 rows.
    The new array now has 14 rows.

    When the tableView is not scrolling, theres no problem. Everything works smoothly.

    I deduced that this is the problem only due to scrolling.

    During scroll if tableView crosses more than 15 rows, i guess this error pops up cuz the new array that just came in(from the background thread) has only 14 rows worth data.

    Maybe the trick is to stop scrolling?? I'll try this out.

    thanks for your replies robbieduncan. Im hoping theres nothing wrong with cellForRowAtIndexPath. Cuz these events work fine when tableView is not scrolling. Any suggestions?
     
  6. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #6
    If you are in the middle of a scroll event when you call reload data does the table view actually reload the data? Or does it wait until the scroll completes? Put a NSLog in the method that returns the number of rows. I would bet that it does not get called until the scroll completes which is why the table is trying to read data for 25 rows. But you have replaced your array with one with only 14 rows so it crashes.
     
  7. Sam77 thread starter macrumors newbie

    Joined:
    Aug 17, 2010
    #7
    Yes, you are right.

    Heres the Log:

    Code:
                                                                Check array count=34 index=17
    2010-09-14 19:02:44.887 MyAppN[1657:307] Check array count=34 index=18
    2010-09-14 19:02:45.022 MyAppN[1657:6013] new ArrayCount= 15
    2010-09-14 19:02:45.210 MyAppN[1657:307] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSMutableArray objectAtIndex:]: index 19 beyond bounds [0 .. 14]'
    Its pretty clear now. While scrolling through an array with 34 count, thru cell index 17-18-... and SuDDENLY, there is a new array 15!! How in the world could the reloaded Tableview scrolll to Index 19!

    seems like we figured it out! thanks!

    For the solution though, im trying to stop the scrolling table and reload it. Atleast scroll it to the top. Yet I see no results almost the same error.

    Code:
    - (void) cacheArray:(NSArray *)CurentArray{
    	//NSLog(@"im in cachearr");
    	if (CurentArray) 
    		
    	{ 
    		[tableView scrollRectToVisible:CGRectMake(0.0, 0.0, 1.0, 1.0) animated:NO]; 
     //After scrolling to the top,
    		self.currMainArray = CurentArray;
    		[tableView performSelectorOnMainThread:@selector(reloadData)  withObject:nil waitUntilDone:YES];
    	}
    

    Little improvement, the error continues!
     
  8. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #8
    The array that you are passing from the background thread to the main thread: is this a new array that was created in the background thread or is it an old array that comes from the object table view controller?

    Why do you have a performSelectorOnMainThread in your cacheArray: method? Isn't this method already performed on the main thread? If this method is really running on the main thread this may be the cause of the problem. Call reloadData directly.
     
  9. Sam77 thread starter macrumors newbie

    Joined:
    Aug 17, 2010
    #9
    This array is in appDelegate. Not from the tableView.

    The tableView data source is within its ViewController (mainViewController). And the data is passed via cacheArray: (in mainViewController).

    Actually I used reloadData directly. If you saw the code in the first post. I used reloadData.

    I mayhav changed it to make that point that I'm doin it in main thread. my bad!
     
  10. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #10
    You shouldn't need to worry about stopping the scrolling or moving it to the top. That is barking up the wrong tree. Concentrate on getting the [tableView reloadData] to work properly.
     
  11. Sam77 thread starter macrumors newbie

    Joined:
    Aug 17, 2010
    #11
    How exactly should I proceed? [tableView reloadData] does work properly, its only this index beyond bounds issue. And this occurs when things are going fast on the UI. Should I relook at the NSOperation procedure? that calls the cacheArray: on MainThread?

    I really am without a clue here.
     
  12. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #12
    You can know when the scrolling operation is in progress and when it finishes. Defer the setting of the array and reload of the table until the scroll finishes.
     
  13. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #13
    I am dubious that calling reloadData when the table is scrolling is a problem. I have some code that does something very similar to what you're doing and I've never seen a problem or gotten a bug report about it. Mine is different in that it adds rows from a database asynchronously, while yours seems to be removing rows.

    I've never heard of this problem before. If it was a general problem we would have heard of it.

    Here's how this is supposed to work. On a background thread you create a new array and fill it with data. Then you call a method on the main thread. This method on the main thread updates the data model and calls reloadData. There can be no delay between updating the data model and calling reloadData.

    I really doubt that scrolling while this is happening is a general problem.
     
  14. Sam77 thread starter macrumors newbie

    Joined:
    Aug 17, 2010
    #14
    Your right, this problem is kinda strange.Maybe if i make a sample project and see if this thing recurs. If it does I'll post it up here for someone merciful to look at.
     
  15. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #15
    you should send that project to Apple as part of a bug report! :D
     
  16. Sam77 thread starter macrumors newbie

    Joined:
    Aug 17, 2010
    #16
    I should, though im probably doing something wrong here.

    I'll post a demo code!
     

Share This Page