UICollectionView reloadData кажется медленным / отстающим (не мгновенным)

Привет. Я использую представление коллекции в своем приложении, и я заметил, что на обновление reloadData больше времени, чем ожидалось. В моем представлении коллекции есть 1 section , и я тестирую его с 5 cell (каждый из которых имеет 2 кнопки и метку). Я поместил несколько журналов в свой код, чтобы показать, сколько времени система действительно принимает для обновления. Интересно, что журналы показывают, что он освежает быстрее, чем он. Например, на устройстве это займет до ~ 0.2 сек (заметное), но вот журналы:

0.007s С момента, когда reloadData вызывается на время cellForItemAtIndexPath вызывается в первый раз

0.002s На ячейку для загрузки и возврата

0.041s С момента, когда reloadData вызывается до момента reloadData ячейки # 5

В функции cellForItemAtIndexPath нет ничего особенно интенсивного (в основном просто находит словарь с тремя значениями в NSArray в indexPath ). Однако, когда я удалил это и просто вернул ячейку с пустой кнопкой, я увидел такое же поведение.

Кто-нибудь знает, почему это может произойти? Это происходит только на физическом устройстве (iPad Air). Благодаря!

EDIT # 1

Комментарий Per @ brian-nickel, я использовал инструмент Time Profiler и обнаружил, что он действительно всплескивается каждый раз, reloadData вызывается reloadData . Вот скриншот:

Профайлер времени

@ArtSabintsev, вот функция, окружающая вызов reloadData , за которым следует cellForItemAtIndexPath :

 //Arrays were just reset, load new data into them //Loop through each team for (NSString *team in moveUnitsView.teamsDisplaying) { //CURRENT TEAM WILL COME FIRST //Create an array for this team NSMutableArray *teamArr = [NSMutableArray new]; //Loop through all units for (int i = [Universal units]; i > 0; i--) { //Set the unit type to a string NSString *unitType = [Universal unitWithTag:i]; //Get counts depending on the team if ([team isEqualToString:currentTeam.text]) { //Get the number of units of this type so that it supports units on transports. If the territory is a sea territory and the current unit is a ground unit, check the units in the transports instead of normal units int unitCount = (ter.isSeaTerritory && (i == 1 || i == 2 || i == 8)) ? [self sumOfUnitsInTransportsOfType:unitType onTerritory:ter onTeam:team] : [ter sumOfUnitsOfType:unitType onTeam:team]; //Get the number of movable units on this territory int movableCount = 0; if (queue.selectedTerr != nil && queue.selectedTerr != ter) { //This is here to prevent the user from selecting units on another territory while moving units from one territory movableCount = 0; } else if (ter.isSeaTerritory && (i == 1 || i == 2 || i == 8)) { //Units on transports - can be an enemy territory movableCount = [self sumOfUnitsInTransportsOfType:unitType onTerritory:ter onTeam:team]; } else if ([Universal allianceExistsBetweenTeam:team andTeam:ter.currentOwner] || i == 3 || i == 9) { //Other units - only planes can be on an enemy territory movableCount = [ter sumOfMovableUnitsOfType:unitType onTeam:team]; } //See if there are units of this type on this territory on this team if (unitCount > 0) { //Add data to this team's dictionary NSMutableDictionary *unitInfo = [NSMutableDictionary new]; [unitInfo setObject:@(i) forKey:@"UnitTag"]; [unitInfo setObject:unitType forKey:@"UnitType"]; [unitInfo setObject:@(unitCount) forKey:@"Count"]; [unitInfo setObject:@(movableCount) forKey:@"MovableCount"]; [unitInfo setObject:team forKey:@"Team"]; //Add the dictionary [teamArr addObject:unitInfo]; //Increment the counter if (unitsOnCT) { //Must check or it could cause a crash *unitsOnCT += 1; } } } } //Add the team array [moveUnitsView.unitData addObject:teamArr]; } //Reload the data in the collection view [moveUnitsView.collectionV reloadData]; 

И мой cellForItemAtIndexPath :

  //Dequeue a cell UnitSelectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"UnitSelectionCell" forIndexPath:indexPath]; //Get the team array (at the index of the section), then the unit's data (at the index of the row) NSMutableDictionary *unitData = (moveUnitsView.unitData[indexPath.section])[indexPath.row]; //Get values int unitTag = [[unitData objectForKey:@"UnitTag"] intValue]; int count = [[unitData objectForKey:@"Count"] intValue]; int movableCount = [[unitData objectForKey:@"MovableCount"] intValue]; NSString *unitType = [unitData objectForKey:@"UnitType"]; //Set the cell's values [cell.upB addTarget:self action:@selector(upMoveUnits:) forControlEvents:UIControlEventTouchUpInside]; [cell.upB setTag:unitTag]; [cell.iconB setBackgroundImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[Universal imageNameForUnit:unitType team:[unitData objectForKey:@"Team"]] ofType:nil]] forState:UIControlStateNormal]; [cell.iconB setTitle:[Universal strForExpDisplay:count] forState:UIControlStateNormal]; [Universal adjustTitlePlacementOfB:cell.iconB autosize:FALSE]; //Don't autosize because this is a collection view cell.unitTypeL.text = unitType; cell.unitTypeL.adjustsFontSizeToFitWidth = cell.unitTypeL.adjustsLetterSpacingToFitWidth = TRUE; //Set fonts [Universal setFontForSubviewsOfView:cell]; //Return the cell return cell; 

