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

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

0.007s С момента вызова reloadData до момента первого вызова cellForItemAtIndexPath

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

0.041s С момента вызова reloadData до момента возврата ячейки №5

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

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

ИЗМЕНИТЬ №1

Согласно комментарию @brian-nickel, я использовал инструмент Time Profiler и обнаружил, что он действительно всплескивает каждый раз, когда вызывается 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"];

РЕДАКТИРОВАНИЕ №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];
    }

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


person rebello95    schedule 02.09.2014    source источник
comment
Использовали ли вы Профилировщик времени ? Если вы столкнулись с задержкой в ​​200 мс, вы должны увидеть большой всплеск после перезагрузки.   -  person Brian Nickel♦    schedule 04.09.2014
comment
Не могли бы вы предоставить нам весь свой пример кода? Кроме того, вы используете многоразовые ячейки?   -  person ArtSabintsev    schedule 04.09.2014
comment
Спасибо за ваши ответы, ребята. Я добавил информацию в ответ на оба ваших комментария - смотрите мое редактирование.   -  person rebello95    schedule 04.09.2014
comment
Установите флажок показывать только Objective-C и инвертировать дерево вызовов в профилировщике времени. Это даст вам лучшее представление о том, какой код занимает какое количество времени.   -  person Aaron Brager    schedule 04.09.2014
comment
Я согласен с @ArtSabintsev. Вы должны показать код для ваших Universal методов.   -  person Aaron Brager    schedule 04.09.2014
comment
У меня были проблемы с производительностью в прошлом с UICollectionViews, потому что я загружал изображения с диска в каждый cellForItemAtIndexPath, что, я вижу, вы также делаете (imageWithContentsOfFile:). Если вы можете кэшировать изображения, чтобы они не загружались с диска каждый раз, это может помочь перезагрузке. Если нет, то это должно хотя бы помочь скорости прокрутки в целом.   -  person roycable    schedule 04.09.2014
comment
В среднем, насколько велико [Universal units]? Кроме того, я склонен согласиться с @roycable. Это может быть связано с тем, что вы загружаете изображения с диска. Посмотрите, сможете ли вы закомментировать различные части кода cellForItemAtIndexPath:, и посмотрите, какие части вызывают появление всплеска, когда вы нажимаете кнопку перезагрузки.   -  person ArtSabintsev    schedule 04.09.2014
comment
Спасибо, парни. Я пробовал все, что только что было упомянуто, и обновил свой вопрос. [Universal units] — очень простая функция, содержащая одну строку: return 17;. Что касается загрузки изображений, см. мой обновленный вопрос.   -  person rebello95    schedule 04.09.2014
comment
Итак, это следует спросить ради вопроса, но используете ли вы бета-версию Xcode 5 или Xcode 6?   -  person ArtSabintsev    schedule 04.09.2014
comment
Я использую Xcode 5, тестирую на iPad Air с последней версией iOS 7. Однако на симуляторе нет задержек.   -  person rebello95    schedule 04.09.2014
comment
О черт... Кажется, я понял это. Есть функция с интенсивным использованием ЦП, которая вызывается сразу после функции, вызывающей reloadData (та, что показана выше). Когда я комментирую этот вызов функции, задержка исчезает. Может ли кто-нибудь объяснить, почему это может быть причиной, даже если интенсивная функция вызывается после перезагрузки данных?   -  person rebello95    schedule 04.09.2014
comment
Я решил проблему. Если кто-то в будущем ищет решение, ознакомьтесь с ответом @aaron-brager ниже и моим третьим редактированием выше. Спасибо всем, кто помог!   -  person rebello95    schedule 04.09.2014


Ответы (2)


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

  • Ваша предположительно рекурсивная функция для установки шрифтов, вероятно, дорогая.
  • Некоторые другие функции Universal выглядят дорого.
  • Не похоже, чтобы вы когда-либо удаляли цель кнопки, и каждый раз, когда ячейка повторно используется, вы добавляете к ней дополнительные цели.
  • imageWithContentsOfFile: пропускает кеш; вместо этого используйте imageNamed:.
person Aaron Brager    schedule 04.09.2014
comment
+1, принято и + награда (после того, как срок позволит мне) за всю вашу помощь, хотя источником проблемы была моя глупость в другой функции. Спасибо! - person rebello95; 04.09.2014

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

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

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

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

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

person Thomas Müller    schedule 04.09.2014
comment
Только что подтвердил, что это действительно работает в основном потоке. Спасибо хоть. - person rebello95; 04.09.2014
comment
Для меня в Swift потребовалось несколько секунд от вызова reloadData до обновления коллекции. Это решило это. - person t0PPy; 23.03.2016