UITableView动态动画(beginUpdates / endUpdates)有时会因不一致而崩溃

时间:2014-03-01 23:49:49

标签: ios multithreading cocoa-touch uitableview

我有一个与后端通信的异步工作线程,如果需要,可以在我的CoreData模型中创建新对象,或者只是在需要时对现有对象进行更改以反映"服务器真相" ,尤其是我称之为status的属性(整数)。

每当这样的更改到对象时,它就会通过NSNotificationCenter触发到所有UI订阅者,其中包含一个UITableViewController,它包含所有对象的精美列表,使用部分来按status对对象进行分组。因此,状态为0的所有对象都放在一个部分中,状态为1的所有对象都放在另一个部分中,等等......

由于它是一个异步工作者线程,我设置我的CoreData-stack以使主上下文连接到PSC,而每个异步线程都拥有它自己的私有上下文将更改合并到主上下文,然后持久存储。

问题

在与后端通信时检测到更改的异步工作线程,在通过其私有上下文更新CoreData模型后,运行postNotification代码

[[NSNotificationCenter defaultCenter] postNotificationName:@"ObjectChanged"
                                                    object:self
                                                  userInfo:userInfo];

...这反过来导致所有注册的订阅者开始由相同的异步线程触发。事情有点奇怪,因为必须在主线程中调用UI-stuff,而且当触发postNotification时,我的UI(持有每个状态的部分的表视图)将会删除/插入tableView中的一些单元格反映检测到的变化。

但是,由于我无法直接注入UI更改,因此我在发送的字典中准备添加已删除 NSIndexPath在主线程中执行;

NSMutableArray* addedIndexPaths = [[NSMutableArray alloc] init];
NSMutableArray* removedIndexPaths = [[NSMutableArray alloc] init];

//... based on some logic, remove and/or add
[addedIndexPaths addObject:[NSIndexPath indexPathForRow:xxx inSection:yyy];  

...

[removedIndexPaths addObject:[NSIndexPath indexPathForRow:xxx inSection:yyy];         

...

//Anything to animate...?
if (addedIndexPaths.count > 0 || removedIndexPaths.count > 0)
{
    NSDictionary* animParams = [NSDictionary dictionaryWithObjectsAndKeys:
                                    addedIndexPaths, @"added",
                                    removedIndexPaths, @"removed",
                                    nil];

    [self performSelectorOnMainThread:@selector(updateTableViewWithChanges:) withObject:animParams waitUntilDone:YES];
}

最终将我们带到updateTableViewWithChanges函数,由主线程运行;

- (void)updateTableViewWithChanges:(NSDictionary *)animParams
{
    NSArray* addedIndexPaths = [animParams objectForKey:@"added"];
    NSArray* removedIndexPaths = [animParams objectForKey:@"removed"];

    //Now, finally -- let's update UI!
    [self.tableView beginUpdates];
    if ([addedIndexPaths count] > 0)
        [self.tableView insertRowsAtIndexPaths:addedIndexPaths withRowAnimation:UITableViewRowAnimationTop];
    if ([removedIndexPaths count] > 0)
        [self.tableView deleteRowsAtIndexPaths:removedIndexPaths withRowAnimation:UITableViewRowAnimationTop];
    [self.tableView endUpdates];
}

一切都很好,大部分时间都是如此,但是在应用程序表更新期间由于不一致而导致应用程序偶尔崩溃;

  

* 由于未捕获的异常终止应用程序' NSInternalInconsistencyException',原因:'无效更新:第0部分中的行数无效。更新后的现有部分(1)必须等于更新前的该部分中包含的行数(2),加上或减去从该部分插入或删除的行数(插入1,删除0)和加或减去移入或移出该部分的行数(0移入,0移出)。'

我怀疑这是因为主线程决定更新它的用户界面,而我的"做了检测 - 更改"代码正在运行,导致UI状态已经是2-up-date,这意味着当我调用动态代码"在NSIndexPath xxx"中添加另一个时,这不会加起来。

您是否有其他方法可以实现我的目标?如果还在运行工作线程并逐步逐步更新UI,那么你还有什么方法可以实现你想要反映在UI 中的异步线程更新状态?我是在完全吠叫错误的树,还是在路上错过了什么?

期待您的想法和想法,如果您还在阅读 - 谢谢;)

编辑(显示我如何从CoreData获取数据的代码,因为我怀疑这个崩溃的根pif的时间安排)..

异步工作线程所持有的私有上下文被合并到主要联系人,然后根据建议(使用performBlockAndWait)保存,但也许是工作者线程在通知订阅者之前完成的实际保存已经改变,花费时间(即使我使用performBlockAndWait,而不是performBlock),并且当主要上下文用于获取对象进行渲染时,它会花费时间不一致吗?

这是我读取/获取对象时的代码(通过其唯一的GUID,它只是一个属性);

NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                        guid, @"GUID", nil];

NSFetchRequest *fetchRequest = [self.managedObjectModel fetchRequestFromTemplateWithName:@"FindObjectByGuid" substitutionVariables:substitutionDictionary];

//Run the fetch!
NSError *error = nil;

//Uses proper context depending on if it's a worker thread or the main context/UI
NSArray *results = [context executeFetchRequest:fetchRequest error:&error];
if (error == nil)
{
    //Great! We did it!
    NSLog(@"Great! Success fetched %lu object(s) from PS mathing given GUID", (unsigned long)[results count]);
    if ([results count] > 0)
        object = [results objectAtIndex:0];
}

/马库斯

1 个答案:

答案 0 :(得分:1)

嗯,是时候总结一下了。作为iOS的相当 new ,我习惯于从头开始自己做所有事情,显然我建模并实现了一个基于发布/订阅的机制,花了很多时间来确保它是通用的自动比较CoreData中的已添加/更改对象与已存在的对象,在NSOrderedSets中提取索引并准备好的通知消息以及接收用户界面可能要为表视图正确设置动画的所有内容...

但是......除了我从未获得完全线程安全的实现(我怀疑CoreData保存到PCS时序和/或来自不同异步线程的同一对象的多个更改),@ Timothy Moose指出我朝着NSFetchedResultsController行事,它作用于CoreData主要上下文(当然是设置这样做)并将好的钩子委托给我的UITableView,非常类似于我在发布/订阅通知中自己做的事情 - 但是,它只是有效。

而不是手动自己编写代码来提取关系和属性匹配特定条件的对象子集,我的NSFetchRequest的谓词现在为我做了,并告诉我确切的变化,以及我应该如何修改我的UITableView也是如此。

奖金;我很乐意摆脱大量的代码,只是相信我总是展示我的CoreData模型的当前状态。无论我有多少异步工作线程,无论他们做了什么 - 当它们从相应的私有上下文存储/合并到主上下文时,我会得到很好的委托。