Когда инициализируется представление коллекции, ячейки регистрируются с использованием:

 [moveUnitsView.collectionV registerNib:[UINib nibWithNibName:@"UnitSelectionCell" bundle:nil] forCellWithReuseIdentifier:@"UnitSelectionCell"]; 

EDIT # 2

@roycable и @ aaron-brager отметили, что это может быть вызвано использованием imageWithContentsOfFile: Чтобы проверить это, я изменил cellForItemAtIndexPath на это:

  //Dequeue a cell UnitSelectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"UnitSelectionCell" forIndexPath:indexPath]; //Get the team array (at the index of the section), then the unit's data (at the index of the row) NSMutableDictionary *unitData = (moveUnitsView.unitData[indexPath.section])[indexPath.row]; //Get values int unitTag = [[unitData objectForKey:@"UnitTag"] intValue]; [cell setBackgroundColor:[UIColor redColor]]; [cell.upB removeTarget:nil action:NULL forControlEvents:UIControlEventTouchUpInside]; [cell.upB addTarget:self action:@selector(upMoveUnits:) forControlEvents:UIControlEventTouchUpInside]; [cell.upB setTag:unitTag]; //Return the cell return cell; 

Как ни странно, это, похоже, не устраняет проблему. Это буквально не делает интенсивных задач в этой функции, но все еще кажется отстающим (и Time Profiler, похоже, подтверждает это).

В ответ на запросы кода от Universal , вместо размещения кода я просто подведу его:

+units только возвращает 17

+unitWithTag: использует switch для возврата NSString соответствующего числу между 1-17

+allianceExistsBetweenTeam: видит, содержит ли массив одну из строк

+setFontForSubviewsOfView: рекурсивная функция, которая в основном использует этот код

К сожалению, это не кажется очень важным, поскольку проблема все еще происходит с упрощенной функцией cellForItemAtIndexPath .

Я также внедрил новые предложения @ aaron-brager. Я удалил target прежде чем добавлять новую, и внес изменения в Time Profiler. Я не видел ничего действительно выскочить … Вот скриншот. Все, что связано с UIImage имеет никакого отношения к этому вопросу, как и NSKeyedArchiver , поэтому единственное, что действительно имеет смысл, это строки, массивы и словари:

Профайлер времени 2

Любая помощь приветствуется – мне действительно нужно получить это исправление (отсюда и щедрость). Спасибо!

Изменить №3 – Определено решение

Таким образом, оказывается, что проблема не была в любой из этих функций. Проблема была в функции (давайте назовем ее Function A ), которая вызвала вышеописанную функцию обновления (назовем ее Function B ). Сразу после Function A называемой Function B , она выполняла задачу с интенсивным использованием ЦП. Я не знал о том, что reloadData хотя бы частично асинхронен , поэтому я беру на себя задачу с интенсивным использованием процессора и reloadData . Я решил свою проблему, добавив следующее перед return cell; :

  if (indexPath.row == [self collectionView:collectionView numberOfItemsInSection:indexPath.section] - 1) { [self performSelector:@selector(performMyCPUIntensiveTask:) withObject:myObject afterDelay:0.1]; } 

Надеюсь, это поможет кому-то еще в будущем. Спасибо всем, кто помог, я искренне ценю это.

Некоторые возможности:

  • Ваша предположительно рекурсивная функция для установки шрифтов, вероятно, дорогая.
  • Некоторые из других Universal функций выглядят так, как будто они могут быть дорогими.
  • Не кажется, что вы удаляете цель кнопки, и каждый раз, когда ячейка повторно используется, вы добавляете к ней дополнительные цели.
  • imageWithContentsOfFile: пропускает кеш; используйте imageNamed: вместо этого.

Убедитесь, что вы находитесь в основном потоке, когда вы вызываете reloadData .

 NSLog("on main thread: %@", [NSThread isMainThread] ? @"YES" : @"NO"); 

Если вы не используете GCD для отправки сообщения в основной поток:

 dispatch_async(dispatch_get_main_queue(), ^{ [moveUnitsView.collectionV reloadData]; }); 

(Не уверен в синтаксисе на 100%, я просто набрал это в браузере)