reload数据同步问题
所以这是我的问题。 在我的应用程序主页中,我有一个简单的 UITableView,最多可容纳 3 个部分,其中 2 个部分最多可容纳 3 个项目,第三个部分最多可容纳 6 个项目。
每个部分都有一个与之关联的标题。
实际内容来自网络,我发出请求,服务器发送数据,我使用 NSXMLParser 解析响应,创建数组来保存每个部分的数据(3 个数组),并创建另一个数组来保留我将要引用的部分必须显示。
然后,最后,我调用 [myTableView reloadData] 刷新内容并重绘表格。 用户还可以自行刷新内容。为此,我使用了拉动刷新机制,就像 Facebook 应用程序一样。
使用下拉刷新刷新内容也会最终调用 [myTableView reloadData]。 当我刷新内容并且表格需要更改其布局时,我遇到了严重的崩溃(例如:表格当前包含所有 3 个部分,在我重新加载数据后,它将仅显示一个部分)。
我使用调试器来追踪问题。这是我发现的内容:
- 用户将整个表格下拉大约 50 个像素,然后释放
- 动画开始,将表格“向上”以容纳显示“正在加载”消息的 320x50 视图 + 在表格顶部动画的 UIActivityIndicatorView
- 刷新被触发后,我在后台任务中发出一个新的 Web 服务请求,获取新数据,解析,更新数组,最后在主线程上执行 reloadData
- 同时,当刷新正在执行所有操作时,我注意到 UITableView 仍然将委托消息发送到:
- tableView:viewForHeaderInSection:
- tableView:cellForRowAtIndexPath:
我认为发生这种情况是因为下拉刷新动画“移动”表格并显示新的单元格/标题,从而调用上面的内容。
问题是,这会导致可怕的崩溃,因为保存节数据的数组和节数组本身似乎是同时修改的。
发生这种情况时,委托方法可能在错误的上下文中使用更新/尚未更新的项目/部分数组。也就是说,在
- numberOfSectionsInTableView:
- tableView: numberOfRowsInSection:
之前有机会为更新后的数组正确更新新结构。
我知道这是一个很长的故事,没有任何代码示例,但我想如果我已经知道问题(至少我希望我知道)并且如果有人明白我的观点,也许有人可以为我指出正确的方向来纠正此同步问题。
感谢您的阅读!
稍后编辑:
我找到了问题所在。我的拉动刷新机制使用 stopLoading 回调,如下所示
- (void)stopLoading
: 正在加载=否;
// 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];
在某些时候,
我重置了表的 contentInset,将其设置为 UIEdgeInsetsZero。 这以及在主线程上返回后台刷新后我在 reloadData 之前调用 stopLoading 的事实 - 在错误的时刻触发了错误的委托回调。
所以这是对我的表的 contentInset 和 reloadData 的更改之间的计时问题。
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.
Each 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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
听起来您正在从不同的线程更新用于表视图的数据存储。这不是一个好主意 - 数据很容易进入不一致的状态,并且您使用的容器无论如何都可能不是线程安全的。
正确的方法是仅从主线程更新此数据。另外,您可能想看看插入行等的方法,这使得很少需要完全重新加载。如果您只想因新数据而更新一个单元格,则只需刷新该单元格即可。
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.