UITableView reloadData在iOS 11中重新出现时崩溃

Aya*_*pta 10 objective-c thread-safety uitableview ios ios11

我认为之前有过类似的问题.不幸的是,他们都没有解决我面临的问题.

我的问题结构非常简单.我有两个视图控制器,比如VC1和VC2.在VC1中,我显示了一个UITableView中的一些项目列表,从数据库加载,在VC2中我显示了所选项目的详细信息,并进行编辑和保存.当用户从VC2返回VC1时,我必须重新填充数据源并重新加载表.VC1和VC2都嵌入在UINavigationController中.

听起来非常微不足道,确实是这样,直到我在UI线程中做了所有事情.问题是在VC1中加载列表有点费时.因此,我必须将繁重的数据加载任务委托给一些后台工作线程,并在数据加载完成时重新加载主线程上的表,以提供流畅的UI体验.所以我的初始构造类似于以下内容:

-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    dispatch_async(self.application.commonWorkerQueue, ^{
        [self populateData]; //populate datasource
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadData]; //reload table view
        });
    });
}
Run Code Online (Sandbox Code Playgroud)

这个功能非常实用,直到iOS10从viewWillAppear:停止立即渲染viewWillAppear开始,并开始将其viewWillAppear视为注册请求,以便prepareForSegue在运行循环的某些后续迭代中重新加载.所以我发现我的应用程序开始偶尔崩溃,如果UITableView在后续调用之前没有完成reloadData,这是非常明显的,因为reloadData不再是线程安全的,如果数据源在完成之前更改UITableView很可能会使应用程序崩溃.所以我尝试添加一个信号量以使[self.tableView reloadData]线程安全,我发现它工作得很好.我后来的构造类似于以下内容:

-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    dispatch_async(self.application.commonWorkerQueue, ^{
        [self populateData]; //populate datasource
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadData]; //reload table view
            dispatch_async(dispatch_get_main_queue(), ^{
                dispatch_semaphore_signal(self.datasourceSyncSemaphore); //let the app know that it is free to repopulate datasource again
            });
        });
        dispatch_semaphore_wait(self.datasourceSyncSemaphore, DISPATCH_TIME_FOREVER); //wait on a semaphore so that datasource repopulation is blocked until tableView reloading completes
    });
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,自从iOS11 向下滚动[self populateData] VC1 ,这个结构也破,选择了一个调出VC2的项目,然后再回到VC1.它再次调用[self populateData]VC1,然后VC1尝试重新填充数据源reloadData.但是崩溃的堆栈跟踪显示UITableView已经开始从头开始重新创建它的单元格并且[self populateData]出于某种原因调用方法,甚至之前UITableView,我的数据源在后台重新填充,并且它处于某种不一致的状态.最终应用程序崩溃了.最令人惊讶的是,只有当我选择了最初不在屏幕上的底行时才会发生这种情况.以下是崩溃期间的堆栈跟踪:

崩溃的堆栈跟踪

我知道如果我从主线程中调用两个方法,一切都会正常运行,如下所示:

-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self populateData]; //populate datasource
    [self.tableView reloadData]; //reload table view
}
Run Code Online (Sandbox Code Playgroud)

但这不是预期的良好用户体验.我觉得问题发生了,因为viewWillAppear:在向下滚动时尝试在重新显示时获取屏幕外顶行.但不幸的是,在理解了这么多该死的东西后,我几乎无法解决这个问题.

我真的希望这个网站的专家能够帮助我摆脱困境,或者向我展示一些方法.感谢提前加载!

PS:[self populateData]是在此上下文中在后台运行的串行调度队列.

Ric*_*ick 6

你应该拆分你的populateData功能.让我们说例如fetchDatabaseRowspopulateDataWithRows.本fetchDatabaseRows应在自己的线程和一个新的数据结构检索行到内存中.完成IO部分后,您应该在UI线程中调用populateDataWithRows(然后reloadData).populateDataWithRows应该修改TableView使用的集合.