reloadData synchronization issue
So here is my problem. In my app home page I have a simple UITableView that can hold maximum 3 sections, 2 sections with a maximum of 3 items each and the third section can hold a maximum of 6 items.
E开发者_运维百科ach section has a header associated with.
The actual content comes from web, I make a request, the server sends data, I parse the response with NSXMLParser, create the arrays to hold data for each section (3 arrays) and also create another array to keep reference of the sections I will have to display.
Then, in the end, I call [myTableView reloadData] to refresh content and redraw table. Users have also the possibility to refresh content themselves. For that I'm using a pull-to-refresh mechanism just like Facebook app.
Refreshing content using pull-to-refresh also ends up calling [myTableView reloadData]. I'm experiencing a nasty crash when I refresh my content and the table needs to change its lay out (e.g: the table currently holds all 3 sections and after I reloadData it will show only one section).
I used the debugger to track down the issue. Here is what I found:
- User pulls down the entire table for like 50 pixels and then releases
- An animation begins that brings the table "up" to accommodate the 320x50 view displaying a "Loading" message + a UIActivityIndicatorView animated on TOP of the table
- Refresh is triggered, I make a new webservice request in a background task, get new data, parse, update arrays, finally on main thread perform reloadData
- In the meantime, while refresh is doing all that I noticed that UITableView STILL sends delegate messages to :
- tableView:viewForHeaderInSection:
- tableView:cellForRowAtIndexPath:
I figure this happens because of the pull-to-refresh animation that "moves" the table up and reveals new cells/headers, thus calling the above.
The problem is that this causes a horrible crash because the arrays that hold data for sections and the sections array itself are modified concurrently it seems.
When this happens, the delegate methods probably use the update/not-yet-updated array of items/sections in the wrong context. That is, before
- numberOfSectionsInTableView:
- tableView: numberOfRowsInSection:
ever have a chance to update the new structure properly for the updated arrays.
I know it's long story without any code sample, but I figured that if I already know the problem (at least I hope I do) and if someone sees my point, maybe that someone can point me in the right direction to correct this synchronization issue.
Thanks for reading!
LATER EDIT:
I found where the issue was. My pull-to-refresh mechanism uses a stopLoading callback that looks like this:
- (void)stopLoading
{ isLoading = NO;
// Hide the header
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.3];
[UIView setAnimationDidStopSelector:@selector(stopLoadingComplete:finished:context:)];
self.myTableView.contentInset = UIEdgeInsetsZero;
[refreshArrow layer].transform = CATransform3DMakeRotation(M_PI * 2, 0, 0, 1);
[UIView commitAnimations];
}
At some point I reset the contentInset for my table setting it at UIEdgeInsetsZero. This and the fact that after background refresh returning on main thread I was calling stopLoading BEFORE reloadData - triggered the wrong delegate callbacks in the wrong moment.
So it was a timing issue between a change to my table's contentInset and reloadData.
It sounds like you are updating the data store used for the table view from different threads. This is not a good idea - the data can easily get into an inconsistent state, and the containers you are using might not be thread safe anyway.
The correct way is to only update this data from the main thread. Also, you might want to have a look at the methods to insert rows etc, which make a complete reload rarely necessary. And if you just want to update one single cell because of new data, then just make that cell refresh.
精彩评论