Обновление: на мой взгляд, вопрос все еще актуален, и поэтому я отмечаю потенциальный недостаток дизайна, который у меня был в моем коде. Я вызывал асинхронный метод viewWillAppear:
данных в viewWillAppear:
VC1, который НИКОГДА не является хорошим местом для заполнения данных и перезагрузки табличного представления, если все не сериализовано в основном потоке. В вашем коде всегда есть потенциальные точки выполнения, когда вы должны перезагрузить табличное представление, а viewWillAppear
не является одним из них. Я всегда перезагружал источник данных табличного представления в VC1 viewWillAppear
при возвращении из VC2. Но идеальный дизайн мог бы использовать переход от VC2 и заполнить источник данных при его подготовке (prepareForSegue
) прямо из VC2, только когда это действительно требовалось. К сожалению, похоже, никто до сих пор не упомянул об этом :(
Я думаю, что есть подобные вопросы, которые были заданы ранее. К сожалению, никто из них по сути не решил проблему, с которой я сталкиваюсь.
Моя структура проблемы очень проста. У меня есть два контроллера представления, скажем, VC1 и VC2. В VC1 я показываю список некоторых элементов в UITableView, загруженных из базы данных, а в VC2 я показываю детали выбранного элемента и позволяю его редактировать и сохранять. И когда пользователь возвращается в VC1 из VC2, я должен заново заполнить источник данных и перезагрузить таблицу. И VC1, и VC2 встроены в UINavigationController.
Звучит очень тривиально, и это действительно так, пока я не сделаю все в потоке пользовательского интерфейса. Проблема загрузки списка в VC1 отнимает много времени. Поэтому я должен делегировать тяжелую задачу по загрузке данных некоторому фоновому рабочему потоку и перезагружать таблицу в основном потоке только после завершения загрузки данных, чтобы обеспечить бесперебойную работу пользовательского интерфейса. Итак, моя первоначальная конструкция была похожа на следующую:
-(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
});
});
}
Это было очень функционально до iOS10, когда UITableView
прекратил немедленный рендеринг через reloadData
и начал обрабатывать reloadData
просто как запрос регистрации для перезагрузки UITableView
в некоторой последующей итерации цикла выполнения. Поэтому я обнаружил, что мое приложение иногда [self.tableView reloadData]
если [self.tableView reloadData]
не было завершено до последующего вызова [self populateData]
и это было очень очевидно, поскольку [self populateData]
больше не является поточно-ориентированным, и если источник данных Изменения до завершения reloadData
очень вероятно, что приложение reloadData
. Поэтому я попытался добавить семафор, чтобы сделать [self populateData]
поточно-ориентированным, и обнаружил, что он отлично работает. Моя последующая конструкция была похожа на следующую:
-(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
});
}
К сожалению, эта конструкция также сломалась после iOS11, когда я прокручивал UITableView
в VC1 вниз, выбирал элемент, который вызывает VC2, а затем возвращался к VC1. Он снова вызывает viewWillAppear:
VC1, который, в свою очередь, пытается пополнить источник данных через [self populateData]
. Но сбой трассировки стека показывает, что UITableView уже начал воссоздавать свои ячейки с нуля и вызывает tableView:cellForRowAtIndexPath:
метод по какой-то причине, даже до viewWillAppear:
где мой источник данных пополняется в фоновом режиме и находится в некотором несовместимом состоянии, В конце концов приложение вылетает. И что самое удивительное, это происходит только тогда, когда я сначала выбрал нижний ряд, которого не было на экране. Ниже приводится трассировка стека во время сбоя:
Я знаю, что все будет работать нормально, если я вызову оба метода из основного потока, например так:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self populateData]; //populate datasource
[self.tableView reloadData]; //reload table view
}
Но это не то, что ожидается для хорошего пользовательского опыта. Я чувствую, что проблема возникает, так как UITableView
пытается получить закадровые верхние строки при повторном появлении при прокрутке вниз. Но, к сожалению, после понимания стольких чертовых вещей я с трудом разобрался в этом.
Мне бы очень хотелось, чтобы эксперты этого сайта помогли мне выбраться из ситуации или показать, как это можно сделать. Большое спасибо заранее!
PS: self.application.commonWorkerQueue
- это очередь последовательной отправки, работающая в фоновом режиме в этом контексте.