На этот вопрос на самом деле есть три ответа. То есть есть три части этого вопроса:
- Как сохранить отзывчивость интерфейса
- Как сохранить обновление быстро
- Как сделать анимацию обновления таблицы плавной
Отзывчивость интерфейса
Чтобы решить первую проблему, я теперь проверяю, что на каждой итерации цикла основного события может быть доставлено не более одного сообщения обновления таблицы. Это предотвращает блокировку основного потока, если фоновый поток загружает данные быстрее, чем он может справиться с этим.
Это сделано благодаря примеру кода, присланного мне Byline автором Milo Bird, который я затем интегрировал в Dave Dribin DDInvocationGrabber . Этот интерфейс позволяет очень просто поставить в очередь метод, который будет вызван на следующей доступной итерации цикла основного события:
[[(id)delegate queueOnMainThread]
parserParsedEntries:parsedEntries
inPortal:parsedPortal];
Мне очень нравится, как легко использовать этот метод. Парсер теперь использует его для вызова всех методов делегата, большинство из которых обновляют пользовательский интерфейс. Я выпустил этот код на GitHub .
Производительность
Что касается производительности, я изначально обновлял по одной строке UITableView за раз. Это было эффективно, но несколько неэффективно. Я вернулся и изучил пример XMLPerformance , где заметил, что анализатор ждал, пока он соберет 10 элементов, перед отправкой в основной поток для обновления таблицы. Это было ключом к поддержанию производительности без блокировки интерфейса путем обновления всех 500 строк одновременно. Я поиграл с обновлением 1, 10 и всех 500 строк за один вызов, а обновление 10, казалось, предложило лучший компромисс между производительностью и блокировкой пользовательского интерфейса. пять, наверное, тоже неплохо бы сработало.
Анимация
И, наконец, есть анимация. Наблюдая за сеансом «Мастеринг таблиц» WWDC 2010 , я понял, что мое использование методов deleteRowsAtIndexPaths:withRowAnimation:
и updateRowsAtIndexPaths:withRowAnimation:
было неправильным. Я следил за тем, где что-то должно быть добавлено и удалено в таблице, и корректировал индексы соответствующим образом, но оказывается, что в этом нет необходимости. Внутри блока обновления таблицы нужно только сослаться на индекс строки от до обновления, независимо от того, сколько можно вставить или удалить, чтобы изменить свою позицию. Блок обновлений, по-видимому, делает всю эту бухгалтерию за вас. (Пример ключевого примера приведен к отметке 8:45 в видео).
Таким образом, метод делегата, который обновляет таблицу для количества записей, переданных ей синтаксическим анализатором (в настоящее время 10 по времени), теперь явно отслеживает позиции строк, которые должны быть обновлены или удалены из до блок обновления, вот так:
NSMutableDictionary *oldIndexFor = [NSMutableDictionary dictionaryWithCapacity:posts.count];
int i = 0;
for (PostModel *e in posts) {
[oldIndexFor setObject:[NSNumber numberWithInt:i++] forKey:e.ident];
}
NSMutableArray *insertPaths = [NSMutableArray array];
NSMutableArray *deletePaths = [NSMutableArray array];
NSMutableArray *reloadPaths = [NSMutableArray array];
BOOL modified = NO;
for (PostModel *entry in entries) {
NSNumber *num = [oldIndexFor objectForKey:entry.ident];
NSIndexPath *path = [NSIndexPath indexPathForRow:currentPostIndex inSection:0];
if (num == nil) {
modified = YES;
[insertPaths addObject:path];
[posts insertObject:entry atIndex:currentPostIndex];
} else {
// Find its current position in the array.
NSUInteger foundAt = [posts indexOfObject:entry];
if (foundAt == currentPostIndex) {
// Reload it if it has changed.
if (entry.savedState != PostModelSavedStateUnmodified) {
modified = YES;
[posts replaceObjectAtIndex:foundAt withObject:entry];
[reloadPaths addObject:[NSIndexPath indexPathForRow:num.intValue inSection:0]];
}
} else {
// Move it.
modified = YES;
[posts removeObjectAtIndex:foundAt];
[posts insertObject:entry atIndex:currentPostIndex];
[insertPaths addObject:path];
[deletePaths addObject:[NSIndexPath indexPathForRow:num.intValue inSection:0]];
}
}
currentPostIndex++;
}
if (modified) {
[tableView beginUpdates];
[tableView insertRowsAtIndexPaths:insertPaths withRowAnimation:UITableViewRowAnimationTop];
[tableView deleteRowsAtIndexPaths:deletePaths withRowAnimation:UITableViewRowAnimationBottom];
[tableView reloadRowsAtIndexPaths:reloadPaths withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
}
Комментарии приветствуются. Вполне возможно, что есть более эффективные способы сделать это (использование -[NSArray indexOfObject:]
особенно подозрительно для меня) и что я, возможно, пропустил некоторые другие тонкости.
Но даже в этом случае это огромное улучшение для моего приложения. Теперь пользовательский интерфейс остается (в основном) отзывчивым во время синхронизации, синхронизация выполняется быстро, и анимация обновления таблицы выглядит почти правильно.